Reputation: 871
So here's the scenario:
DDD states that you use a repository to get the aggregate root, then use that to add/remove to any collections it has.
Adding is simple, you simple call .Add(Item item)
on the Collection
you wish to add to. A new row is added to the database when you save. However, deleting is different - calling .Remove(Item item)
doesn't remove the item from the database, it simply removes the foreign key. So while, yes, it is technically no longer part of the collection anymore, it's still in the database.
Reading around, the only solution is to delete it using the data context. But according to DDD the domain object shouldn't be aware of the data context so therefore deleting will have to be done outside of the domain.
What is the right way to go about this? Or Is leaving the database full of orphans acceptable (perhaps running a routine to clear them out)?
Upvotes: 21
Views: 5843
Reputation: 742
I solved this scenario by configuring the reference column as required and the delete behavior as Cascade
Example:
modelBuilder.Entity<AggregateRoot>()
.HasMany(x => x.Items)
.WithOne()
.IsRequired()
.OnDelete(DeleteBehavior.Cascade);
In this case, EF Core (6.x) no longer set the reference column to NULL
, but deleted the record just by removing the Item
from the Items
collection of the aggregate root.
The decisive configuration here was the delete behavior Cascade
.
Upvotes: 2
Reputation: 7135
I've solved this problem in the application I'm currently working on by using domain events; a DDD concept Eric Evans said should have been in his book.
While domain objects aren't allowed to know about the object context, an IDomainEventHandler
is - I've therefore got a DomainObjectDeletionHandler
which deletes 'removed' objects from the object context before control returns to my application layer and the changes are saved.
For more information, I've written a blog about my implementation of domain events and how I approached hooking everything together.
Hope that helps :)
Edit
For example, if you have an Order
class which has an OrderItems
collection of type OrderItem
:
public class Order
{
// Other stuff
public void RemoveOrderItem(int orderItemId)
{
var orderItemToRemove = OrderItems.First(oi => oi.Id == orderItemId)
OrderItems.Remove(orderItemToRemove);
DomainEvents.Raise(new OrderItemRemoved(orderItemToRemove));
}
}
Upvotes: 13
Reputation: 24994
Why not use two repositories?
var parent = ParentRepo.Get(parentId);
parent.Children.Remove(childId); // remove it from the property Collection
ChildRepo.Delete(childId); // delete it from the database
ParentRepo.Commit(); // calls underlying context.SaveChanges()
Assuming you're sharing contexts via IOC/DI, calling commit with one repo will commit for both, otherwise just call ChildRepo.Commit
as well.
Upvotes: 0
Reputation: 457
When removing a child entity from a collection, EF will leave it as orphan, removing just the foreign key.
If you don't want to explicitly remove it using the DbContext, you can use what it is called "Identifying Relationship" (http://msdn.microsoft.com/en-us/library/ee373856.aspx at the bottom).
The trick is to set a composite primary key on the child including the parent's primary key.
Once you do that, when removing the entity from the parent's collection, it will be removed from the table as well.
Upvotes: 5
Reputation:
I do not know if this is by design, but if a detail object has a composite key containing its master object's key columns, it will be automatically deleted if you remove it from the master object's collection. If you have an Order object with an OrderID key and ICollection OrderLines navigation property, give OrderLine a composite key containing OrderID and OrderLineID.
But since I do not know if I can rely on that, the solution I've used myself is to let EF handle it the way it does, and fix up 'detached' (not in EF terms) detail objects on the call to SaveChanges(), enumerating over all modified entities and changing the state to deleted as appropriate.
Upvotes: 3