Question

When deleting a model (aggregate root) from the repository, all associated aggregates must be deleted too.

I am struggling to implement this in my Entity Framework 6 implementation of the repository pattern

In my example, I want to delete a Customer from the CustomerRepository. All the customer's Order objects should also be deleted.

Repository (stripped down):

public interface IRepository<T> where T : DomainEntity
{
    void Remove(T item);       
}

public class EntityFrameworkRepository<T> : IRepository<T> where T : DomainEntity
{
    private readonly DbSet<T> dbSet;
    public DbContext context;

    public EntityFrameworkRepository(IUnitOfWork unitOfWork)
    {
        context = entityFrameworkUnitOfWork.context;
        dbSet = dbSet = context.Set<T>();
    }

    public virtual void Remove(T item)
    {
        DbEntityEntry dbEntityEntry = context.Entry(item);

        if (dbEntityEntry.State == EntityState.Detached)
        {
            dbSet.Attach(item);
        }

        dbSet.Remove(item);
    }
}

public class EntityFrameworkUnitOfWork : IUnitOfWork
{
    public readonly DbContext context;

    public EntityFrameworkUnitOfWork()
    {
        this.context = new ReleaseContext();
    }

    public void Commit()
    {
        context.SaveChanges();
    }
}

ICustomerRepository and CustomerRepository (EF implementation):

public interface ICustomerRepository : IRepository<Customer>
{
    IEnumerable<Customer> GetAllActive();
}

public class CustomerRepository : EntityFrameworkRepository<Customer>, ICustomerRepository
{
    public CustomerRepository(IUnitOfWork unitOfWork)
        : base(unitOfWork)
    { }

    public override void Remove(Order item)
    {
        item.Orders.Clear();

        base.Remove(item);
    }
}

Client-code:

customerRepository.Remove(customer);
unitOfWork.Commit();

Exception thrown:

System.InvalidOperationException: The operation failed: The relationship could not be changed because one or more of the foreign-key properties is non-nullable. When a change is made to a relationship, the related foreign-key property is set to a null value. If the foreign-key does not support null values, a new relationship must be defined, the foreign-key property must be assigned another non-null value, or the unrelated object must be deleted.

I would except calling item.Orders.Clear() to indicate to EF that the associations must be deleted.

Was it helpful?

Solution 2

There is a good practice: don't delete anything :) Mark it as "deleted" instead.

Because WHY? Show us a real business requirement to physically delete stuff? Not only it slows things down (usually DBs lock a lot while deleting), causes fragmentation, etc., but in most of the cases it is absurd! No business would allow you do physically delete a customer and a list of orders!

Business does not delete anything. In the real business no one will go an find all the papers related to a particular customer and dispose of them in a shredder. Unless they did something illegal and FBI is knocking at the door :)

Talk to your business experts who know a little about computers (these are the real business experts). They will tell you what happens to customers when they stop being customers (perhaps they are "archived", perhaps something else, or perhaps just nothing) and then model it. It is us, programmers, usually invent the concept of "deleting" stuff.

Besides, analysing historical information can be really helpful some time in the future!

There are only two options when physical delete can be necessary:

  1. To save disk space (which is not a problem anymore when disk space is as cheap as dirt)
  2. To have some legal obligation to physically delete data when customer wants it to be deleted (which is a very rare requirement and usually is met in a certain domains).

For #1, again, space is not a problem these days so implementing delete can cost more than benefit from it. For #2 you want to be explicit anyway and you would probably manage your data storage differently. For example you may want to have a DB per client then so you can just drop the DB and all the backups to comply to the regulation (yes, you must remove backups in order to legally say that you don't hold the deleted data anymore)

So which case is yours? Why you want to delete, what are you real business requirements?

OTHER TIPS

The error suggests your clear method hasn't removed child entities.
Are you are aware of the fluent api addition cascade on delete ?

HasRequired(t => t.Parent).WithOptional().WillCascadeOnDelete(true);

So if you delete a root object, all dependents can be removed by Db. Although that option is not always available...

Since you are using IRepository... did you consider using some pattern like

public int DeleteWhere(Expression<Func<TPoco, bool>> predicate) {
    var delList = Context.Set<TPoco>().Where(predicate);
    foreach (var poco in delList) {
        SetEntityState(poco, EntityState.Deleted);
    }
    return delList.Count;
}

var custId = 1;   
var repOrder = new Respository<Order>();
var delCnt = repOrder.DeleteWhere(t => t.CustomerId == custId);

MyContext.SaveChanges()
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top