Reputation: 672
This seems like a simple task but I still didn't find a solution.
I have a model class that represents a address and a class for orders:
internal class Address
{
[Key]
public Guid ID { get; set; }
/*fields for street, city, etc*/
}
internal class Order
{
[Key]
public Guid ID { get; set; }
/*other fields*/
public Address ShipToAddress { get; set; }
public Address BillToAddress { get; set; }
}
The problem is, that in like 95% of the cases, the ship to and the bill to address are the same.
Even if I set both fields to the same object, Entity Framework creates 2 rows in the database containing the exact same data (except for the ID of course).
Is there a (simple) way to avoid creating those duplicates?
This is how the order is created / inserted:
var billToAddress = new Address();
var shipToAddress = billToAddress;
var order = new Order
{
ShipToAddress = shipToAddress,
BillToAddress = billToAddress,
};
_dbContext.Orders.Add(order);
_dbContext.SaveChanges();
Upvotes: 1
Views: 785
Reputation: 16689
This behaviour occurs when the Same Address object is added to the Order object when the order object is Added to the Orders collection.
By default, when Creating a new object, all other navigation objects that do not have their [Key]
fields set, are assumed that they too need to be added to the database, this pattern can happen as well with other types of lookup fields and relationships.
There are a few solutions to this:
Change the workflow so that when adding Address
objects to the ShipTo or BillTo address they have already been committed to the database (their ID field will be set) then you can add the same Address to both fields without any issues.
Order
is saved to the database first.This is a bit more involved, but you could make an Address
Factory method that returns a new Address record that has already been committed to the database. Without using the factory pattern, your code might look like this:
var order = new Order();
var billToAddress = new Address();
_dbContext.Addresses.Add(billToAddress);
_dbContext.SaveChanges();
order.ShipToAddress = billToAddress;
order.BillToAddress = billToAddress;
_dbContext.Orders.Add(order);
_dbContext.SaveChanges();
You can override the SaveChanges
method on the DbContext
so that before save, you can iterate through the Order
changes, detect when Billto and ShipTo address objects are the same, then remove one before executing base.SaveChanges()
then add the reference back and then call base.SaveChanges
again.
Option 1 is a design choice, making this decision means the database side is always pretty simple, the complexity is in the user interface.
Although I have done option 3 a few times in the past, I prefer option 2 (when option 1 is not acceptable) as it keeps this logic close to the other logic associated with Order
s and Address
es, long term option 2 is easier to maintain.
You could look at another design pattern altogether, I like to make
Order
have a collection ofAddress
records and eachAddress
has anAddressType
which is an enum:Billing, Sender, Receiver...
Then in your UI, an
Address
withAddressType
=AddressTypes.Billing
address is always created first, because we always need this, the user can add new address records as they want to, when onlyBilling
address exists, all other address operations will use this reference.UI Validation and database constraints can be implemented to ensure that duplicate address records with the same type are no created.
Upvotes: 1