I got pretty common problem (I think). "REST" API, .NET 8, MediatR CQRS, EF Core
Let's assume we have the following classes:
public class OrderHeader : AuditableEntity
{
public required string AddressId { get; init; }
public Address Address { get; set; } = null!;
//...
}
public class Address : AuditableEntity
{
public required string Street { get; init; }
//...
}
Let's say the order is done. Everything is saved and fine. Then, I do a PUT
command on the Address
object which is a navigation property of our completed Order
, and we change the street for example.
Then in my frontend, even if order is done, in history view, the address will be overwritten with the new one, and we all know it must not happen.
Until now, I've dealt with such a problem in following way:
public class OrderHeader : AuditableEntity
{
public required string AddressId { get; init; }
public Address Address { get; set; } = null!;
public string PersistentAddressStreet { get; set; } = "";
//...
}
In POST CreateOrderHeaderQueryHandler
, I'd just map all of the Address
properties into PersistantAddressProperty
properties, which results in permanently saved address.
Is there any better, more clean, more commonly used way of achieving same thing? I don't really know DDD, which maybe could help, or maybe my solution is just fine because it is just working?
Many ERP systems would handle this in a fashion similar to the following:
You could create a "Master" table for your addresses, e.g. AddressBook (common ERP naming). This would store "template" data for each of your customers, distributors, shipping facilities, vendors, etc.
Then create another table, like "OrderAddress". This would have a very similar schema to "AddressBook", but it would have a foreign key (e.g. "OrderNumber", maybe also "OrderLine") to relate it to "OrderHeader". This would be the navigation property in your example. When creating a new OrderHeader, it could pull from addresses stored in "AddressBook" but copies the data to "OrderAddress" rather than relating directly to it.
As a result, the data in "OrderAddress" becomes uncoupled from that in "AddressBook", so you can edit the data in either table without affecting the other.
One more recommendation if you take this approach: Use an interface (e.g. IAddress
) to enforce having common properties between the two tables where the schema overlaps, this could potentially save a bunch of headaches, and you might find places where you can leverage the use of the interface rather than the concrete type as well.