Tuesday, August 6, 2019

Working with Disconnected Entity Graph in Entity Framework Core

In the previous chapter, you learned how the ChangeTracker automatically changes the EntityStateof each entity in the connected scenario. Here, you will learn about the behaviours of different methods on the root entity and child entities of the disconnected entity graph in Entity Framework Core.
Entity Framework Core provides the following different methods, which not only attach an entity to a context, but also change the EntityState of each entity in a disconnected entity graph:
  • Attach()
  • Entry()
  • Add()
  • Update()
  • Remove()
Let's see how the above methods change the EntityState of each entity in an entity graph in Entity Framework Core 2.x.

Attach()

The DbContext.Attach() and DbSet.Attach() methods attach the specified disconnected entity graph and start tracking it. They return an instance of EntityEntry, which is used to assign the appropriate EntityState.
The following example demonstrates the behaviour of the DbContext.Attach() method on the EntityState of each entity in a graph.
public static void Main()
{
    var stud = new Student() { //Root entity (empty key)
        Name = "Bill",
        Address = new StudentAddress()  //Child entity (with key value)
        {
            StudentAddressId = 1,
            City = "Seattle",
            Country = "USA"
        },
        StudentCourses = new List<StudentCourse>() {
            new StudentCourse(){  Course = new Course(){ CourseName = "Machine Language" } },//Child entity (empty key)
            new StudentCourse(){  Course = new Course(){  CourseId = 2 } } //Child entity (with key value)
        }
    };

    var context = new SchoolContext();
    context.Attach(stud).State = EntityState.Added;  

    DisplayStates(context.ChangeTracker.Entries());
}

private static void DisplayStates(IEnumerable<EntityEntry> entries)
{
    foreach (var entry in entries)
    {
        Console.WriteLine($"Entity: {entry.Entity.GetType().Name},
                             State: {entry.State.ToString()} ");
    }
}
Output:
Entity: Student, State: Added 
Entity: StudentAddress, State: Unchanged 
Entity: StudentCourse, State: Added 
Entity: Course, State: Added 
Entity: StudentCourse, State: Added 
Entity: Course, State: Unchanged

In the above example, stud is an instance of the Student entity graph which includes references of StudentAddress and StudentCourse entities. context.Attach(stud).State = EntityState.Addedattaches the stud entity graph to a context and sets Added state to it.
The Attach() method sets Added EntityState to the root entity (in this case Student) irrespective of whether it contains the Key value or not. If a child entity contains the key value, then it will be marked as Unchanged, otherwise it will be marked as Added. The output of the above example shows that the Student entity has Added EntityState, the child entities with non-empty key values have Unchanged EntityState and the ones with empty key values have Added state.
The following table lists the behaviour of the Attach() method when setting a different EntityStateto a disconnected entity graph.
Attach()Root entity with Key valueRoot Entity with Empty or CLR default valueChild Entity with Key valueChild Entity with empty or CLR default value
context.Attach(entityGraph).State = EntityState.AddedAddedAddedUnchangedAdded
context.Attach(entityGraph).State = EntityState.ModifiedModifiedExceptionUnchangedAdded
context.Attach(entityGraph).State = EntityState.DeletedDeletedExceptionUnchangedAdded

Entry()

The DbContext.Entry() method behaves differently in Entity Framework Core compared with the previous EF 6.x. Consider the following example:
var student = new Student() { //Root entity (empty key)
    Name = "Bill",
    Address = new StudentAddress()  //Child entity (with key value)
    {
        StudentAddressId = 1,
        City = "Seattle",
        Country = "USA"
    },
    StudentCourses = new List<StudentCourse>() {
            new StudentCourse(){  Course = new Course(){ CourseName="Machine Language" } },//Child entity (empty key)
            new StudentCourse(){  Course = new Course(){  CourseId=2 } } //Child entity (with key value)
        }
};

var context = new SchoolContext();
context.Entry(student).State = EntityState.Modified;

DisplayStates(context.ChangeTracker.Entries());
Output:
Entity: Student, State: Modified
In the above example, context.Entry(student).State = EntityState.Modified attaches an entity to a context and applies the specified EntityState (in this case, Modified) to the root entity, irrespective of whether it contains a Key property value or not. It ignores all the child entities in a graph and does not attach or set their EntityState.
The following table lists different behaviours of the DbContext.Entry() method.
Set EntityState using Entry()Root entity with Key valueRoot Entity with Empty or CLR default valueChild Entities with/out Key value
context.Entry(entityGraph).State = EntityState.AddedAddedAddedIgnored
context.Entry(entityGraph).State = EntityState.ModifiedModifiedModifiedIgnored
context.Entry(entityGraph).State = EntityState.DeletedDeletedDeletedIgnored

Add()

The DbContext.Add and DbSet.Add methods attach an entity graph to a context and set Added EntityState to a root and child entities, irrespective of whether they have key values or not.
var student = new Student() { //Root entity (with key value)
    StudentId = 1,
    Name = "Bill",
    Address = new StudentAddress()  //Child entity (with key value)
    {
        StudentAddressId = 1,
        City = "Seattle",
        Country = "USA"
    },
    StudentCourses = new List<StudentCourse>() {
            new StudentCourse(){  Course = new Course(){ CourseName="Machine Language" } },//Child entity (empty key)
            new StudentCourse(){  Course = new Course(){  CourseId=2 } } //Child entity (with key value)
        }
};

var context = new SchoolContext();
context.Students.Add(student);

DisplayStates(context.ChangeTracker.Entries());
Output:
Entity: Student, State: Added 
Entity: StudentAddress, State: Added
Entity: StudentCourse, State: Added 
Entity: Course, State: Added 
Entity: StudentCourse, State: Added 
Entity: Course, State: Added

The following table lists possible EntityState of each entity in a graph using the DbContext.Add or DbSet.Add methods.
MethodRoot entity with/out Key valueChild Entities with/out Key value
DbContext.Add(entityGraph) or DbSet.Add(entityGraph)AddedAdded

Update()

The DbContext.Update() and DbSet.Update() methods attach an entity graph to a context and set the EntityState of each entity in a graph depending on whether it contains a key property value or not. Consider the following example.
var student = new Student() { //Root entity (with key value)
    StudentId = 1,
    Name = "Bill",
    Address = new StudentAddress()  //Child entity (with key value)
    {
        StudentAddressId = 1,
        City = "Seattle",
        Country = "USA"
    },
    StudentCourses = new List<StudentCourse>() {
            new StudentCourse(){  Course = new Course(){ CourseName="Machine Language" } },//Child entity (empty key)
            new StudentCourse(){  Course = new Course(){  CourseId=2 } } //Child entity (with key value)
        }
};

var context = new SchoolContext();
context.Update(student);

DisplayStates(context.ChangeTracker.Entries());
Output:
Entity: Student, State: Modified 
Entity: StudentAddress, State: Modified 
Entity: StudentCourse, State: Added 
Entity: Course, State: Added 
Entity: StudentCourse, State: Added 
Entity: Course, State: Modified

In the above example, the Update() method applies the Modified state to the entities which contain non-empty key property values and the Added state to those which contain empty or default CLR key values, irrespective of whether they are a root entity or a child entity.
Update()Root entity with Key valueRoot Entity with Empty or CLR default valueChild Entities with Key valueChild Entities with Empty Key value
DbContext.Update(entityGraph) or DbSet.Update(entityGraph)ModifiedAddedModifiedAdded

Remove()

The DbContext.Remove() and DbSet.Remove() methods set the Deleted EntityState to the root entity.
var student = new Student() { //Root entity (with key value)
    StudentId = 1,
    Name = "Bill",
    Address = new StudentAddress()  //Child entity (with key value)
    {
        StudentAddressId = 1,
        City = "Seattle",
        Country = "USA"
    },
    StudentCourses = new List<StudentCourse>() {
            new StudentCourse(){  Course = new Course(){ CourseName="Machine Language" } },//Child entity (empty key)
            new StudentCourse(){  Course = new Course(){  CourseId=2 } } //Child entity (with key value)
        }
};

var context = new SchoolContext();
context.Remove(student);

DisplayStates(context.ChangeTracker.Entries());
Output:
Entity: Student, State: Deleted 
Entity: StudentAddress, State: Unchanged
Entity: StudentCourse, State: Added 
Entity: Course, State: Added 
Entity: StudentCourse, State: Added 
Entity: Course, State: Unchanged

The following table lists the behaviour of the Remove() method on the EntityState of each entity.
Remove()Root entity with Key valueRoot Entity with Empty or CLR default valueChild Entities with Key valueChild Entities with Empty Key value
DbContext.Remove(entityGraph) or DbSet.Remove(entityGraph)DeletedExceptionUnchangedAdded
Thus, be careful while using the above methods in EF Core.

No comments:

Post a Comment

How to register multiple implementations of the same interface in Asp.Net Core?

 Problem: I have services that are derived from the same interface. public interface IService { } public class ServiceA : IService { ...