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.
Related
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.
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
I'm using Entity Framework 6, Code First approach. I'll try to present my problem with a simple piece of code:
public void ViewEntity(MyEntity Entity) // Want to read properties of my entity
{
using (var Db = new MyDbContext())
{
var DummyList = Db.MyEntities.ToList(); // Iteration on this DbSet
Db.MyEntities.Attach(Entity); // Exception
}
}
The exception message is: Attaching an entity of type 'MyProgram.MyEntity' failed because another entity of the same type already has the same primary key value.
From what I've read on MSDN it's an expected behaviour. But what I want on that last line is to first check if there is an entity with the same key already attached to a context; if it is, use it instead, and only otherwise attach my entity to context.
But I've failed to find a way to do so. There are many utility methods on ObjectContext instance (for example GetObjectByKey). I can't test them all 'cause they all ultimately need a qualifiedEntitySetName, and I don't have any in my real imlpementation, because this method should be on an abstract class and it should work for all entity types. Calling Db.Entity(this) is no use, there is no EntityKey which would have EntitySetName.
So all of this became complex really fast. And in my terms I just want to check if the object is already in "cache" (context), use it, otherwise use my object and attach it to this context.
To be clear, I have a detached object from a TreeNode.Tag in the first place, and I just want to use it again, or if it's impossible; if there already is one in the context), use that one instead. Maybe I'm missing some crucial concepts of EF6, I'm just starting out with EF.
I've found a solution for me. As I guessed correctly ObjectContext.GetObjectByKey method does what I need, but first I needed to construct qualifiedEntitySetName, and I found a way to do so. A tad bit cumbersome (using reflection, iterating properties of MyDbContext), but does not compare to a headache of a problem I made out of all this. Just in case, here's the patch of code that is a solution for me:
public SdsAbstractObject GetAttachedToContext()
{
var ObjContext = (SdsDbContext.Current as IObjectContextAdapter).ObjectContext;
var ExistingItem = ObjContext.GetObjectByKey(GetEntityKey()) as SdsAbstractObject;
if (ExistingItem != null)
return ExistingItem;
else
{
DbSet.Attach(this);
return this;
}
}
public EntityKey GetEntityKey()
{
string DbSetName = "";
foreach (var Prop in typeof(SdsDbContext).GetProperties())
{
if (Prop.PropertyType.IsGenericType
&& Prop.PropertyType.GenericTypeArguments[0] == ObjectContext.GetObjectType(GetType()))
DbSetName = Prop.Name;
}
if (String.IsNullOrWhiteSpace(DbSetName))
return null;
else
return new EntityKey("SdsDbContext." + DbSetName, "Id", Id);
}
An Entity can be in one of five stages : Added, Unchanged, Modified, Deleted, Detached.
public void ViewEntity(MyEntity entity) // Want to read properties of my entity
{
using (var Db = new MyDbContext())
{
var DummyList = Db.MyEntities.ToList(); // Iteration on this DbSet
// Set the Modified state of entity or you can write defensive code
// to check it before set the state.
if (Db.Entry(entity).State == EntityState.Modified) {
Db.Entry(entity).State = EntityState.Modified
}
// Attached it
Db.MyEntities.Attach(Entity);
Db.SaveChanges();
}
}
Since EF doesn't know which properties are different from those in the database, it will update them all.
in my code I'm loading an entity using its id , then update its content using AutoMapper and finally call Context.SaveChanges . but it's not working ! . but when I set properties manually it takes effect ! what is wrong ?
var entity = Context.MyEntities.Find(id);
entity = Mapper.Map<MyEntity>(viewModel);
Context.SaveChanges;
but this one works :
var entity = Context.MyEntities.Find(id);
entity.SomeProp = viewModel.SomeProp;
Context.SaveChanges;
then update its content using AutoMapper
This is not true - Mapper.Map<MyEntity>(viewModel) returns new instance of MyEntity class. It does not update properties of existing instance. You should attach that new instance to context:
var entity = Context.MyEntities.Find(id); // this line is useless
entity = Mapper.Map<MyEntity>(viewModel);
Context.MyEntities.Attach(entity);
Context.SaveChanges;
Also retrieving entity from context does not makes sense when you are creating new one. You are reusing same variable for holding references to different objects, and that is confusing. What really happens could be described this way:
var entityFromDb = Context.MyEntities.Find(id);
var competelyNewEntity = Mapper.Map<MyEntity>(viewModel);
Context.MyEntities.Attach(competelyNewEntity);
Context.SaveChanges;
In your second option you are updating properties of entity, which exists in context, you don't need to attach it.
BTW there is third option (and best) - use another mapping method, which updates destination entity instead:
var entity = Context.MyEntities.Find(id);
Mapper.Map(viewModel, entity); // use this method for mapping
Context.SaveChanges;
I found this snippet here:
public static T DeepClone<T>(this T obj)
{
using (var ms = new MemoryStream()) {
var bf = new BinaryFormatter();
bf.Serialize(ms, obj);
ms.Position = 0;
return (T)bf.Deserialize(ms);
}
}
Which says that we can do deep copy of all related objects through this thing.
I'm trying to do a copy like this:
db.Detach(myEntity);
myEntity.EntityKEy = null;
Entity newEntity = new Entity();
newEntity = DeepClone<Entity>(Entity);
db.Entities.AddObject(newEntity);
db.SaveChanges();
IT works, but still does not copy any nested\related records. what do I do wrong here?
I have this structure Entity->ChildEntity ->ChildChildEntity
-> - one-to-many
so I assume when I copy entity it will also copy all child records.
UPDATE:
After suggestions, I did this:
Entity newEntity = new Entity();
Eneity Entity = db.Include("ChildEntity").Where(p=>p.Id==Id).Single();
newEntity = DeepClone<Entity>(Entity);
db.Detach(myEntity);
myEntity.EntityKEy = null;
db.Entities.AddObject(newEntity);
db.SaveChanges();
Getting exception on AddObject line:
An object with the same key already exists in the ObjectStateManager.
The ObjectStateManager cannot track multiple objects with the same
key.
The important point is that you must load related entities and create deep clone prior to detaching. If you detach the entity all relations are silently removed because Detach method works only for single entity and entity graph cannot consists of both attached and detached entities. That is a reason why you need serialization instead of simply calling Detach.
Don't forget to turn off lazy loading otherwise your serialization will pull data for other navigation properties from database as well. Also remember that this deep copy will create new version of all entities in the graph so adding the root entity will add all related entities as well.
The EntityKeys for all the child objects get cloned too, so you need to set each child's EntityKey to null before trying to add them with AddObject.
Entity oldEntity = db.Include("ChildEntity").Where(p => p.Id == Id).Single();
Entity newEntity = oldEntity.DeepClone(); // assuming you've put your DeepClone extension method in a static class so that it can be used as an extension
newEntity.EntityKey = null;
foreach(var childEntity in newEntity.ChildEntities)
{
childEntity.EntityKey = null;
}
db.Entities.AddObject(newEntity);
db.SaveChanges();
If you haven't loaded the child entities before detaching the entity, they will not be serialized. Make sure all those navigational properties you want to deep clone are loaded before you detach the entity.
Edit
Eager load the navigational properties that must be serialized
var entity = db.Entities.Include("ChildEntity.ChildChildEntity")
.Where(l=>l.ID == myId).Single();
You maybe should save context before you try to attach the entity one more time
Entity newEntity = new Entity();
Eneity Entity = db.Include("ChildEntity").Where(p=>p.Id==Id).Single();
newEntity = DeepClone<Entity>(Entity);
db.Detach(myEntity);
db.SaveChanges();
myEntity.EntityKEy = null;
db.Entities.AddObject(newEntity);
db.SaveChanges();