开发者

How do I find the source of all the objects that are being tracked for changes in EF CodeFirst CTP5?

开发者 https://www.devze.com 2023-02-16 21:12 出处:网络
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 t

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?


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.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号