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();
}
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.
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.
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 ...
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.
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.
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.
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:
DbContext
makes a snapshot of each entity it loadsSaveChanges
is called, it will proceed to call DetectChanges
which will do it's magic to figure it out what's changed or not.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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With