Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does EF Core Modified Entity State behave?

Does it matter we put the entity state = modified after changes or before making changes?

using (var db = new LakshyaContext())
{
    foreach (var category in db.Categories)
    {
        db.Entry(category).State = EntityState.Modified; // before
        category.Count = 25; //Making Changes
        db.Entry(category).State = EntityState.Modified; //After
    }

    db.SaveChanges();
}
like image 622
Abhijeet Avatar asked Jan 20 '19 06:01

Abhijeet


People also ask

How does DbContext change state of entity?

This can be achieved in several ways: setting the EntityState for the entity explicitly; using the DbContext. Update method (which is new in EF Core); using the DbContext. Attach method and then "walking the object graph" to set the state of individual properties within the graph explicitly.

How does EF core detect changes?

EF Core change tracking works best when the same DbContext instance is used to both query for entities and update them by calling SaveChanges. This is because EF Core automatically tracks the state of queried entities and then detects any changes made to these entities when SaveChanges is called.

What is each entity state in Entity Framework 6?

Each entity has a state based on the operation performed on it via the context class. The entity state represented by an enum System.Data.Entity.EntityState in EF 6 and Microsoft.EntityFrameworkCore.EntityState in EF Core with the following values: The Context not only holds the reference to all the entity objects as soon as retrieved from ...

How has Entity Framework Core changed the behavior of change tracking?

They also help me describe the behaviors of change tracking in EF Core compared to earlier versions of Entity Framework. Change tracking has become more consistent in EF Core so you can be more confident in knowing what to expect when you’re working with disconnected data.

What is the difference between Entity Framework and EF Core?

Keep in mind that while EF Core attempts to keep the paradigms and much of the syntax of earlier versions of Entity Framework, EF Core is a new set of APIs—a completely new code base written from scratch. Therefore, it’s important not to presume that everything will behave exactly as it did in the past.

What's new in EF Core?

In EF Core, the Add, Attach and Remove methods are back as methods on the DbContext, along with Update and the four related Range methods (AddRange, and so forth). But these methods are much smarter now. They’re now able to determine the type and automatically relate the entity to the correct DbSet.


1 Answers

So first, let's get the most important thing out of the way:

You are right. In your example, you don't need to manually call db.Entry(category).State = EntityState.Modified. This is because you are loading the entries (categories) from the context above. This is known as the "Connected Scenario" where the DbContext is aware of the entities, it's tracking them. This is the same, for instance in an ASP.NET Core app, where the context is shared across the HTTP request.

Any modification you make between the scope of using (var db = new LakshyaContext()), will be known by the context when you call SaveChanges.

Now, when working on disconnected scenarios (as you said UnTracked entities), we have to dig a little bit deeper.

To understand that, first you need to know how the DbContext know what's changed. Take the following example:

using (var context = new MyContext())
{
    // loads the book by it's ISBN
    var book = context.Books
        .Single(p => p.ISBN == "123456");
    
    // Do changes
    book.Price = 30;
    
    // Save changes
    context.SaveChanges();
}

How does it know that the Price changed? since it's just a normal auto property on the Book class? The magic lies behind the DetectChanges method.

In some specific cases, the DbContext calls the DetectChanges method. The most obvious one is when SaveChanges is called. In a top level, the way it works is:

  1. The DbContext makes a snapshot of each entity it loads
  2. When SaveChanges is called, it will proceed to call DetectChanges which will do it's magic to figure it out what's changed or not.
  3. DbContext then takes care of sending the correct commands to the db.

At this point, we know the responsibility of DetectChanges. The important part now is knowing when DetectChanges is called (apart from SaveChanges that we already know). This is crucial to finally answer your "Order" question. From the linked article from Arthur Vickers

The methods that call DetectChanges:

  • DbSet.Find
  • DbSet.Local
  • DbSet.Remove
  • DbSet.Add
  • DbSet.Attach
  • DbContext.SaveChanges
  • DbContext.GetValidationErrors
  • DbContext.Entry
  • DbChangeTracker.Entries

Let's examine this code that demonstrates the "disconnected" scenario.

public Task UpdateBook() 
{
    Book book = null;
    
    // Just loads the book from this context
    using (var context = new MyContext())
    {
        book = context.Books
            .Single(p => p.ISBN == "123456");       
    }
    
    // Starts a new context where the book is going to be updated
    using (var anotherContext = new MyContext())
    {
        // Changed the price - remember this is not loaded from this context!
        book.Price = 40;
    
        // THIS IS KEY: This will call `DetectChanges`      
        // This entity will be tracked by the context now
        anotherContext.Entry(book).State = EntityState.Modified
        
        // Update will occur normally
        anotherContext.SaveChanges();
    }
}

When we go into the second DbContext, it is not aware of our book entity. We change the price and then call db.Entry(book).State = EntityState.Modified. At this point, the DbContext will start tracking it, and DetectChanges is invoked. Proceeding calling SaveChanges will work as expected.

If we had done the opposite, calling db.Entry(book).State = EntityState.Modified before actually changing the price things would.... still work!

Why? Well, manually changing the state of the entity with db.Entry(book).State will add the entity to the context, meaning it will start tracking it for changes. So, even if we call db.Entry(book).State and then apply changes on the entity it will not matter because calling SaveChanges at the end, will trigger again DetectChanges, and since it was already called before, there was already a snapshot in place for the entity.

One way you can verify this behavior yourself is running the code above with logging enabled for the DbContext:

// Calling db.Entry.. produces this log:

DetectChanges starting for 'MyContext'.
Microsoft.EntityFrameworkCore.ChangeTracking:Debug: DetectChanges completed for 'MyContext'.
Context 'MyContext' started tracking 'Book' entity.


// Calling SaveChanges produces this log:

SaveChanges starting for 'MyContext'
DetectChanges starting for 'MyContext'.
DetectChanges completed for 'MyContext'.
Opening connection to database 'BooksDB'
Beginning transaction with isolation
...

Now some remarks:

The update above in the disconnected scenario will issue an update on ALL COLUMNS in the table. This might not be what you expected. There are ways to prevent this. Read more here

DetectChanges does a lot of stuff internally, not only applying merges on changes. It takes care of Foreign Keys, updating references of navigation properties and more, and doing "fixup".

More resources to read on: (especially the ones from Arthur Vickers!)

Secrets of DetectChanges Part 1: What does DetectChanges do?

Secrets of DetectChanges Part 2: When is DetectChanges called automatically?

Possible Issue with Change Tracker Caching Entity State EF Core 2.0.2

Working with Disconnected Entity Graph in Entity Framework Core

Entity Framework Core TrackGraph For Disconnected Data

like image 156
jpgrassi Avatar answered Oct 22 '22 20:10

jpgrassi