edgarian
edgarian

Reputation: 871

EF code first: How to delete a row from an entity's Collection while following DDD?

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

Answers (5)

ddegasperi
ddegasperi

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

Steve Wilkes
Steve Wilkes

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

drzaus
drzaus

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

Jorge Fioranelli
Jorge Fioranelli

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

user743382
user743382

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

Related Questions