【发布时间】:2011-03-15 22:34:42
【问题描述】:
我遇到了“一个实体对象不能被多个 IEntityChangeTracker 实例引用”的问题。经过一番检查,似乎我有一个正在跟踪更改的对象。问题是我不知道问题对象的来源......它显然已被放入上下文中,但我不确定哪个调用没有被正确分离。
因此,经过数小时的尝试解决这个问题,我正在寻找如何遍历树以找到与我发生冲突的源对象,因为这可能有助于我了解源对象的位置正在添加中。
错误在第 226 行被抛出,所以看起来我要么存在“隐形”客户,要么可能是客户的属性之一导致了这种情况,因为客户有几个其他属性是他们自己的复杂对象类型...
Line 224: if (null != this.Customer)
Line 225: {
Line 226: context.Entry(this.Customer).State = EntityState.Unchanged;
Line 227: }
错误并没有说明 哪个 对象导致了错误,它只是指向第 226 行。假设它是导致此错误的幻像 Customer 对象,我'试过了:
var test = ((IObjectContextAdapter)dataContext).ObjectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Added | EntityState.Deleted | EntityState.Modified | EntityState.Unchanged);
foreach(var e in test)
{
if(e.GetType() == typeof(Customer))
{
dataContext.Detach(e);
}
}
这个想法是遍历包含所有对象引用的东西,希望找到顽皮的客户并启动它。但是,唉,这行不通。在此循环中找不到客户。哦,仅供参考 - 这是在前面的代码之前运行的几行代码,所以我不会在那里偷偷创建任何额外的对象。
所以我需要一种方法来确定实际上是哪个对象导致了错误。
@Ladislav - 仅供参考 - 我有一个包含所有业务对象 (BO) 的公共库。这个公共库被其他项目使用——Windows 服务、Web 服务等。我试图让每个 BO 负责填充和保存自己,这样我就没有一个 hugo 数据访问类。每个 BO 负责自己的 Save() 方法。以下是 current saveUpdate 方法的示例:
public void SaveOrUpdate(DataContext context)
{
if (context.Entry(this).State == EntityState.Detached)
{
context.Customers.Add(this);
context.SaveChanges();
}
else //update
{
context.Entry(this).State = System.Data.EntityState.Modified;
context.SaveChanges();
}
}
关于您对范围的建议,我尝试了各种策略 - 乍一看,每个人都说以原子方式进行 - 所以我让每个方法都获取 DataContext 的一个新实例来完成它的工作。这很好用,只要对象不是很复杂,并且不相互依赖,即只包含基本类型属性,如 int 和 string。
但是一旦我开始遇到这些并发错误,我就对其进行了深入研究,发现 DataContext 以某种方式保留了对对象的引用即使它被丢弃这有点疯狂工程,恕我直言。也就是说,如果我将 Customer BO 添加到 DataContext 然后允许 DataContext 超出范围并被 Disposed,然后启动 new DataContext 来做某事,原来的 Customer BO 指针仍然存在!
所以我在 StackOverflow 上阅读了一堆(我可能会补充您的许多答案),Rick Strahl's treatise on DataContext Lifetime Management 和 8 Entity Framework Gotchas by Julia Lerman
所以 Julia 说要放入一个 Dispose 方法,我也这样做了,但它没有帮助,DataContext 仍然神奇地保留了引用。
所以 Rick 说尝试使用“全局”DataContext,这样您就只需要担心一个 DataContext,它应该知道正在发生的一切,所以它不会踩到自己的脚趾。但这似乎也不起作用。公平地说,Rick 说的是 Linq to SQL 和一个网络应用程序,但我有点希望它也适用于我。
然后各种答案说你不想要一个全局数据上下文,因为它会变得非常大,非常快,因为它保存了关于你所有对象的所有信息,所以只需使用工作单元的DataContext。
嗯,我已经分解了一个工作单元,它表示对一组对象所做的所有更改、添加和更新,您希望一起完成这些更改、添加和更新。因此,对于我的示例,这里有一些 BO 和属性:
消息组
- 属性:列表
- 属性:客户
客户
- 属性:列表
- 属性:列表
留言
- 属性:客户
- 属性:MessageGroup
- 属性:用户
用户
- 属性:客户
- 属性:列表
当 MessageGroup 到达(作为 Xml)时,系统会对其进行检查和解析。 MessageGroup 构造函数使用依赖注入并将 DataContext 作为其参数之一——因此所有正在创建的“子”BO 都在使用 DataContext 的这一实例。从数据库中获取客户(或创建一个新客户)并分配给 MessageGroup...让我们假设它是一个现有客户 - 所以不需要对其进行更新,它是新鲜的的数据上下文。
然后 MessageGroup.Messages 列表被循环,第一个要创建的 Child BO 是一个新的 User 对象。我将相同的客户对象(来自 MessageGroup)分配给用户。但是,当调用 context.Users.Add(this) 时,我得到了错误。如果我不将客户分配给用户,我不会收到错误消息。
所以现在我有一个来自数据库的新客户(或一个子属性,我不确定),我不需要跟踪这导致我焦虑。我想我可以通过使用类似的东西将它从上下文中删除:
var cust = Customer.GetCustomerFromExternalId(crm.CustomerId);
dataContext.Detach(cust);
dataContext.SaveChanges();
但我仍然收到错误,即使我已明确删除它。当然,如果它是 Customer 的子属性之一,也许它还没有被删除?
目前我想知道存储库模式是否适合我的目的。我还想知道 EF CodeFirst 是否存在根本缺陷或过于复杂?也许我应该改用 SubSonic 或 NHibernate?
【问题讨论】:
-
还要注意 CTP5 是旧版本。 EF4.1RC 已经发布:microsoft.com/downloads/en/…
-
@Ladislav - “老”有点厚脸皮!它是昨天发布的!
-
是的 :) 但 CTP5 是不支持的版本 EF4.1 是支持的版本。我稍后会检查您的问题。
-
我不知道 Julia 描述的那种行为。很有趣。
-
是的,在浏览论坛时,Juila 似乎对 EF 感到很痛苦!
标签: c# entity-framework entity-framework-4 entity-framework-ctp5 ef-code-first