greenoldman
greenoldman

Reputation: 21082

How can I correctly attach record object to data context in Entity Framework?

I need to switch data context for some records. So basically I have db context A and B, I fetch records using A, then I switch to B, alter records, and save them.

When I call Attach for B, I get exception that records are using by multiple data context, when I add Detach for A, I get exception, that records are not attached to A.

So how can I switch the data context?

Example

db_creator is creator of db context. Here I fetch the data (corrected version):

using (var db = db_creator.Create())
{
  var coll = db.Mailing.Where(it => !it.mail_IsSent).ToList(); // (*)
  coll.ForEach(it => db.Detach(it));
  return coll;
}

(*) the mistake was caused by refactoring this piece, I created extra data context, and then later I tried to detach records from another one.

Now I would like to switch data context to new one, do some computation and modifications and save the records. coll is List of the records:

using (var db = db_creator.Create())
{
  coll.ForEach(it => db.Mailing.Attach(it));
  ...
  db.SaveChanges();
}

Upvotes: 1

Views: 1329

Answers (2)

Andreas
Andreas

Reputation: 6475

I have come to the conclusion that it's best (i.e. easier to avoid problems) to use ApplyCurrentValues instead of attaching. That is because when you call Attach there are several things going on that we don't know about, but which may surface in one way or the other through an exception. I prefer to do things the way I have control over what is done.

var myMailings = db_creator.Create().Mailing.Where(it => !it.mail_IsSent).ToList();
... // make modifications and retrieve coll a collection of Mailing objects
using (var db = db_creator.Create()) {
  ... // if you want to further modify the objects in coll you should do this before writing it to the context
  foreach (Mailing it in coll) {
    if (it.EntityKey != null) db.GetObjectByKey(it.EntityKey); // load entity
    else db.Mailing.Single(x => x.YourId == it.YourId); // load the entity when EntityKey is not available, e.g. because it's a POCO
    db.Mailing.ApplyCurrentValues(it); // sets the entity state to Modified
  }
  db.SaveChanges();
}

EDIT:

I tested the performance of this vs using Attach. It should be noted that for a simple table with an integer primary key, an int, a float and a string column for updating 1000 entries: the difference was 2.6s vs 0.27s, so this is significantly slower.

EDIT2:

A similar question was raised here. There the answer warned about using ApplyCurrentValues in conjunction with timestamp columns.

I also compared performance when loading the entity with db.GetObjectByKey(it.EntityKey) and there the performance difference is much smaller. ApplyCurrentValues then just takes 0.44s.

Upvotes: 1

Kamyar
Kamyar

Reputation: 18797

I recommend change your design and have ONE context at a time. (Based on your project type this could vary. Usually in web apps it's one context per http request.)

For example in a web application, you can do this like below:

protected MyContext Context
{
    get
    {
        var context = HttpContext.Current.Items["MyContext"];
        if (context == null)
        {
            context = new MyContext();
            HttpContext.Current.Items.Add("MyContext", context);
        }
        return context as MyContext;
    }
}  

And dispose it in your Application_EndRequest:

app.EndRequest += (sender, args) =>
{
    HttpContext.Current.Items.Remove("MyContext");
}

If you have multiple project types, then consider using an Ioc.
But if you still want to use two contexts, you can do as below(myEntity is your object you want to detach/attach):

if (context1.Entry(myEntity).State != EntityState.Detached);
{
    ((IObjectContextAdapter)context1).ObjectContext.Detach(myEntity);
}
context2.MyEntities.Attach(myEntity);

Upvotes: 1

Related Questions