Entity Framework Multiple Object Contexts

20,941

Solution 1

@Danny Varod is right. You should use one ObjectContext for the whole workflow. Moreover because your workflow seems as one logical feature containing multiple windows it should probably also use single presenter. Then you would follow recommended approach: single context per presenter. You can call SaveChanges multiple times so it should not break your logic.

The source of this issue is well known problem with deficiency of dynamic proxies generated on top of POCO entities combined with Fixup methods generated by POCO T4 template. These proxies still hold reference to the context when you dispose it. Because of that they think that they are still attached to the context and they can't be attached to another context. The only way how to force them to release the reference to the context is manual detaching. In the same time once you detach an entity from the context it is removed from related attached entities because you can't have mix of attached and detached entities in the same graph.

The issue actually not occures in the code you call:

itemFromA.RelatedProperty = itemFromB;

but in the reverse operation triggered by Fixup method:

itemFromB.RelatedAs.Add(itemFromA);

I think the ways to solve this are:

  • Don't do this and use single context for whole unit of work - that is the supposed usage.
  • Remove reverse navigation property so that Fixup method doesn't trigger that code.
  • Don't use POCO T4 template with Fixup methods or modify T4 template to not generate them.
  • Turn off lazy loading and proxy creation for these operations. That will remove dynamic proxies from your POCOs and because of that they will be independent on the context.

To turn off proxy creation and lazy loading use:

var context = new MyContext();
context.ContextOptions.ProxyCreationEnabled = false;

You can actually try to write custom method to detach the whole object graph but as you said it was asked 500 times and I haven't seen working solution yet - except the serialization and deserialization to the new object graph.

Solution 2

I think you have a few different options here, 2 of them are:

  1. Leave context alive until you are done with the process, use only 1 context, not 2.

  2. a. Before disposing of context #1, creating a deep clone of graph, using BinaryStreamer or a tool such as ValueInjecter or AutoMapper.

    b. Merge changes from context #2 into cloned graph.

    c. Upon saving, merge changes from cloned graph into graph created by new ObjectContext.


For future reference, this MSDN blogs link can help decide you decide what to do when: http://blogs.msdn.com/b/dsimmons/archive/2008/02/17/context-lifetimes-dispose-or-reuse.aspx

Solution 3

I don't think you need to detach to solve the problem.

We do something like this:

public IList<Contact> GetContacts()
{
  using(myContext mc = new mc())
  {
    return mc.Contacts.Where(c => c.City = "New York").ToList();
  }
}

public IList<Sale> GetSales()
{ 
  using(myContext mc = new mc())
  {
    return mc.Sales.Where(c => c.City = "New York").ToList();
  }  
}

public void SaveContact(Contact contact)
{
    using (myContext mc = new myContext())
    {
       mc.Attach(contact);
       contact.State = EntityState.Modified;
       mc.SaveChanges();
    }
}

public void Link()
{
   var contacts = GetContacts();
   var sales = GetSales();

   foreach(var c in contacts)
   {
       c.AddSales(sales.Where(s => s.Seller == c.Name));
       SaveContact(c);
   }
}

This allows us to pull the data, pass it to another layer, let them do whatever they need to do, and then pass it back and we update or delete it. We do all of this with a separate context (one per method) (one per request).

The important thing to remember is, if you're using IEnumerables, they are deferred execution. Meaning they don't actually pull the information until you do a count or iterate over them. So if you want to use it outside your context you have to do a ToList() so that it gets iterated over and a list is created. Then you can work with that list.

EDIT Updated to be more clear, thanks to @Nick's input.

Share:
20,941
Jeff
Author by

Jeff

Updated on July 09, 2022

Comments

  • Jeff
    Jeff almost 2 years

    This question has been asked 500 different times in 50 different ways...but here it is again, since I can't seem to find the answer I'm looking for:

    I am using EF4 with POCO proxies.

    A. I have a graph of objects I fetched from one instance of an ObjectContext. That ObjectContext is disposed.

    B. I have an object I fetched from another instance of an ObjectContext. That ObjectContext has also been disposed.

    I want to set a related property on a bunch of things from A using the entity in B....something like

    foreach(var itemFromA in collectionFromA)
    {
       itemFromA.RelatedProperty = itemFromB;
    }
    

    When I do that, I get the exception:

    System.InvalidOperationException occurred
      Message=The relationship between the two objects cannot be defined because they are attached to different ObjectContext objects.
      Source=System.Data.Entity
      StackTrace:
           at System.Data.Objects.DataClasses.RelatedEnd.Add(IEntityWrapper wrappedTarget, Boolean applyConstraints, Boolean addRelationshipAsUnchanged, Boolean relationshipAlreadyExists, Boolean allowModifyingOtherEndOfRelationship, Boolean forceForeignKeyChanges)
           at System.Data.Objects.DataClasses.RelatedEnd.Add(IEntityWrapper wrappedEntity, Boolean applyConstraints)
           at System.Data.Objects.DataClasses.EntityReference`1.set_ReferenceValue(IEntityWrapper value)
           at System.Data.Objects.DataClasses.EntityReference`1.set_Value(TEntity value)
           at 
    

    I guess I need to detach these entities from the ObjectContexts when they dispose in order for the above to work... The problem is, detaching all entities from my ObjectContext when it disposes seems to destroy the graph. If I do something like:

    objectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Added | EntityState.Deleted | EntityState.Modified | EntityState.Unchanged)  
    .Select(i => i.Entity).OfType<IEntityWithChangeTracker>().ToList()  
    .ForEach(i => objectContext.Detach(i));
    

    All the relations in the graph seem to get unset.

    How can I go about solving this problem?

  • Jeff
    Jeff about 13 years
    1. It looks like your example is with EntityObjects, not POCOs, correct? 2. I don't have access to the object context directly at the point where I am trying to set the related entity. I only have a contact entity and a sale entity and I'm trying to set the sale.Seller property using the contact entity. The object contexts have long since been disposed and have gone out of scope.
  • Jeff
    Jeff about 13 years
    Updated question with the error I get...sorry should have posted that in the first place...
  • Jerph
    Jerph about 13 years
    You don't really want to open up a new context for each request. And then open up another every-time you do a save. This is going to kill performance, you want to maintain one unit of work for your entire action. Or in other words, open the context do your work, and then close the context.
  • Jeff
    Jeff about 13 years
    Agreed...but that case isn't applicable here. I have my collection A as a result of an entire workflow and business transaction that has taken place. entity B is separate and only comes into play after the workflow has completed.
  • taylonr
    taylonr about 13 years
    @Nick, our website has sub-second performance on searches of thousands (or even millions) of records. Performance with a one-per-call approach has not been a problem for us.
  • taylonr
    taylonr about 13 years
    @Jeff, no, we're using POCOs, code first. What about the above code is causing the confusion?
  • taylonr
    taylonr about 13 years
    @Jeff, given the above error in your update, this probably won't solve the problem. I thought you were running in to something else
  • Jeff
    Jeff about 13 years
    POCOs don't normally have a EntityState property on them...but then neither do EntityObjects (they have an EntityState)...so I'm not sure what's going on there: contact.State = EntityState.Modified;
  • Jeff
    Jeff about 13 years
    I sure as hell hope this isn't the answer.
  • Reaper
    Reaper about 13 years
    Why are you closing your contexts? - If you are merging results from various contexts then you are obviously not really working stateless. I suspect you many not be managing the context lifetime correctly.
  • Jeff
    Jeff about 13 years
    I have two completely different screens in a Windows Forms application. Sometimes, these two screens are shown in sequence, initiated by another (third) screen. In this workflow, the resulting entity/entities from 2 screens are used by the caller to perform an operation. Accordingly, the presenters for the two screens have already disposed and so have the ObjectContexts they were using.
  • Reaper
    Reaper about 13 years
    Do both screens run under the same application? Are both screens presented to the same user? Don't you want to see the affect changes in one screen have on the other?
  • Reaper
    Reaper about 13 years
    Why is your presentation layer opening an ObjectContext? Don't you have a Business layer or Controller or ViewModel underneath that is common to the various views?
  • Jeff
    Jeff about 13 years
    It doesn't. There's a services layer that a Presenter talks to. Simplest scenario I can explain is this: User goes through workflow to create a new "Widget". Widget is saved via a business services call and the Presenter for "Create New Widget" screen returns an instance of the Widget to the calling Presenter (the one that popped up the "Create New Widget" window). A screen with a ComboBox populated by entities appears. The Presenter for this screen returns the selected entity in the ComboBox. The calling Presenter needs to take the new widget entity and set the selected ComboBox value on it.
  • Reaper
    Reaper about 13 years
    So your entire application shares the state (which makes sense, since it is a desktop application). Why are you using stateless SOA or SOA in general for a desktop application? Why not use one context and have your views bind to the same model/state?
  • Jeff
    Jeff about 13 years
    Services layer may run in distributed mode (WCF) in the future.
  • Reaper
    Reaper about 13 years
    Split the service layer into 2: 1. A BL layer that can be shared between desktop and server, 2.a. A Services layer for desktop that (assuming stateless) opens and closes a new context each time. 2.b. A desktop controller layer that opens up context and keeps it alive. Layers 2.a./2.b. use layer 1. Layer 1. is given the context (does not manage it).
  • Jeff
    Jeff about 13 years
    This seems more like accommodating for a poorly thought out, half baked Microsoft implementation than developing a well architected solution.
  • Reaper
    Reaper about 13 years
    Actually, a "well architected solution" must fit the problem domain. Yours is a desktop application, not a stateless web-service.
  • Jeff
    Jeff about 13 years
    The entire process you described splitting the services layer is only necessary because of a poor implementation/architecture in Entity Framework. Having to contort the architecture of your software to address fundamental flaws in the framework you build it on detracts from how "well architected" your software is. To say the problem here is statefullness vs. statelessness is to neglect the fact that the only reason this is a problem is because of EF.
  • Reaper
    Reaper about 13 years
    I would advise against using the exact same application (minus the presentation layer) for a stateful desktop app and a multi-user stateless web service, since they are not meant to act the same. Blaming EF won't help :-) Even with a stateful web-service I would recommend revising the architecture.
  • Jerph
    Jerph about 13 years
    @taylonr sub-second isn't really that great. My typical response time is about 200ms. You should check out the EfProf, it might help you find issues and speed up your site.
  • taylonr
    taylonr about 13 years
    @Nick, our site doesn't need much help, we're in that range, I was just talking in general when I said sub-second (guess I could have said sub-half-second but that seemed like a mouthful)
  • Jerph
    Jerph about 13 years
    @taylonr - well Ayende is a very smart guy efprof.com/learn/alerts/MultipleSessionsInTheSameRequest
  • taylonr
    taylonr about 13 years
    @Nick, now I understand the confusion... I said "One Per Method" which wasn't entirely true, most of our requests are a single method (Like Get All Sales Leads for Mike, so in those cases, it's one per method and each request is one method.) In our more complicated requests we still do 1 per request...sounds like me not being clear ended up with us arguing the same side :) My bad.