4

I am using EF6 with Generic Repository pattern. Recently I experienced a problem trying to delete a composite entity in a single go. Here is a simplified scenario:

public class Parent
{
    public int Id { get; set; }
    public string Name { get; set; }
    public virtual ICollection<Child> Children { get; set; }
}
public class Child
{
    public int Id { get; set; }
    public string Name { get; set; }

    [ForeignKey("Parent")]
    public int ParentId { get; set; }

    public virtual Parent Parent { get; set; }
}

For deleting the Parent entity with related Children I am doing something like this:

public virtual T GetById(int id)
{
    return this.DBSet.Find(id);
}
public virtual void Delete(T entity)
{
    DbEntityEntry entry = this.Context.Entry(entity);

    if (entry.State != EntityState.Deleted)
    {
        entry.State = EntityState.Deleted;
    }
    else
    {
        this.DBSet.Attach(entity);
        this.DBSet.Remove(entity);
    }
}

First I find the parent object by ID and then pass it to the delete method to change it's state to deleted. The context.SaveChanges() finally commits the delete.

This worked fine. The find method only pulled up Parent object and Delete worked since I have a cascade on delete enabled on Children.

But the moment I added another property in Child class:

[ForeignKey("Gender")]
public int GenderId { get; set; }

public virtual Gender Gender { get; set; }

For some reason EF started pulling related Children on the Parent.Find() method. Because of this I get the following error:

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.

Even after reverting the changes (removing the Gender property) the problem still exists. I am not able to understand this weird behavior!!

All I want to do is Delete the Parent object along with the Children. There are some solutions around it but none really serves my purpose:

  1. Turn LazyLoading to false - this.Configuration.LazyLoadingEnabled = false; This works but in my real application I need this property to true.
  2. Iterate all children first and Delete them and then delete the Parent. This seems at best a workaround and is very verbose.
  3. Use Remove() rather than just changing the EntityState to Deleted. I need to track Changes for Auditing so EntityState helps there.

Can someone explain why EF is loading related Entities even when I am not using them?

Rob
  • 26,989
  • 16
  • 82
  • 98
Amanvir Mundra
  • 420
  • 1
  • 6
  • 20
  • When you reverted, did you also revert the DB? Your problem is probably DB related. – Sefe Jan 15 '17 at 08:04
  • Yep I did. I dropped the DB and recreated it as well. – Amanvir Mundra Jan 15 '17 at 08:09
  • I doubt it's DB related because I am able to delete the parent entity with a sql query and cascade on delete works for children. – Amanvir Mundra Jan 15 '17 at 08:10
  • Also on disabling LazyLoading it works, so it looks more like context or EF related. – Amanvir Mundra Jan 15 '17 at 08:11
  • 1
    Do you need to load the entity first? You can delete it by creating a new instance, setting the id, attaching it to the context and then calling `Delete`. – Sefe Jan 15 '17 at 08:17
  • I am using generic repository pattern. The it is doing is by Finding the entity first and then setting its state to Delete. If I don't load the entity and change its state then I'll be losing in on the auditing functionality. – Amanvir Mundra Jan 15 '17 at 08:44
  • 1
    The fact that reverting the changes makes me suspicious. Are you sure this isn't being caused by your debugger inspecting `Children` and thus loading the rows? – Rob Jan 18 '17 at 12:24
  • It seems that without seeing the code that is calling `Delete()`, we can't really answer why the related entities are being loaded. Of course, because `Delete()` doesn't really know this either (without becoming more complicated) it shows the reason why you should be using `Remove()` rather than fiddling with `EntityState`. – Marc L. Jan 18 '17 at 15:50
  • 1
    @Rob thanks a ton. I feel like an idiot. It was actually the debugger that was loading the properties in the sample app :/ But in my real app the problem was different. Your comment gave me a new direction to investigate. Thanks. It seems that in my real app the context was not destroyed between requests. The problematic entity was loaded fully in the context in previous requests, and the delete request seemed to be actually pulling from the cache or something. I'll add an answer to explain in detail. – Amanvir Mundra Jan 19 '17 at 05:35
  • May be a precautionary again do this to revert the changes for foreign key, delete the database and all migration generated files in your project and then again do the process of enabling or adding the migration. As you said all is running fine until you did another foreign relation. All your schema of code first is already save in your context class so nothing to lose. – Mohtisham Zubair Jan 25 '17 at 07:59

1 Answers1

2

It seems that the problem was related to the life-cycle of context. I am using Unit Of Work and injecting it into my service layers using ninject.

kernel.Bind<IUnitOfWork>().To<UnitOfWork>().InRequestScope();

The UnitOWork class implements IDisposable.

public bool DeleteView(int viewId)
    {
        // This is a workaround. It seems ninject is not disposing the context. 
        // Because of that all the info (navigation properties) of a newly created view is presisted in the context.
        // Hence you get a referential key error when you try to delete a composite object.
        using (var context = new ApplicationDbContext())
        {
            var repo = new GenericRepository<CustomView>(context);
            var view = repo.GetById(viewId);
            repo.Delete(view);
            context.SaveChanges();
        }
        //var model = _unitOfWork.CustomViews.GetById(viewId);
        //_unitOfWork.CustomViews.Delete(model);
        //_unitOfWork.Save();

        return true;
    }

The commented code throws and error, while the un-commented one (using block) works. A controller method before this call loads the CustomView entity (which is of a similar structure as Parent with a list of children). And a subsequent user action can be triggered to delete that view.

I believe this has something to do with the context not being disposed. Maybe this has something to do with Ninject or UnitOfWork, I haven't been able to pin-point yet. The GetById() might be pulling the whole entity from context cache or something.

But the above workaround works for me. Just putting it out there so that it might help somebody.

Amanvir Mundra
  • 420
  • 1
  • 6
  • 20
  • Use Autofac - much more mature and easy to understand solution to lifetimes, scopes, disposing and units of work (per request). – Matt Kocaj Jan 19 '17 at 06:02
  • In other words, the whole post is incorrect and has nothing in common with EF, but patterns, injections etc. I think once the bounty expires, the question should be deleted because it's just misleading and I don't see how it can help somebody. – Ivan Stoev Jan 19 '17 at 12:26