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();
Related
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'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.
I'm using the Northwind database as an example for this post where I am having a problem saving detached entities back to the database using the entity framework.
I have the following two methods to get me the territories and the regions:
static List<Region> GetRegions()
{
using (NorthwindEntities entities = new NorthwindEntities())
{
entities.Region.MergeOption = System.Data.Objects.MergeOption.NoTracking;
return entities.Region.ToList();
}
}
static List<Territories> GetTerritories()
{
using (NorthwindEntities entities = new NorthwindEntities())
{
entities.Territories.MergeOption = System.Data.Objects.MergeOption.NoTracking;
return entities.Territories.ToList();
}
}
These methods both work fine and get me the collection of objects I require in the detached state.
I also have a static method called SaveEntity, which takes in both an old entity and the currently editted entity, this is as follows:
static void SaveEntity(EntityObject oldEntity, EntityObject newEntity)
{
using (NorthwindEntities entities = new NorthwindEntities())
{
entities.Attach(oldEntity);
entities.ApplyPropertyChanges(newEntity.EntityKey.EntitySetName, newEntity);
entities.SaveChanges();
}
}
This method partially works where the changes to the object are saved down to the database, but any changes to the relationship of related objects are not saved.
I have the following code calling the above methods as my example:
List<Territories> territories = GetTerritories();
List<Region> regions = GetRegions();
Region region = regions.Where(n => n.RegionID == 2).FirstOrDefault();
Territories oldTerritory = territories.Where(n => n.TerritoryID == "01581").FirstOrDefault();
Territories newTerritory = ObjectCopier.Clone<Territories>(oldTerritory);
newTerritory.TerritoryDescription = "Hello World";
newTerritory.Region = region;
SaveEntity(oldTerritory, newTerritory);
The change to TerritoryDescription is successfully saved, but the change to Region is not, in the database it still remains as RegionID=1 instead of RegionID=2.
Can anyone provide me with some insight to why ApplyPropertyChanges doesn't propogate changes to related objects?
Also, does anyone know of how I can get around this problem?
Instead of fetching regions and territories seperately, fetch both of them in the same query. Something like (I assume you want to update the entity, do not want to create a new one);
static List<Region> GetTerritoriesWithRegions()
{
using (NorthwindEntities entities = new NorthwindEntities())
{
entities.Territories.MergeOption = System.Data.Objects.MergeOption.NoTracking;
return entities.Territories.Include("Region").ToList();
}
}
Then update them as the following;
List<Territories> territoriesWithRegions = GetTerritoriesWithRegions();
Territories territory = territories.Where(n => n.TerritoryID == "01581").FirstOrDefault();
territory.TerritoryDescription = "Hello World";
Region region = territories.Where(p => p.Any(q => q.Region.RegionID == 2)).FirstOrDefault().Region;
territory.Region = region;
SaveEntity(territory);
And save them;
static void SaveEntity(EntityObject entity)
{
using (NorthwindEntities entities = new NorthwindEntities())
{
entities.Attach(entity);
entities.Context.ObjectStateManager.ChangeObjectState(entity, EntityState.Modified);
entities.SaveChanges();
}
}
I have coded this on notepad so there may be mistakes; if any please comment so I will update accordingly.
I think you can find answers here (Alex James is better one).
Entity Framework Updating with Related Entity
Basically, the reason is because in EF relationships are objects too and have statuses like entities (deleted, added,...), so you would need to have the original reference value in the context as well.