2

I have a class Customer. I am trying to clone a Customer object and modify it, then I want those modifications to be reflected in the context (database as well). I am using following code to do that.

    Customer old = context.Customers.Where(c=>c.CustomerID ==1 ).SingleOrDefault();
    Customer m = CustomExtensions.ShallowCopyEntity<Customer>(old);
    m.Name = "Modified";
    m.MobileNo = "9999999999";

    context.Customers.Attach(m);

But its throwing following exception

Attaching an entity of type 'DataBindingSample.Customer' failed because another entity of the same type already has the same primary key value. This can happen when using the 'Attach' method or setting the state of an entity to 'Unchanged' or 'Modified' if any entities in the graph have conflicting key values. This may be because some entities are new and have not yet received database-generated key values. In this case use the 'Add' method or the 'Added' entity state to track the graph and then set the state of non-new entities to 'Unchanged' or 'Modified' as appropriate.

I tried changing EntityState to Modified but it didn't work.

Can anyone tell me how to achieve this?

My main goals are

  1. I want to clone (I will use deep clone when necessary) an existing entity
  2. Want to modify the cloned entity (as well as referenced entities - I will use deep clone in this case)
  3. Finally I want to save changes to database

EDIT

As pointed out in this comment i am trying to attach object which aready exists in context. So i can detach it first and then atttach again as shown bellow if attach is compulsory.

        Customer old = context.Customers.Where(c=>c.CustomerID ==1 ).SingleOrDefault();
        Customer m = CustomExtensions.ShallowCopyEntity<Customer>(old);
        m.Name = "Modified789789";
        m.MobileNo = "9999999999";
        ((IObjectContextAdapter)context).ObjectContext.Detach(old);
        context.Customers.Attach(m);
        context.Entry(m).State = EntityState.Modified;
        context.SaveChanges();

Otherwise i can follow 2 options mentioned in this answer.

Community
  • 1
  • 1
Rajeev
  • 843
  • 2
  • 11
  • 22
  • did you try to give a new primary-key value to your clone before attempting to save it? – jsanalytics Jul 25 '15 at 15:32
  • @jstreet, No, actually its not a new object, i want to modify an existing object. If i give new primary key its doesn't make sense. – Rajeev Jul 25 '15 at 15:33
  • problem is, when you clone an existing object, it is a new object and needs its own primary key. If you just want to modify an existing object then you should not clone it. You can't have it both ways. – jsanalytics Jul 25 '15 at 15:36
  • I don't get why you're cloning it. Cloning by definition creates something new. It doesn't modify it. – dmeglio Jul 25 '15 at 15:53
  • @dman2306 the reason i am cloning it is, I am displaying Customer in a form where user will do all the modifications, and here all the modifactions are getting reflected in context (databinding). BUt if user choses to cancel all modifications its not possible. So i am cloning it so that all the modifcations will be done on cloned object and finally cloned object will be saved if user choses to save. – Rajeev Jul 25 '15 at 15:57
  • @jstreet see Method 3 in this [thread](http://stackoverflow.com/questions/15336248/entity-framework-5-updating-a-record), we can attach updated entity. – Rajeev Jul 25 '15 at 16:08
  • @Rajeev you context already loaded the entity with this primary key (your `SingleOrDefault()` call) so you can't attache the updated entity because it has the same key. In the mentioned example, entity has not been loaded in context and, thus, can be attached. – Nikolai Samteladze Jul 25 '15 at 16:12
  • 2 options that I can think of: (1) copy updated values back to the original entity, (2) update the original entity and discard changes if user canceled. – Nikolai Samteladze Jul 25 '15 at 16:15
  • @Rajeev: exactly, notice the code in the link you posted, it is saving the "original", no clones. – jsanalytics Jul 25 '15 at 16:18
  • @NikolaiSamteladze you are write, but updating values manually is lot of work, i have to copy all properties of customer object as well as other referencesobjects (Customer.Addresses), isn't there an alternative? – Rajeev Jul 25 '15 at 16:19
  • @Rajeev, if the user decides to cancel and not save the changes, then just do not call SaveChanges() and discard the EF context you're working on. – jsanalytics Jul 25 '15 at 16:22
  • Discard the changes on the original if user canceled. Copying is not a lot of work really. Try Automapper. – Nikolai Samteladze Jul 25 '15 at 16:23
  • @Rajeev Did you get it resolved? What did you end up doing? – Nikolai Samteladze Jul 26 '15 at 02:51
  • @NikolaiSamteladze thanks for suggesting Automapper, i using that now. – Rajeev Jul 26 '15 at 06:36
  • You might have a look at my answer on [ASP.NET MVC - Attaching an entity of type 'MODELNAME' failed because another entity of the same type already has the same primary key value](http://stackoverflow.com/questions/23201907/asp-net-mvc-attaching-an-entity-of-type-modelname-failed-because-another-ent/39557606#39557606). – Murat Yıldız Sep 18 '16 at 12:30

1 Answers1

2

There are 2 options that I can think of:

  1. Copy the updated values back to the original entity loaded into your DbContext and then save changes.
  2. Updated values of the original entity and then discard them if user canceled the update.

Options 1

Just copy the updated values back to the originally loaded entity. Automapper is your friend in tasks like this. This approach can later be extended to allow user to change a model of your entity and not the data layer object itself (e.g. to expose a limited number of fields that user can edit).

var entity = context.Customers.SingleOrDefault(c => c.CustomerID == 1);
var updatedEntity = CustomExtensions.ShallowCopyEntity<Customer>(old);

updatedEntity.Name = "Modified";
updatedEntity.MobileNo = "9999999999";

entity.Name = updatedEntity.Name;
entity.MobileNo = updatedEntity.MobileNo;

context.SaveChanges();

If you add Automapper nuget, then you mappings (copying) will become much easier:

Mapper.CreateMap<Customer, Customer>();
Mapper.Map(updatedEntity, entity);

And your code will look like:

// Configuring mapping. Needs to be done only once.
Mapper.CreateMap<Customer, Customer>();

var entity = context.Customers.SingleOrDefault(c => c.CustomerID == 1);

// Check if entity is null

var updatedEntity = CustomExtensions.ShallowCopyEntity<Customer>(old);

updatedEntity.Name = "Modified";
updatedEntity.MobileNo = "9999999999";

// Copy the updated values back
Mapper.Map(updatedEntity, entity);

context.SaveChanges();

Options 2

Make changes in the originally loaded entity and discard them if user changed her mind and canceled. See this post and this post on how to do it. Discarding the whole DbContext might not be a good option in case you still need it (duh).

Community
  • 1
  • 1
Nikolai Samteladze
  • 7,699
  • 6
  • 44
  • 70