Max_dev
Max_dev

Reputation: 508

Deleting object from collection Domain Driven Design

In our project we are following Domain Driven Design with entity framework. Recently i came upon one issue where i want to delete a object from collection . Lets say Customer have collection of purchase. I want to remove the particular purchase of one customer.

 public class Customer
{
    public int ID { get; set; }

    public string Name { get; set; }

    public string Address { get; set; }

    public virtual ICollection<Purchase> Purchases { get; set; }
}

public class Purchase
{
    public int ID { get; set; }

    public int CustomerId { get; set; }

    public DateTime PurchaseDate { get; set; }
}

I tried to remove the purchase by

customer.Purchases.Remove(purchase);

but the above code removed only the relation not deleting the purchase from db . I want to remove them from db.

So to make that work i have given that responsibility to Repository and used context object inside repository to remove the purchase.

repository.DeletePurchase(purchase);

Is the above is right approach in Domain Driven Design or any possibilities to move the delete behavior within entity?

Pleas help me the follow the best practice to implement DDD.

Upvotes: 4

Views: 2174

Answers (3)

Chalky
Chalky

Reputation: 1642

First of all, let it happen on the domain entities: if Purchases are only accessed via a collection on the Customer then just remove it from there (you are doing this now).

Secondly, something outside the domain need to ensure the Customer is correctly persisted back to the store, including deleting the Purchases. Something also needs to supplement your particular ORM's logic to achieve that. A couple of options for where you might do that:

  1. Since this an EF-specific concern, override the SaveChanges method in your context, trap the error, delete those purchases from the DB (or check which Purchases in the DB but are not in the in-memory context, then delete from DB)

  2. Or, in your application layer, which is responsible for the lifecycle of your Customer, you could track the purchase IDs prior to calling whatever service or entity method is going to remove those. Then when that has returned, you check which purchase IDs are now missing from the Customer, call the repository method to delete them, and call repository SaveChanges(). Bit nasty, because there might be lots of places where you remove a Purchase.

Upvotes: 0

Ilya Palkin
Ilya Palkin

Reputation: 15737

If you are following Domain Driven Design, an implementation of your collections should follow best practices (e.g. collection should be exposed as read-only, without setter, with AddPurchase/ DeletePurchase methods).

I assume that Purchase is not deleted because context.SaveChanges() is not invoked.

Unfortunately it also might be a known issue Entity cannot be deleted from child collection but can be from context.

EDIT : There are two options and all of them are not good enough.

  1. context.Set<Purchase>().Remove(purchase); can be invoked.
  2. Composite Primary key (Id + CustomerId) can be used for Purchase entity.

        modelBuilder.Entity<Purchase>()
            .HasRequired(it => it.Customer)
            .WithMany(it => it.Purchases)
            .HasForeignKey(it => it.CustomerId);
    
        modelBuilder.Entity<Purchase>().HasKey(it => new
            {
                Id = it.Id,
                CustomerId = it.CustomerId
            });
    

    By default HasKey() prevents identity. That's why it should be specified as attribute.

    public class Purchase
    {
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public int Id { get; set; }
    
        public Customer Customer { get; set; }
    
        public int CustomerId { get; set; }
    
        public DateTime PurchaseDate { get; set; }
    }
    

There are some related discussions:

  1. Error when deleting one-to-many children
  2. A relationship is in the Deleted state
  3. Is it possible to remove child from collection and resolve issues on SaveChanges?

Upvotes: 4

Yves Reynhout
Yves Reynhout

Reputation: 2990

What is problematic to answer here is that I have no idea what behavior you're executing. "Removing the purchase of a customer" ... the more important question is why and when that's allowed/appropriate. Even then, purchases are somewhat of a transaction, what's the point of removing them (pretending they didn't happen, loosing history along the way). Your design is also problematic. What if I purchase something every day of the year? How many purchases will I need to load up just to remove that single purchase when we fast forward a year from now? How about modeling a purchase as a separate aggregate and just copy the id of the customer it is for? We can always find out all purchases of a customer using nothing but predicate on that customer id. Would that make sense in your context?

Time and time people forget to formulate the actual behavior they're trying to model, and adding proper context for the future reader is but a distant dream. Getting intimate with your domain and model are not optional.

Upvotes: 4

Related Questions