Dbset.Attach(Entity) VS Dbcontext.Entry(Entity).State = Entitystate.Modified

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:

  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

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



Leave a reply



Submit