如何查找 EF CodeFirst CTP5 中正在跟踪更改的所有对象的来源?

发布于 2024-10-22 07:22:26 字数 3839 浏览 3 评论 0原文

我遇到“实体对象无法被 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 超出范围并被处理,然后启动一个新的 DataContext 来执行某些操作,则原始的 Customer BO 指针仍然存在!

所以我在 StackOverflow 上读了很多(我可能会补充一下你的很多答案), Rick Strahl 关于 DataContext 生命周期管理的论文8 实体Julia Lerman 的框架陷阱

所以 Julia 说放入一个 Dispose 方法,我也这么做了,但这没有帮助,DataContext 仍然神奇地保留着引用。

因此,Rick 说尝试使用“全局”DataContext,这样您就只需担心一个 DataContext,并且它应该知道正在发生的一切,因此它不会踩到自己的脚趾。但这似乎也不起作用。公平地说,Rick 谈论的是 Linq to SQL 和 Web 应用程序,但我有点希望它也适用于我。

然后各种答案说你想要一个全局DataContext,因为它会变得非常大,非常快,因为它保存了所有对象的所有信息,所以只需使用DataContext 工作单元

好吧,我已经将工作单元分解为对您希望一起完成的一组对象所做的所有更改、添加和更新。对于我的示例,以下是一些 BO 和属性:

MessageGroup
- 属性:列表
- 属性:客户

客户
- 属性:列表
- 属性:列表

消息
- 财产:客户
- 属性:消息组
- 属性:用户

用户
- 财产:客户
- 属性:列表

在系统中,当 MessageGroup 到达(以 Xml 形式)时,将对其进行检查和解析。 MessageGroup 构造函数使用依赖注入并将 DataContext 作为其参数之一 - 因此正在创建的所有“子”BO 都使用 DataContext 的这一实例。从数据库中获取客户(或创建一个新客户)并将其分配给 MessageGroup...让我们假设它是一个现有客户 - 因此不需要对其进行更新,它是新鲜的的数据上下文。

然后循环 MessageGroup.Messages 列表,并且要创建的第一个子 BO 是一个新的 User 对象。我将相同的 Customer 对象(来自 MessageGroup)分配给 User。但是,当调用 context.Users.Add(this) 时,我收到错误。如果我不将客户分配给用户,则不会收到错误消息。

所以现在我有一个来自数据库的新客户(或子属性,我不确定),我不需要跟踪它,这会引起我的焦虑。我想我可以通过使用类似以下内容从上下文中删除它:

var cust = Customer.GetCustomerFromExternalId(crm.CustomerId);
dataContext.Detach(cust);
dataContext.SaveChanges();

但我仍然收到错误,即使我已经明确删除了它。当然,如果它是 Customer 的子属性之一,也许它还没有被删除?

目前我想知道存储库模式是否适合我的目的。我还想知道 EF CodeFirst 是否存在根本缺陷或过于复杂?也许我应该使用 SubSonic 或 NHibernate 来代替?

I'm having the 'An entity object cannot be referenced by multiple instances of IEntityChangeTracker' problem. After some checking around it seems like I have an object that's being tracked for changes. The problem is that I don't know the source of the problem object... it's obviously been put into the context, but I'm not sure which call hasn't been properly Detached.

So, after hours of trying to figure this out, I'm looking for how to walk the tree to find the source object that I'm having the conflict with, as maybe that will help me understand where the source object is being Added.

The error is being thrown on line 226, so it looks like I either have a 'stealth' Customer existing, or maybe one of the properties of Customer is causing this, as Customer has a couple other properties that are their own complex object types...

Line 224:                    if (null != this.Customer)
Line 225:                    {
Line 226:                        context.Entry(this.Customer).State = EntityState.Unchanged;
Line 227:                    } 

The error doesn't say which object is causing the error, it just points at line 226. Upon assuming it's a phantom Customer object that's causing this, I've tried:

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

The idea was to loop through the thing that holds references to all the objects, hopefully find the naughty Customer and give it the boot. But, alas, this didn't work; no Customers are found in this loop. Oh, FYI - this is run a few lines before the previous code so I'm not sneaking in any extra object creation there.

So I need a way to determine which object is in fact causing the error.

@Ladislav - FYI - I have a common library that contains all the business objects (BO). This common library is used by other projects - Windows Service, Web Service, etc. I've tried to make each BO responsible for populating and saving itself, so that I don't have one hugo data access class. Each BO is responsible for it's own Save() method. Here's an example of a current saveUpdate method:

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

In regards to your suggestion of scope, I've tried various strategies - At first blush everyone says do it atomically - so I had each method grabbing a new Instance of the DataContext to do it's work. This worked fine, as long as the objects weren't very complex, and didn't depend on each other, i.e. only contained base type properties like int and string.

But once I started getting these concurrency errors, I dug into it and found out that the DataContext somehow held on to references to objects even when it was disposed of That's a bit of a crazy-bad piece of engineering, IMHO. i.e. So if I add a Customer BO to the DataContext then allow the DataContext to go out of scope and be Disposed, and then spin up a new DataContext to do something, the original Customer BO pointer is still there!

So I read a bunch on StackOverflow (with many answers by you, I might add), Rick Strahl's treatise on DataContext Lifetime Management and the 8 Entity Framework Gotchas by Julia Lerman

So Julia says put in a Dispose method, and I did, but it didn't help, the DataContext is still magically holding onto the reference.

So Rick says try to use a 'global' DataContext, so that you only have one DataContext to worry about, and it should know everything that's going on, so it doesn't step on it's own toes. But that didn't seem to work either. To be fair, Rick is talking about Linq to SQL, and a web app, but I was kinda hoping it would apply to me too.

And then various answers say that you Don't want a Global DataContext, since it's going to get very big, very quickly, since it's holding all the info about all your objects, so just use DataContext for a Unit Of Work.

Well, I've broken down a Unit of Work to mean all changes, additions and updates done to a group of objects that you'd like done together. So for my Example Here are some BOs and properties:

MessageGroup
- Property: List
- Property: Customer

Customer
- Property: List
- Property: List

Message
- Property: Customer
- Property: MessageGroup
- Property: User

User
- Property: Customer
- Property: List

In the system when a MessageGroup arrives (as Xml), it's examined and parsed. The MessageGroup constructor used Dependency Injection and takes the DataContext as one of it's parameters - so all the 'child' BOs being created are using this one instance of the DataContext. The Customer is fetched from the database (or a new one is created) and assigned to the MessageGroup... let's assume it's an existing Customer - so no updates need to be done to it, it's fresh out of the DataContext.

Then the MessageGroup.Messages list is looped and the first Child BO to create is a new User object. I assign the same Customer object (from the MessageGroup) to the User. However, when context.Users.Add(this) is called, I get the error. If I don't assign the Customer to the User, I don't get the error.

So now I have a Customer (or a child property, I'm not sure) that's fresh from the DB, that I don't need tracked causing me angst. I thought I could just remove it from the context by using something like:

var cust = Customer.GetCustomerFromExternalId(crm.CustomerId);
dataContext.Detach(cust);
dataContext.SaveChanges();

But I still get the error, even though I've explicity removed it. Of course if it's one of the child properties of Customer, maybe that hasn't been removed?

Currently I'm wondering if the Repository Pattern is suitable for my purpose. I'm also wondering if EF CodeFirst is fundamentally flawed or just overly complex? Maybe I should use SubSonic or NHibernate instead?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

黑白记忆 2024-10-29 07:22:26

据我所知,可能没有明确的方法从 POCO 实体获取相关上下文 - 动态代理的所有相关属性都是非公开的。要检查 DbContext 中的实体,请使用:

context.ChangeTracker.Entries<Customer>().Where(e => e.State == ...)

避免问题的最佳方法是每个“工作单元”使用单个上下文。如果您有来自多个上下文的实体,您显然不会遵循这种方法。此外,您似乎正在使用多个并发活动上下文。

As I know there is probably no clear way to get related context from POCO entity - all related properties of dynamic proxy are non public. For checking entities in DbContext use:

context.ChangeTracker.Entries<Customer>().Where(e => e.State == ...)

The best way to avoid your problems is using single context per "unit of work". You obviously don't follow this approach if you have entities from multiple contexts. Moreover it looks like you are using multiple concurrent alive contexts.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文