DbSet.Attach(entity) vs DbContext.Entry(entity).State = EntityState.Modified
When you do context.Entry(entity).State = EntityState.Modified;
, you are not only attaching the entity to the DbContext
, you are also marking the whole entity as dirty. This means that when you do context.SaveChanges()
, EF will generate an update statement that will update all the fields of the entity.
This is not always desired.
On the other hand, DbSet.Attach(entity)
attaches the entity to the context without marking it dirty. It is equivalent to doing context.Entry(entity).State = EntityState.Unchanged;
When attaching this way, unless you then proceed to update a property on the entity, the next time you call context.SaveChanges()
, EF will not generate a database update for this entity.
Even if you are planning on making an update to an entity, if the entity has a lot of properties (db columns) but you only want to update a few, you may find it advantageous to do a DbSet.Attach(entity)
, and then only update the few properties that need updating. Doing it this way will generate a more efficient update statement from EF. EF will only update the properties you modified (in contrast to context.Entry(entity).State = EntityState.Modified;
which will cause all properties/columns to be updated)
Relevant documentation: Add/Attach and Entity States.
Code example
Let's say you have the following entity:
public class Person
{
public int Id { get; set; } // primary key
public string FirstName { get; set; }
public string LastName { get; set; }
}
If your code looks like this:
context.Entry(personEntity).State = EntityState.Modified;
context.SaveChanges();
The SQL generated will look something like this:
UPDATE person
SET FirstName = 'whatever first name is',
LastName = 'whatever last name is'
WHERE Id = 123; -- whatever Id is.
Notice how the above update statement will update all the columns, regardless or whether you've actually changed the values or not.
In contrast, if your code uses the "normal" Attach like this:
context.People.Attach(personEntity); // State = Unchanged
personEntity.FirstName = "John"; // State = Modified, and only the FirstName property is dirty.
context.SaveChanges();
Then the generated update statement is different:
UPDATE person
SET FirstName = 'John'
WHERE Id = 123; -- whatever Id is.
As you can see, the update statement only updates the values that were actually changed after you attached the entity to the context. Depending on the structure of your table, this can have a positive performance impact.
Now, which option is better for you depends entirely on what you are trying to do.
Why does the DbContext.Attach set an entity state to EntityState.Modified?
I'm not seeing the behaviour you're describing. I added this code to your fiddle:
using (var context = new EntityContext())
{
var customer = new Customer() {CustomerID = 1};
context.Customers.Attach(customer);
var entity = context.Entry(customer);
Console.WriteLine(entity.State);
}
And the output is:
Unchanged
Just like the documentation says it would be.
Maybe that comment (// Book Modified, Author Added - no "store generated key"
) is either a mistake, or describing that they intend to add the author to it. In that case, you would have to set the record to Modified
before calling SaveChanges
, or just modify whatever you want to modify after you call Attach
, like this:
using (var context = new EntityContext())
{
var customer = new Customer() {CustomerID = 1};
context.Customers.Attach(customer);
//change something
customer.Description = "something";
var entity = context.Entry(customer);
Console.WriteLine(entity.State);
}
That would now show Modified
.
How does EF Core Modified Entity State behave?
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:
- The
DbContext
makes a snapshot of each entity it loads - When
SaveChanges
is called, it will proceed to callDetectChanges
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
Why use Attach for update Entity Framework 6?
If you have an entity that you know already exists in the database but which is not currently being tracked by the context - which is true in your case - then you can tell the context to track the entity using the Attach
method on DbSet
.
So in summary what Attach
method does is track the entity in the context and change its state to Unchanged
. When you modify a property after that, the tracking changes will change its state to Modified
for you.
In the case you expose above you are telling explicitly that state is Modified
but also to attach the entity to your context. You can find a detailed explanation in this post.
When should you use Attach
method?
When you have an entity that you know already exists in the database but want to make some changes:
var entity= new Entity{id=1};
context.YourDbSet.Attach(entity);
// Do some change...
entity.value=5;
context.SaveChanges();
This is the same:
context.Entry(entity).State = EntityState.Unchanged;
// Do some change...
entity.value=5;
context.SaveChanges();
When should you change entity's State to Modified explicitly?
When you have an entity that you know already exists in the database but the changes have already been made then. The same scenario of your example
Difference between DbSet.Remove and DbContext.Entry(entity).State = EntityState.Deleted
Ended up solving this by calling the following:
dbContext.Set<Agreement>().Remove(agreement);
I wanted to get rid of the Agreements
property on the DbContext
, which is why I was trying to do it with Entry(agreement).State = EntityState.Deleted
.
DbContext.Entry attaching Entity
If you're wanting to attach an object, what you actually want is DbSet.Attach. DbContext.Entry is only giving you information about the entity, and allows you to change the state if it's already been attached.
Here's a good post about entity states from MSDN
Related Topics
What's a Good Way of Doing String Templating in .Net
How to Concatenate Two Ienumerable<T> into a New Ienumerable<T>
When to Use a Sortedlist<Tkey, Tvalue> Over a Sorteddictionary<Tkey, Tvalue>
Converting String to Float in C#
How to Automate Sap Gui with C#
Change the Value in App.Config File Dynamically
How to Tell If My Application Is Running as a 32-Bit or 64-Bit Application
How to Async Download Multiple Files Using Webclient, But One at a Time
What's the Point of a Lambda Expression
What's Wrong with These Parameters
Linq to SQL Where Clause Optional Criteria
How to Store JSON in an Entity Field with Ef Core
How to Make a Combobox Non-Editable in .Net
Photo Capture on Windows Store App for Windows Phone
Retrieving Files from Directory That Contains Large Amount of Files