3

I'm working with EF6 code first in a WinForm project. I used following method for reading entities from Db, updating them and then save back them to Db:

  1. Read Entity graph using Linq to entities(after reading the DbContext disposes)
  2. Show readed Entity graph to end user.
  3. End user may apply this changes to Entity graph:
    • Update root entity
    • Add some child entities
    • Edit some child entities
    • Delete some child Entities
  4. User call a method to persist his changes to Db
  5. Create a new DbContext instance.
  6. Reload same Entity's graph from Db
  7. Map the all property's value from user entity to reloaded entity using AutoMapper
  8. Attach the result entity of 6 step to my DbContext using GraphDiff
  9. Call DbContext.SaveChanges(); to persist changes to Db enter image description here

    var root = new MyDbcontext()
                               .Roots
                               .LoadAggregation()
                               .ToList();
                               //    LoadAggregation in this case, means following codes:   
                               //    .Include("Child1")   
                               //    .Include("Child2")
    
    root.Child1s.Remove(child11);
    root.Child1.Add(Child13); // 
    root.Child2.Add(Child22);
    using(var uow = new UnitOfWork())   
    {
        uow.Repository<Root>().Update(root);
        uow.Repository<AnotherRoot>().Update(anotherRoot); //user may want to update multiple Roots
        uow.SaveChanges();   <---- at this point Child13.Id and  Child22.Id generated by Db
    }
    

    public void Update(Root entity) //Update method in my Repository class
    { 
       var context = new MyDbcontext();
       var savedEntity = context.Roots //reload entity graph from db
                                .LoadAggregation()
                                .ToList();
       Mapper.Map(entity,savedEntity); // map user changes to original graph
       context.UpdateGraph(savedEntity, savedEntity.MappingConfiguration); // attach updated entity to dbcontext using graphdiff
    } 

    public void SaveChanges() // SaveChanges() in UnitofWork class
    {  
      context.SaveChanges();
    }

It works fine,

In second graph the Child13 and Child22 added by user and when I call uow.SaveChanges() they will save to Db and their Ids will be assign. but Child13.Id and Child22.Id in entity objects are 0 yet, I could manually update the Ids but I'm looking for generic way to update these Id values with Db generated Ids.

Masoud
  • 8,020
  • 12
  • 62
  • 123

1 Answers1

3

Personally, I'd take a slightly different approach.

A transaction like this shouldn't span multiple contexts. Even if you could update those ID's manually, you'd need some sort of alternate way of identifying those child objects to ensure you synced up the object instance correctly with the ID assigned by your database. Or it's possible other aspects of those objects were updated while transient, and will need to be applied to the database as well. Generally speaking, objects loaded from different DBContexts are different objects, even if they have the same database identity unless you've implemented some sort of caching on top of EF.

I'd do something like this. Use one context, and let EF manage all of the identify for you by injecting the context where you need to do persistence operations:

var context = new MyDbcontext();
var root = context
                           .Roots
                           .LoadAggregation()
                           .ToList();
                           //    LoadAggregation in this case, means following codes:   
                           //    .Include("Child1")   
                           //    .Include("Child2")

root.Child1.Remove(child11);
root.Child1.Add(Child13); // 
root.Child2.Add(Child22);
using(var uow = new UnitOfWork())   
{
    uow.Repository<Root>().Update(root, context);
    uow.Repository<AnotherRoot>().Update(anotherRoot, context); //user may want to update multiple Roots
    uow.SaveChanges(context);   <---- at this point Child13.Id and  Child22.Id generated by Db
}

public void Update(Root entity, MyDbcontext context) //Update method in my Repository class
{ 
   var savedEntity = context.Roots //reload entity graph from db
                            .LoadAggregation()
                            .ToList();
   Mapper.Map(entity,savedEntity); // map user changes to original graph
   context.UpdateGraph(savedEntity, savedEntity.MappingConfiguration); // attach updated entity to dbcontext using graphdiff
} 

public void SaveChanges(context) // SaveChanges() in UnitofWork class
{  
  context.SaveChanges();
}

If you're unable to use the same context for some reason, You may want to consider adding an additional identity field of some sort to the child object, such as a Guid, and writing that to the child prior to persisting back to the database via SaveChanges. At least you'd have a fairly consistent means of identifying the objects for syncing.

  • In my `Update` method, I map the `entity` object to `savedEntity` and `savedEntity` will be update, so I think your solution does not work. – Masoud Aug 19 '14 at 05:58
  • I think your second way is useful so could you explain more with code(root graph traversal for syncing Ids)? – Masoud Aug 19 '14 at 07:32
  • I'm trying to figure out what you're doing with the mapper. From my understanding, the data mapper is used to follow the DTO pattern for objects outside of the context. But it seems your first example and my re-factor are pulling all data from the context. So, if you're grabbing all data from the same context, all identities should be in sync because all of the objects lie in the same context pool. – MutantNinjaCodeMonkey Aug 19 '14 at 21:32
  • Otherwise, to address your second question, I'm not sure how to illustrate in code that matches whatever pattern you're following here. But the core issue is determining how transient ORM objects are equal when they don't have a database identity. I suggested a Guid because it can be generated prior to committing to the database, and you can also carry it across when you're mapping to your DTO. At that point, it's a matter of looking at your persisted objects (which will have the guid and db-generated ID), and write the db IDs to the objects with matching guids that don't have them. – MutantNinjaCodeMonkey Aug 19 '14 at 21:40