Entity Framework多个对象上下文

7
这个问题已经以50种不同的方式被问了500次...但是我还是要问一遍,因为我似乎找不到我想要的答案:
我正在使用带有POCO代理的EF4。
A. 我有一个对象图,它是从一个ObjectContext实例中获取的。该ObjectContext已被处理。
B. 我有一个对象,它是从另一个ObjectContext实例中获取的。该ObjectContext也已被处理。
我想使用B中的实体来设置A中一堆东西的相关属性...类似于
foreach(var itemFromA in collectionFromA)
{
   itemFromA.RelatedProperty = itemFromB;
}

当我这样做时,我会收到异常:
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 

我猜我需要在ObjectContex销毁时从中分离这些实体,以使上述操作正常运行...问题是,当ObjectContext销毁时,从它分离所有实体似乎会破坏整个图。如果我做这样的事情:

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

图中的所有关系似乎都被取消了。

我应该如何解决这个问题?

4个回答

14

@Danny Varod是正确的。您应该为整个工作流程使用一个ObjectContext。此外,由于您的工作流似乎是包含多个窗口的一个逻辑特性,因此它可能还应该使用单个Presenter。然后,您将遵循推荐的方法:每个Presenter一个上下文。您可以多次调用SaveChanges,因此它不会破坏您的逻辑。

这个问题的源头是POCO实体顶部生成的动态代理的不足,再加上POCO T4模板生成的Fixup方法。这些代理在您处理完上下文后仍然持有对其的引用。由于这样,它们认为它们仍然连接到上下文中,并且无法连接到另一个上下文。唯一强制它们释放对上下文的引用的方法是手动分离。同时,一旦您从上下文中分离了一个实体,它就会从相关的附加实体中删除,因为您不能在同一图形中同时拥有附加和分离的实体。

实际上,这个问题并没有出现在您调用的代码中:

itemFromA.RelatedProperty = itemFromB;

但在由Fixup方法触发的相反操作中:

itemFromB.RelatedAs.Add(itemFromA);

我认为解决这个问题的方法有:

  • 不要这样做,使用单一的上下文来处理整个工作单元 - 这是预期的用法。
  • 删除反向导航属性,这样Fixup方法就不会触发该代码。
  • 不要使用带有Fixup方法的POCO T4模板,或者修改T4模板以不生成它们。
  • 对于这些操作关闭延迟加载和代理创建。这将从您的POCO中删除动态代理,因此它们将独立于上下文。

要关闭代理创建和延迟加载,请使用:

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

实际上,您可以尝试编写自定义方法来分离整个对象图,但正如您所说,这已经被问了500次,我还没有看到有效的解决方案 - 除了将其序列化并反序列化到新的对象图中。


5
我认为你有几个不同的选择,其中两个是:
  1. 在处理过程完成之前保持上下文处于活动状态,仅使用一个上下文而不是两个。

  2. a. 在处理完上下文#1之前,使用BinaryStreamer或诸如ValueInjecter或AutoMapper之类的工具创建图形的深度克隆。

    b. 将来自上下文#2的更改合并到克隆的图形中。

    c. 在保存时,将克隆的图形中的更改合并到由新ObjectContext创建的图形中。


供以后参考,此MSDN博客链接可帮助您决定何时进行以下操作: http://blogs.msdn.com/b/dsimmons/archive/2008/02/17/context-lifetimes-dispose-or-reuse.aspx

1
我真的希望这不是答案。 - Jeff
为什么要关闭上下文?- 如果您正在合并来自各种上下文的结果,那么显然您并没有真正地工作在无状态模式下。我怀疑您可能没有正确管理上下文生命周期。 - Danny Varod
我在一个Windows Forms应用程序中有两个完全不同的屏幕。有时,这两个屏幕按顺序显示,由另一个(第三个)屏幕启动。在这个工作流程中,来自2个屏幕的结果实体/实体被调用者用于执行操作。因此,两个屏幕的Presenter已经被处理,它们使用的ObjectContexts也已经被处理。 - Jeff
将服务层分为两部分:1. 一个业务逻辑层,可在桌面和服务器之间共享;2.a. 一个桌面服务层(假设无状态),每次打开和关闭一个新的上下文;2.b. 一个桌面控制器层,打开并保持上下文处于活动状态。层2.a./2.b. 使用层1. 层1. 获得上下文(不管理它)。 - Danny Varod
我建议不要使用完全相同的应用程序(除了表示层),用于有状态的桌面应用程序和多用户无状态Web服务,因为它们的作用不同。责怪EF是没有帮助的 :-) 即使是有状态的Web服务,我也建议重新审查架构。 - Danny Varod
显示剩余8条评论

3

我认为你不需要分离来解决这个问题。

我们做的类似于这样:

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);
   }
}

这使我们能够提取数据,将其传递给另一层,让它们执行所需操作,然后将其返回并更新或删除。我们使用单独的上下文 (每个方法一个)(每个请求一个)完成所有这些操作。
重要的是要记住,如果您使用IEnumerable,则它们是延迟执行。这意味着它们直到您进行计数或迭代时才实际提取信息。因此,如果您想在上下文之外使用它,请执行ToList(),以便对其进行迭代并创建列表。然后您就可以使用该列表进行操作。 编辑 已更新以更清晰明了,感谢@Nick的建议。

更新了带有错误的问题...抱歉,应该在第一时间发布它... - Jeff
@Nick,我们的网站在搜索数千(甚至数百万)条记录时具有亚秒级性能。采用一次调用的方法并没有给我们带来问题。 - taylonr
@Jeff,不是的,我们正在使用POCOs,先写代码。上面的代码有什么让人困惑的地方吗? - taylonr
1
POCO通常不会在它们上面有一个EntityState属性...但是EntityObjects也没有(它们有一个EntityState)...所以我不确定那里发生了什么:contact.State = EntityState.Modified; - Jeff
@Nick,现在我明白混淆的原因了...我说过“每个方法一个”,这并不完全正确,我们大多数的请求都是单个方法(比如获取Mike的所有销售线索,所以在这些情况下,每个方法只有一个请求。)在我们更复杂的请求中,我们仍然每个请求一个方法...听起来像是我没有表达清楚,最终导致我们争论同一方面:)我的错。 - taylonr
显示剩余7条评论

0

好的,我明白你的对象上下文已经消失了。

但是让我们这样看待它,Entity Framework 实现了工作单元概念,在其中跟踪您在对象图中所做的更改,以便可以生成与您所做的更改相对应的 SQL。如果没有附加到上下文,则无法跟踪更改。

如果您无法控制上下文,则我认为您无能为力。

否则,有两个选择:

  1. 使您的对象上下文保持更长的生命周期,例如用户登录会话等。
  2. 尝试使用自跟踪文本模板重新生成代理类,这将使断开连接状态下的更改跟踪成为可能。

但即使在自跟踪的情况下,您仍然可能会遇到一些小问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接