I have question about the change tracker in Entity Framework. I read the change tracker document on MSDN - it said that if you insert a model into a database, its status in context will be Added, and when you execute SaveChanges(), it inserts a new row into the database and its status becomes unchanged.
So why should we attach a model before updating it in Entity Framework?
public virtual void Update(TEntity entity)
{
_dbset.Attach(entity); => ???
_context.Entry(entity).State = EntityState.Modified;
_context.SaveChanges();
}
Update :
While an entity is altered within the scope of a DbContext, and was not loaded /w AsNoTracking, the change tracker for that DbContext will pick up changes and apply updates when you call SaveChanges. As in the example you've given, in many cases you might be dealing with an Entity that currently isn't tracked by a DbContext anymore.
As a simple example: (Tracked entities)
public void UpdateDescription(int orderId, string description)
{
using (var context = new AppDbContext())
{
var order = context.Orders.Single(x => x.OrderId == orderId);
order.Description = description;
context.SaveChanges();
}
}
In this example, the Order entity is loaded, tracked, and updated within the scope of a single DbContext. Instead, if we have a method structure like this:
public Order GetOrderById(int orderId)
{
using (var context = new AppDbContext())
{
return context.Orders.Single(x => x.OrderId == orderId);
}
}
public void UpdateOrder(Order order)
{
using (var context = new AppDbContext())
{
context.Attach(order);
context.Entity(order).State = EntityState.Modified;
context.SaveChanges();
}
}
Such as in the case of web applications where an Edit page retrieves an Order entity and passes that to a view. When the user updates some fields on the page Model and submits, a separate Update call is made passing the model. With the "GetOrderById" call, the Order entity was loaded by one instance of the DbContext. When the "UpdateOrder" call occurs, that Order is effectively a deserialized POCO, not a tracked entity anymore. This would be why you need to attach it to the DbContext instance that Update will be using, and set it's entity state to Modified in order for EF to treat it as an entity that needs to be updated.
It is worth noting that in the first tracked example, the UPDATE SQL statement will effectively be something like:
UPDATE Orders SET Description = 'newDescription' WHERE OrderId = 1
where in the second example, the UPDATE statement would be more like:
UPDATE Orders SET OrderNumber = 21, CustomerId = 12, Description = 'newDescription' /* + all fields/FKs in Order table... */ WHERE OrderId = 1
By attaching and setting an entity state to Modified, EF will update all non-PK properties on the entity, even if you only intend/expect for one field to have been modified. In the case of a web application, passing entities back to a controller to be updated this way can leave your system vulnerable to data tampering. (Changing fields/FKs that your UI did not allow) It also means you need to pass a complete entity all of the time to avoid data possibly getting wiped unexpectedly which increases the message payload size going back and forth between client and server.
Because EntityFramework only changed entities that exist in ChangeTracker.
And when you Attaching an entity to DbContext, entity framework know that one entity has tracked and maybe contains changes and apply changes in DataBase after SaveChanges.
Related
I have these two tables:
public class FiscalYear
{
... other fields
public int FiscalYears_Id { get; set; }
}
public class SkipHeader
{
... other fields
public int FiscalYears_Id { get; set; }
public virtual FiscalYear FiscalYear { get; set; }
}
Attempting to create a new SkipHeader like so:
var skipHeader = new SkipHeader()
{
... other fields get assigned to
FiscalYear = Session.FiscalYear,
}
Will cause the database to create a new FiscalYear record instead of using the Session.FiscalYear which is simply a static property that gets assigned to at program start. However, if I assign the FiscalYears_Id instead:
var skipHeader = new SkipHeader()
{
... other fields get assigned to
FiscalYears_Id = Session.FiscalYear.FiscalYears_Id,
}
The program uses the existing record as expected.
This bug eluded me and my colleague for months! Now that I found a solution, I would like to know WHY this is the case?
This bug eluded me and my colleague for months! Now that I found a
solution, I would like to know WHY this is the case?
This occurs because the DbContext doesn't know about your FiscalYear object instance, such as whether it represents a new record or an existing one.
Take the following example:
var fiscalYear = new FiscalYear { Id = 4, Name = "2019/20" };
var skipHeader = new SkipHeader { FiscalYear = fiscalYear };
context.SkipHeaders.Add(skipHeader);
context.SaveChanges();
fiscalYear in this instance is an object instance that has been given an ID and Name. When we associate it to a new SkipHeader and add the SkipHeader to the DbContext, EF will see this fiscalYear. Since it isn't an object tracked by the context, it treats it as a new entity like the SkipHeader.
Depending on how your entities are configured for dealing with the PK will determine what happens.
If your PK (Id) is set up as an Identity column (DB will populate) then the FiscalYear will be inserted and assigned the next available Id value. After the SaveChanges() call, fiscalYear.Id would be "6" or "22" or whatever the next new ID assigned to it would be. (Not "4")
If your PK is not an Identity column (App will populate) and a FiscalYear row already exists in the DB for ID 4, then EF will throw a duplicate key Exception on SaveChanges().
Where people get confused is that they assume that since the FiscalYear was at one point (Say during a web request) loaded from a DbContext, it is still somehow acting as a tracked entity when passed into another method outside of the scope of that DbContext. (During another update web request) It's not. When a web request for instance accepts a FinancialYear as a parameter from the client, it is deserializing a FinancialYear. As far as EF is concerned, that is absolutely no different than the new FinancialYear { } example above. The DbContext is not aware of that entity.
Take the following example:
FiscalYear fiscalYear = null;
using (var context = new AppDbContext())
{
fiscalYear = context.FiscalYears.Single(x => x.Id == 4);
}
using (var context = new AppDbContext())
{
var skipHeader = new SkipHeader { FiscalYear = fiscalYear };
context.SkipHeaders.Add(skipHeader);
context.SaveChanges();
}
This provides a basic outline of a Fiscal Year that was loaded by one instance of a DbContext, but then referenced by another instance of a DbContext. When SaveChanges is called, you will get a behaviour like you are getting now. This is what essentially happens in web requests, as when an entity is returned, the entity definition is merely a contract and the Entity is serialized to send to the client. When it comes back into another request, a new untracked object is deserialized.
As a general rule, Entities should not be passed outside the scope of the DbContext they were read from. EF does support this via detaching and re-attaching entities, but this is honestly more trouble than it is typically worth because you cannot 100% rely on just attaching an entity using DbContext.Attach() as often there can be conditional cases where another entity instance is already being tracked by a context and the Attach will fail. In these cases you'd need to replace references with the already tracked entity. (Messy conditional logic to catch possible scenarios) References are everything when dealing with EF. Two different object references with the same key & values are treated as separate and different objects by EF. Rather than passing references around, it's usually a lot simpler, and better to pass just the FK. This has the benefit of being a smaller payload for web requests.
One option you've found out is to update via the FK:
var skipHeader = new SkipHeader()
{
... other fields get assigned to
FiscalYears_Id = Session.FiscalYear.FiscalYears_Id,
}
This works, however when you have entities that are exposing both FK (FiscalYears_Id) and navigation property (FiscalYear) you can potentially find mismatch scenarios when updating records. This is something to be careful with as an application evolves.
For instance, take an example where you are editing an existing SkipHeader with a FiscalYears_Id of "4". This will have an associated FiscalYear reference available with a PK of "4".
Take the following code:
var skipHeader = context.SkipHeaders.Include(x => x.FiscalYear).Single(x => x.Id == skipHeaderId);
skipHeader.FiscalYears_Id = newFiscalYearId; // update FK from "4" to "6"
var fiscalYearId = skipHeader.FiscalYear.Id; // still returns "6"
context.SaveChanges();
We set the FK value on the skip header, however that does not update the reference for FiscalYear until after we call SaveChanges. This can be an important detail when dealing with FKs alongside navigation properties. Now normally we wouldn't bother going to the Navigation Property to get the ID again, but any code we call that is expecting the new FiscalYear reference to be updated will have a different behavior depending on whether SaveChanges had been called before or after the code in question. If before, all FiscalYear details will be for the old fiscal year even though we changed the FK reference.
This can also lead to odd Lazy Loading errors as well such as:
var skipHeader = context.SkipHeaders.Single(x => x.Id == skipHeaderId);
skipHeader.FiscalYears_Id = newFiscalYearId; // update FK from "4" to "6"
var fiscalYearId = skipHeader.FiscalYear.Id; // NullReferenceException!
context.SaveChanges();
Normally, provided you have lazy loading enabled loading a SkipHeader without eager loading the FiscalYear (.Include(x => x.FiscalYear))and then querying a property from the FiscalYear would lazy load this relative. However, if you change the SkipHeader's FiscalYear_ID FK and then try to access a property off the FiscalYear before calling SaveChanges(), you will get a NullReferenceException on the FiscalYear. EF will NOT lazy load either the old or new FiscalYear entity. Bugs in behaviour like that commonly creep in as applications get developed and code starts calling common functions that assume they are dealing with complete entities.
The alternative to setting updated values for known rows by FK is to load the entity to associate, and associate it by reference:
using (var context = new AppDbContext())
{
var fiscalYear = context.FiscalYears.Single(x => x.Id == fiscalYearId);
var skipHeader = new SkipHeader()
{
... other fields get assigned to
FiscalYear = fiscalYear;
}
context.SaveChanges();
}
This example just uses a locally scoped DbContext. If your method has an injected context then use that instead. The context will return any cached, known instance of the Fiscal Year or retrieve it from the DB. If the FiscalYear ID is invalid then that operation will throw an exception specific to the Fiscal Year not being found due to the Single() call rather than a more vague FK violation on SaveChanges(). (Not an issue when there is only one FK relationship, but in entities that have dozens of relationships...)
The advantage of this approach is that the FiscalYear will be in the scope of the DbContext so any methods/code using it will have a valid reference. The entities can define the navigation properties without exposing the extra FK values,using .Map(x => x.MapKey()) [EF6] or Shadow Properties [EFCore] instead to avoid 2 sources of truth for FK values.
This hopefully will provide some insight into what EF is doing and why it resulted in the behaviour you've seen and/or any errors or buggy behaviour you might have also come across.
Assuming you have pretty standard setup with DbContext being scoped (per request) dependency - the reason is that the new instance of your DbContext does not track the Session.FiscalYear instance - so it creates new. Another way to solve this is using DbContext.Attach:
context.Attach(Session.FiscalYear);
var skipHeader = new SkipHeader()
{
... other fields get assigned to
FiscalYears_Id = Session.FiscalYear.FiscalYears_Id,
}
// save skipHeader
More about change tracker in EF.
I am using EF6 and in this i am trying to update the existing record in the database by using the EntityState.Modified state but its inserting the new row in the table rather tha updating it.I am using following code please let me know where i am going wrong.
public Product UpdateProduct(ProductVM objProduct)
{
Product obj = new Product();
obj = JsonConvert.DeserializeObject<Product>(JsonConvert.SerializeObject(objProduct));
DbContext.Entry(obj).State = EntityState.Modified;
DbContext.SaveChanges();
return obj;
}
This is happening because Entity Framework is "unaware" of this entity of yours. You are passing just a view model and even though you know it has a PK or FK, there's no way EF knows what to do, so it creates a new one. See that you create a new Product()? This line, creates a new Product and since it is new, EF will treat its state as Added.
Instead, you need to first get your object from DB then change the fields. Something like this:
public Product UpdateProduct(ProductVM objProduct)
{
obj = JsonConvert.DeserializeObject<Product>(JsonConvert.SerializeObject(objProduct));
//Get the product from DB to update
var product = DbContext.SingleOrDefult(x => x.Id == objProduct.Id);
//update fields...
product.SomeField = obj.SomeField;
//Save changes
DbContext.Entry(product).State = EntityState.Modified;
DbContext.SaveChanges();
return product;
}
If this Product has foreign keys to other tables you should check this question and answer too:
Difference between updating an entity by using a foreign key and using the navigation properties In Entity Framework
Update: Ahh! Dumb error: I had two instances of a Repository available, and, our implemntation requires that we provide a connection string for each Repo, so the two Repos were pointing to different databases, and I was adding an entity from one DB to another, and its id probably could not be found in the updated DB.
I'm running .Net MVC 4.0, with EF 5. We implement a repository pattern. In the following Delete method of a WebApi controller I have the following code:
int userID = UserHelper.GetCurrentUserID();
DateTime now = DateTime.UtcNow;
ExhibitLinkRepository el = new ExhibitLinkRepository();
el.setCase = caseID;
ExhibitLink link = el.All.SingleOrDefault(l => l.id == id);
//Mark Link Deleted
link.usermodified_id = userID;
link.datetimemodified = now;
link.deleted_flag = true;
exhibitLinkRepository.InsertOrUpdate(link);
exhibitLinkRepository.SaveChanges();
Where exhibitLinkRepository.InsertOrUpdate:
public void InsertOrUpdate(ExhibitLink exhibitLink)
{
if (exhibitLink.id == default(int))
{
// New entity
context.ExhibitLinks.Add(exhibitLink);
}
else
{
// Existing entity
context.Entry(exhibitLink).State = EntityState.Modified;
}
}
When I invoke context.SaveChanges() I get the dreaded:
Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. Refresh ObjectStateManager entries.
Now, the ExhibitLink table has foreign key constraints as follows:
There happen to be triggers on the underlying DB for some of these related tables, but disabling them did not change the outcome.
I just don't get it. Any ideas?
What I want to achieve is to only UPDATE particular property of an entity in the Db.
(To mimic something like UPDATE Foo Set Status = 'kool' WHERE ID = 99).
I try this:
public void SetFooStatus(Foo foo)
{
var fooToUpdate = new Foo()
{
Id = foo.Id,
Status =foo.Status
};
this.Context.Foos.Attach(fooToUpdate);
this.Context.Entry(fooToUpdate).Property("Status").IsModified = true;
this.Context.ValidateOnSaveEnabled = false;
this.dbSet.Attach(entityToUpdate); // This is IDbSet<Foo>
this.dbSet.Entry(entityToUpdate).State = EntityState.Modified;
this.dbSet.SaveChanges();
}
Problem is when I set State to EntityState.Modified, all the properties are marked as Chagned -IsModified returns true-.
When I comment it out, then Update is not accomplished -no changes occur in the Db.
Question:
How can I enforce EF to UPDATE only the Status property of the foo object without touching other fields in the Db?
EF does not allow updating a single column. Instead of attaching an entity you may want to fetch it from the server which will set all the fields to the values you have on the server and therefore you won't change other properties. You probably want to use DbSet.Find() method which will try to find an entity by its id in the context and if one does not exist will fetch it from the database.
I've a small problem with updating entities if the entity is changed outside the DbContext (is a detached entity). If I attach the modified entity, it's state is not modified.
My code looks like this:
var specificationToSave = GetSpecificationFromTmpStore(userSessionGuid);
using (var context = DataContextFactory.GetDataContext())
{
// this works for update, if I change the values inside the context while debugging
// but it breaks with new entities
context.Specifications.Attach(specificationToSave);
// this works for insert new entities, modified entities will be saved as new entities
context.Specifications.Add((specificationToSave);)
context.SaveChanges();
}
I know NHibernate and it's method SaveOrUpdate. NHibernate decides because of the values if it is updating or inserting the entities.
What is the best practice to do this with EF 4.x and with entities which are modified outside the DbContext?
How can I tell the EF that this entity is in modified state?
If you use the Attach approach on an entity which has already changed, you will also need to tell EF that the entity is modified, after attaching it.
context.Specifications.Attach(entity);
context.Entry(entity).State = EntityState.Modified;
context.SaveChanges();
An alternative is to fetch (with tracking), then update the fields, and save:
var entity = context.Specifications.First(s => s.Id == 1234);
entity.Name = "Foo";
... other changes here
context.SaveChanges();
Another option is to make the changes to the entity after you have reattached it, e.g. as per here
context.Specifications.Attach(entity);
entity.Name = "Foo";
... other changes here
context.SaveChanges();
Edit
You can use generics with DbSet - either class, or method - as follows:
public void Update<TEntity>(TEntity entity)
{
DbContext.Set<TEntity>().Attach(entity);
DbContext.Entry(entity).State = EntityState.Modified;
DbContext.SaveChanges();
}
Edit : For updating of detached Parent / Child Graphs
For updating of simple / shallow parent-child relationships where efficiency and performance is not important, simply deleting all old children and reinserting the new ones is an easy (although ugly) solution.
However, for a more efficient scenario requires us to traverse the graph, detect changes, and then add newly inserted, update existing, ignore unchanged, and delete removed items from the Context.
Slauma shows a great example of this here.
You might want to look at using GraphDiff, which can do all this leg work for you!
For disconnected entities, I found this solution.
For finding changes on an existing entity:
var existing = context.Find<Item>(1);
if (existing != null)
{
context.Entry(existing).CurrentValues.SetValues(changed);
}
Its EntityState will be Modified afterwards but only where there are actual changes.
Full example I did in a unit/integration test:
await using var context1 = new MyContext(new DbContextOptionsBuilder().UseSqlite("Data Source=demo.db").Options);
await context1.Database.EnsureDeletedAsync();
await context1.Database.EnsureCreatedAsync();
await context1.Items.AddAsync(new Item
{
Id = 1,
Name = "Something to start with"
});
await context1.SaveChangesAsync();
await using var context2 = new MyContext(new DbContextOptionsBuilder().UseSqlite("Data Source=demo.db").Options);
var existing = context2.Find<Item>(1);
var entry = context2.Entry(existing);
entry.CurrentValues.SetValues(new Item
{
Id = 1,
Name = "Something to start with"
});
entry.State.Should().Be(EntityState.Unchanged);
entry.CurrentValues.SetValues(new Item
{
Id = 1,
Name = "Updated now."
});
entry.State.Should().Be(EntityState.Modified);
Using Microsoft.EntityFrameworkCore.Sqlite and FluentAssertions.