1

I'm using code first and have a number of entities that have a requirement to have a "work in progress" version of each. Traditionally we've implemented this pattern by cloning the tables for all of these entities with a prefixed "wip." version. Although this system works there is a lot of uglyness in the code and I'm hoping to find a cleaner less obtrusive solution using Entity Framework.

Ideally I'd love something close to this:

using (MyDBContext ctx = new MyDBContext()) {
    Person myPerson = ctx.First(x => x.Name == "frank");
    // Do work with the non "work in progress" entities
}

using (MyWIPContext wipCtx = new MyWIPContext()) {
    Person myPerson = wipCtx.First(x => x.Name == "frank");
    // Do work with the "work in progress" entites
    // If I need to move this entity to non "Work in Progress" maybe do:
    ctx.Attach(myPerson);
    ctx.SaveChanges();   // Where ctx is the non "Work in Progress context"
}

From my digging I feel like this might be possible. I found that I can add a rule to prefix "wip." in front of my tables (How to Add Table Prefix In Entity Framework Code First Globally?)

Also found a post deal with multiple schema's (Entity Framework and multiple schemas) Referencing this article (http://romiller.com/2011/05/23/ef-4-1-multi-tenant-with-code-first/)

Some of the problems I'm hitting is with using migrations to create the database. If I have multiple DBContext's migrations start getting messy and with that second article they don't work at all as they don't give there DBContext a way to be constructed so migrations fail.

Does anyone know of a clean way to implement this pattern. I'd love to have it as non obtrusive as possible. The entities should not be aware that they have 2 places they can persist in (Work in Progress & Real Version). I know I could do this by adding flags to the entities but I feel like there is another way.

Community
  • 1
  • 1
Mikeb
  • 781
  • 7
  • 24
  • Which EF version are you using? – tede24 Mar 15 '16 at 21:24
  • Its a new project so I could use the latest and greatest – Mikeb Mar 15 '16 at 22:05
  • So if you use EF6 the problem with migrations and multiple dbcontext doesn't exist anymore. You just need to set "Configuration" parameter when executing commands. http://stackoverflow.com/questions/21537558/multiple-db-contexts-in-the-same-db-and-application-in-ef-6-and-code-first-migra. The issue I can see in your approach is about managing IDs and FKs in case you create new entities in WIP area and use database generated identities – tede24 Mar 15 '16 at 22:18
  • I'm lucky in that I'll be using no DB generated identities, all the entities have there own external keys. Is there no way to have both DbContext's merged into 1 migration? It'll all be on the same database – Mikeb Mar 16 '16 at 19:56
  • I don't think you can share migrations. IE AddColumn would include table name as parameter so if you have different tables it won't work – tede24 Mar 16 '16 at 22:57

1 Answers1

1

You could have 2 classes (LivePerson and WipPerson) inheriting your current Person class. Then you would tell Entity Framework to map each class to a different table.

EF config:

modelBuilder.Entity<LivePerson>()
    .Map(m => m.MapInheritedProperties()
        .ToTable("Persons"));

modelBuilder.Entity<WipPerson>()
    .Map(m => m.MapInheritedProperties()
        .ToTable("WipPersons"));

Then you can do this:

// Create Frank
using (var db = new Db())
{
    // Create Frank
    LivePerson frank = new LivePerson { Name = "Frank" };

    // Clone Frank
    WipPerson wipFrank = Clone<LivePerson, WipPerson>(frank);

    // Add to database and save
    db.Persons.Add(frank);
    db.WipPersons.Add(wipFrank);
    db.SaveChanges();
}

// Edit WIP
using (var db = new Db())
{
    var wipFrank = db.WipPersons.First(/*Todo: Use query*/);

    wipFrank.Notes = "Okay";

    db.SaveChanges();
}

This would give you a copy of the same thing but in separate tables. However there is a new complexity: you have to be able to clone a Person from one type to another.

I used this method:

private static TOut Clone<TIn, TOut>(TIn obj)
{
    // using Newtonsoft.Json;
    return JsonConvert.DeserializeObject<TOut>(JsonConvert.SerializeObject(obj));
}

If your person is simple enough to be cloned I think this solution might work... Or at least you can concentrate on improving the clone from one type to another based on your needs.

This solution would also allow you to go one step further and add some properties to the WipPerson class to keep track of when the clone was created, who created it, who last edited, from which LivePerson the clone was made and so on.

Chris
  • 2,009
  • 1
  • 16
  • 25