Deleting parent-child relation with Entity Framework or Entity Framework Core

c# entity-framework entity-framework-core

Question

I'm learning how to use Entity Framework, having a NHibernate background. There are plenty of tutorials on the net, but I didn't find something for my specific case. I don't want to delete the Category record, I only want to delete the relation!

I have following poco's:

    public class TrainingCourse
    {
        [Key]
        public int CourseId { get; set; }
        public string CourseName { get; set; }
        public virtual ICollection<TrainingContent> Content { get; set; } = new List<TrainingContent>();
    }

    public class TrainingContent
    {
        [Key]
        public int ContentId { get; set; }
        public string ContentName { get; set; }
        public int? CategoryId { get; set; }
        [ForeignKey("CategoryId")]
        public Category Category { get; set; }
    }

    public class Category
    {
        public int CategoryId { get; set; }
        public string CategoryName { get; set; }
    }

In the database I have following 'data' inserted

            var category = new Category { CategoryName = "Category 1" };
            var course = new TrainingCourse { CourseName = "Course 1" };
            context.TrainingCourses.Add(course);
            var content = new TrainingContent { ContentName = "Content 1", Category = category };
            context.TrainingContents.Add(content);
            course.Content.Add(content);

I now want to delete the relation from TrainingContent to Category: in database terms speaking, I want to set my foreignkey CategoryId in the table TrainingContent to null.

When doing this in one context, it is working, my foreign key is NULL after the save:

        using (var context = new ClientContext())
        {
            _course = context.TrainingCourses.Include(c => c.Content.Select(cat => cat.Category)).FirstOrDefault(n => n.CourseName == "Course 1");

            _course.Content.ToList()[0].ContentName = "Content 1 changed";
            _course.Content.ToList()[0].Category = null;

            context.SaveChanges();
        }

But in the real world, we are working with disconnected entities. I simulate this with following code

        using (var context = new ClientContext())
        {
            _course = context.TrainingCourses.Include(c => c.Content.Select(cat => cat.Category)).FirstOrDefault(n => n.CourseName == "Course 1");
        }

        _course.Content.ToList()[0].ContentName = "Content 1 changed";
        _course.Content.ToList()[0].Category = null;

        using (var context = new ClientContext())
        {
            context.Entry(_course.Content.ToList()[0]).State = EntityState.Modified;
            context.SaveChanges();
        }

This is not working, the content name is changed, but my foreign key is still defined.

If I set the navigation property to NULL and the FK to NULL, it is working:

        using (var context = new ClientContext())
        {
            _course = context.TrainingCourses.Include(c => c.Content.Select(cat => cat.Category)).FirstOrDefault(n => n.CourseName == "Course 1");
        }

        _course.Content.ToList()[0].ContentName = "Course 1 changed";
        _course.Content.ToList()[0].Category = null;
        _course.Content.ToList()[0].CategoryId = null;

        using (var context = new ClientContext())
        {
            context.Entry(_course.Content.ToList()[0]).State = EntityState.Modified;
            context.SaveChanges();
        }

Why this behaviour? I'm new to EF and EF Core, so maybe there is a simple explication? I tested this originally with with Entity Framework Core, but same behaviour.

1
0
9/30/2016 11:18:56 AM

Accepted Answer

Your problem is that with a navigation property of null, EF will not check referential integrity (this will happen on SQL side).

However, this is necessary most of the time. Consider you want to attach a content to another category, you might want to do:

var content=context.Contents.FirstOrDefault();
content.CategoryId=2;
context.Entry(content).State=EntityState.Modified;
context.SaveChanges();

this looks fine, doesn't it? However, notice you never actually loaded (lazily or explicitly) the corresponding Category object - it is still null.

So, in this example and in yours the code itself looks fine, but the FK and the navigation property do not match. However, since the navigation property does not have to be correct (unless tracked by the context, since then it is loaded and EF knows its correct), EF will expect the FK to be correct and therefore save this value into the database.

Notice that this issue will not happen in a few cases: The first obviously is when EF was able to track the category in its ChangeTracker - then it will know you actually want to consider the category navigation property, not necessarily the FK. The second is when your FK is not part of your object. Since then EF has to create a FK, however you cannot set it yourself. Then EF has to set the FK corresponding to the navigation property. (notice that this will not work with null. null means either the value is not loaded or the value is nothing (along with some other meanings) and EF cannot determine which one is correct).

Also, keep in mind that in disconnected scenario EF will check the referential integrity of the whole object tree upon Attaching the root of that tree - if any FK's do not match the navigation property, EF will throw an Exception (with the exception of null because of reasons above). And: when you work in disconnected scenario, be aware that when any parent entries are in Added state, all child objects have to be attached (atm I do not know if they also have to be Modified/New), since otherwise EF cannot fix the child object's FK's and throw an exception.

0
10/4/2016 8:41:26 AM


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow