4

I'm trying to do a deep clone of some entities. The approach mentioned in this article looks good but I run into errors. It suggests using AsNoTracking() to retrieve the entity and then to reinsert it into the context where it will cause an insert as it looks like a new object.

Here's my code:

        Using ctxt As New ProductionDataEntities
            Dim grade = ctxt.Grades.Include(Function(g) g.GradeWidths).AsNoTracking.First
            ctxt.Grades.AddObject(grade)
            ctxt.SaveChanges()
        End Using

But when I run it I get:

An object with the same key already exists in the ObjectStateManager. The existing object is in the Modified state. An object can only be added to the ObjectStateManager again if it is in the added state.

And when I modify grade.Name it's EntityState changes to modified implying that it is being Tracked.

I'm using EF5 Db-First.

Alternatively I've tried cloning by detaching the grade and then reinserting it, this works but the GradeWidths don't get copied. As soon as I call detach the gradewidth count goes from 2 to 0.

Questions:

  1. Any idea why AsNoTracking isn't working and what I can do to fix this?
  2. Alternatively is there another approach someone can recommend for simple deep cloning?

Thanks.

---- additional info ----

I have 5 1-to-many relationships and eventually I'll want to clone from the top level all the way down. But I'm simplifying it to just look at the lowest level.

  • 1 Grade to many GradeWidths
Community
  • 1
  • 1
FloatingKiwi
  • 4,408
  • 1
  • 17
  • 41
  • You want to attach the entity to the context, not to add it. Adding would only create a new row in the table anyway. – DavidG Sep 05 '16 at 14:30
  • @DavidG - I'm trying to create a new row (ie Duplicate it). Besides I cant call attach as the entity is already attached despite me calling AsNoTracking – FloatingKiwi Sep 05 '16 at 14:35
  • 2
    Ah I should have read it! Then you probably just need to blitz the primary key value. – DavidG Sep 05 '16 at 14:42
  • Still open. Anyone have any ideas? – FloatingKiwi Sep 09 '16 at 12:17
  • Did you try to null the primary key before adding it? Or make it 0 if it's an int. – DavidG Sep 09 '16 at 12:23
  • Yes, I've tried that. It can't be set to null as the Context still thinks it's attached. If I detach it I can change the primary key to nothing then add it again and it clones the record. However it doesn't clone the child records which is my overall aim. – FloatingKiwi Sep 09 '16 at 12:31
  • To be sure that it is AsNoTracking that is not working you can stop your app and see grade type (if it's a proxy). – bubi Sep 15 '16 at 14:03
  • @bubi - It's type is Data.Grade regardless of whether or not I'm using AsNoTracking – FloatingKiwi Sep 15 '16 at 15:15
  • It doesn't make sense that `AsNoTracking` doesn't have any effect. Before anything, it should be clear why this is. You seem to be working with an `ObjectContext` right? – Gert Arnold Sep 17 '16 at 13:15
  • @GertArnold - `ProductionDataEntities` inherits from `ObjectContext`. – FloatingKiwi Sep 17 '16 at 13:30
  • Even then, `AsNoTracking` should work. Do you work with exactly the same code as shown above? – Gert Arnold Sep 17 '16 at 13:32
  • @GertArnold - Yes, that's the code from one of my nunit tests. The error is thrown on `AddObject` complaining about duplicate key but attempting to change the grade id results in another error as the object is apparently still being tracked. – FloatingKiwi Sep 17 '16 at 13:38
  • Does your Primary keys are Identity? If the Primary keys are identity then it should work. – Reza Aghaei Sep 17 '16 at 13:52
  • @RezaAghaei - Yes, they are identity. – FloatingKiwi Sep 17 '16 at 13:54
  • That's really really weird. `AsNoTracking` should work, it actually does work in a little EF5 ObjectContext I spun up quickly. – Gert Arnold Sep 17 '16 at 14:05
  • @GertArnold - I created a brand new EF5 context and a new EF6 context and both still have the problem. I'm trying to figure out where the issue is but i've got no idea where to look. I'm using .net4 vs2010 but I wouldn't have expected that to cause an issue. – FloatingKiwi Sep 17 '16 at 14:35

2 Answers2

2

Using ObjectContext

Using ObjectContext you can first attach the parent object and then change object state to added. Also for each child objects, change the object state to added. You even don't need AsNoTracking().

For example for a Category(1)↔(N)Product relation I used this code:

Using db As New SampleSystemEntities
    Dim c = db.Categories.Include(Function(x) x.Products).First
    db.Attach(c)
    db.ObjectStateManager.ChangeObjectState(c, EntityState.Added)
    For Each p As Product In c.Products
        db.ObjectStateManager.ChangeObjectState(p, EntityState.Added)
    Next
    db.SaveChanges()
End Using

Using DbContext

If you use DbContext every thing will work fine using your code:

Using db As New SampleSystemEntities
    Dim c = db.Categories.Include(Function(x) x.Products).AsNoTracking().First
    db.Categories.Add(c)
    db.SaveChanges()
End Using
Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398
  • Thanks Reza. This is a great alternative which works for me. I'm still interested in why AsNoTracking is failing to work on my pc so I'll leave the question open for a few more days. – FloatingKiwi Sep 17 '16 at 14:26
  • No problem, I also will share more if I had anything else to share :) – Reza Aghaei Sep 17 '16 at 14:28
  • If you use `DbContext` then `AsNoTracking` will work as expected. Do you have any requirement which makes you to use `AsNoTracking`? – Reza Aghaei Sep 17 '16 at 14:32
  • I'm quite new to Entity Framework so I've been reading up on ObjectContext vs DbContext but for the life of me I can't figure out how to get a DbContext. The tool always seems to generate ObjectContext despite what's mentioned in the tutorials. Any idea whats wrong? vs2010? .net4.0? – FloatingKiwi Sep 17 '16 at 14:59
  • I managed to switch it to DbContext and everything works perfectly now with `AsNoTracking`. I followed the procedure in [this link](https://msdn.microsoft.com/en-us/data/jj206878.aspx). Thanks a lot for your help. – FloatingKiwi Sep 17 '16 at 15:23
  • 1
    You're welcome. I also could use EF 5.0 with VS2010. I first installed Entity Framework NuGet package. Then added [EF 5.x DbContext Generator for VB.NET](https://visualstudiogallery.msdn.microsoft.com/875c882d-333e-455a-8dae-5353510527dd?SRC=VSIDE). After creating my edmx, I removed the generated code and using `Add Code Generation Item...` added t4 templates for EF 5 and using AsNoTracking all thing was OK. – Reza Aghaei Sep 17 '16 at 15:28
  • Do you also installed Entity Framework NuGet package? – Reza Aghaei Sep 17 '16 at 15:33
  • Yes, it was the adding `EF 5.x DbContext Generator for VB.NET` step that I hadn't done. – FloatingKiwi Sep 17 '16 at 15:35
  • Great! So forget about using ObjectContext and use DbContext :) – Reza Aghaei Sep 17 '16 at 15:37
1

Without the model is quite hard to understand the exact behaviour (in this case primary Keys are important).
Also, I'm not using EF 5 but EF 6 it should be quite similar.

In your app you are reading an entity with related entities (GradeWidths) from database and you need to add it again to the database.
I suppose that your model is 1 Grade n GradeWidths and you are creating new instances of GradeWidths copied from the original Grade (your model could be n-m and in this case you could use 1 GradeWidth on more Grades but I suppose you are not).

In this case to add new entities you need to reset all the Ids, of the Grade and of the GradeWidths. Then, you have a "clean" clone and you can Add it to the DbSet (Add, not attach).

using (var context = new MyContext(connection))
{
    context.Grades.Add(new Grade()
    {
        GradeWidths = new List<GradeWidth>(new[]
        {
            new GradeWidth() {Width = 10},
            new GradeWidth() {Width = 20},
            new GradeWidth() {Width = 30}
        })
    });
    context.SaveChanges();
}

using (var context = new MyContext(connection))
{
    Grade grade = context.Grades.Include(g => g.GradeWidths).AsNoTracking().First();

    // We need to reset all the ids
    grade.Id = 0;
    foreach (GradeWidth gradeWidth in grade.GradeWidths)
        gradeWidth.Id = 0;


    context.Grades.Add(grade);
    context.SaveChanges();
}

using (var context = new MyContext(connection))
{
    Debug.Assert(context.Grades.Count() == 2);
    Debug.Assert(context.GradeWidths.Count() == 6);
}
bubi
  • 6,414
  • 3
  • 28
  • 45
  • I'm getting an error `The property 'GradeID' is part of the object's key information and cannot be modified.` This will only go away if I detach the object but then I lose the GradeWidths collection. – FloatingKiwi Sep 17 '16 at 12:25
  • In my case it works properly and the object is detached (before send you I run the code). Probably there is a bug in EF 5 in asnotracking. Anyway, if you detach the main object you have to detach also the details of you need to add them. – bubi Sep 17 '16 at 13:44