"An entity object cannot be referenced by multiple instances of IEntityChangeTracker." What is it?
Hi All
While I attach an object to a ObjectContext, it shows
"An entity object cannot be referenced by multiple instances of IEntityChangeTracker."
What is this problem?
Regards
Alex
답변
Sounds like the object was attached to another context and not detached. There's a discussion of this in another forum thread (http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=2971822&SiteID=1) (guess who started it? :-)), but if you kill an instance of an objectContext without detaching the entities first the entity still think it's attached to the context.
I hate this problem because it puts another roadblock resulting in a bunch of hacks to workaround it when trying to persist entities or move them across tiers. THey chose this to avoid a performance hit, but it creates a lot of confusion and causes a lot of extra code.
모든 응답
Sounds like the object was attached to another context and not detached. There's a discussion of this in another forum thread (http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=2971822&SiteID=1) (guess who started it? :-)), but if you kill an instance of an objectContext without detaching the entities first the entity still think it's attached to the context.
I hate this problem because it puts another roadblock resulting in a bunch of hacks to workaround it when trying to persist entities or move them across tiers. THey chose this to avoid a performance hit, but it creates a lot of confusion and causes a lot of extra code.
- I so the link there thanks
I am wondering should I create a shared property in my application that returns the object context?
and create a method in the application that disposes the context and sets the field to null?
what about it?
does it sound good, risky?
any links on how to manage contexts in cross-classes/cross-methods will be really appreciated!
Shimmy - Working in a web app I am very fond of the Repository approach. I generally create a new context inside a using statement which then kills the context for me once my snippet of work is complete. So for an AccountsRepository I might have a Save(Account account) method which would have something like the following:
using (RanchBuddyDataContext dc = new Connection().GetContext()) { if(account.AccountID > 0) { dc.Accounts.Attach(account, true); } else { dc.Accounts.InsertOnSubmit(account); } dc.SaveChanges(); }
While this worked great in LINQ to SQL I am finding that it doesn't work so well with the Entity Framework. I am now getting the error that started this thread. In order to address this problem I have to do a few things and am not liking the work that goes into this. Hopefully someone can adjust my way of thinking on this.
For my particular issue I have a person that logs into the system. Once the log in is correct I stash their account object (fairly light) into their current session. I then refer to that now and then as needed. This works great. However, when the user goes to the edit account screen, I would normally load the screen with their session held Account object. Allow the user to edit their data and then throw it at a repository method as descibed above.
Not any more!
Up to the point where the user logs in is unchanged. However, the object that is returned to me by my login method now has to be detached from the context - a step that I must remember any time that I want to keep and object out of the db, fiddle with it (edit it), and then eventually persist it back to the db. Then it is put in the session for later use. Once the user edits their account I have to get an original copy of the object (I am guessing so that the context is aware of it???) and then attempt to persist my object (with changes) back to the db.
I now have this code for getting the Account by username:public Account GetAccountByUsername(string Username) { Account result = null; using (RanchBuddyEntities dc = connection.GetContext()) { result = dc.AccountSet.Where(a => a.Username == Username).FirstOrDefault(); dc.Detach(result); } return result; }
If the account that is returned from a username search matches the supplied password then the account object here goes into my session. Notice that at the bottom of my query above I have a dc.Detach(result) statement to remove it from the context. From what I have read so far I have to do this so that I can re-attach it later on or keep a copy of the original object with me everywhere I go.
Then comes my save code.using (RanchBuddyEntities dc = connection.GetContext()) { if(account.AccountID > 0) { Account original = dc.AccountSet.Where(a => a.AccountID == account.AccountID).FirstOrDefault(); dc.ApplyPropertyChanges("AccountSet",account); } else { dc.AddToAccountSet(account); } dc.SaveChanges(); }
In this code I now have to get the original object into the context (as I don't carry the original object around with me just in case I need it and I guess the context is not aware of it if I don't manually load it??), then ApplyPropertyChanges of my updated object, and finally SaveChanges() on the context.
I would love to hear if anyone has a better way of working with this. I much prefer the way I was able to do this in LINQ to SQL as there were less steps to remember and I didn't need to litter various methods with snippets so that one method would work correctly with another (when they shouldn't care about each other at all).
- I'd forgotten about this issue for a while and then bumped into it again - I find there's a pretty simple solution to it -
(
EntityClass ) currentContext.GetObjectByKey(objectOfEntityClassInOtherContext.EntityKey)
In short, whenever you are in the situation of binding an entity from one context to another, just go get a new instance in the current context using the entity key from the old context.
That said, the performance characteristics of this are unknown - Julie ?
Public Class BasePage : Inherits Page Private m_entities As Entities Public ReadOnly Property DataContext() As Entities Get If m_entities Is Nothing OrElse m_entities.IsDisposed Then m_entities = New Entities Return m_entities End Get End Property Public Overrides Sub Dispose() MyBase.Dispose() If DataContext IsNot Nothing AndAlso Not DataContext.IsDisposed Then DataContext.Dispose() End Sub End Class
Then have all you aspx pages inherit from this page, and no worries about the context, you feel like it's built in the page, I already got used to it!Partial Class Entities Private m_IsDisposed As Boolean Public ReadOnly Property IsDisposed() As Boolean Get Return m_IsDisposed End Get End Property Protected Overrides Sub Dispose(ByVal disposing As Boolean) MyBase.Dispose(disposing) m_IsDisposed = True End Sub End Class
Shimmy- 답변으로 제안됨Shimmy Weitzhandler 2009년 6월 14일 일요일 오후 10:46
- After reading this replies and other related posts, I came up with a solution which seems to cover most situations. The following code gives you access to theObjectContext through the lifetime of the current request. When the ObjectContext gets disposed it automatically removes the changetracker from unmodified entities, allowing you to do this:
var entity = new MyDataEntity(); using (var context = new MyDataEntities()) { entity.TextField = "Some Text"; context.AddToDataEntity(entity); context.SaveAllChanges(); } using (var context2 = new MyDataEntities()) { context2.Attach(entity); entity.TextField = "Some Different Text"; context2.SaveAllChanges(); }
or if you want to maintain the ObjectContext:public MyDataEntity CreateEntity(string text) { var context = MyDataEntities.Current; var entity = new MyDataEntity(); entity.TextField = text; context.AddToMyDataEntity(entity); context.SaveAllChanges(); return entity; } var context = MyDataEntities.Current; var entity = CreateEntity("Some Text"); entity.TextField = "Some Different Text"; context.SaveAllChanges();
Here's the additions to the MyDataEntities class:
public partial class MyDataEntities { /// <summary> /// ObjectContext HttpContext item key name. /// </summary> private const string HttpContextItemKey = "MyDataEntities"; /// <summary> /// Gets the ObjectContext for the current request. Creates a new one if one does not already exist or is disposed. /// </summary> /// <returns>The ObjectContext for the current request.</returns> public static MyDataEntities Current { get { var context = HttpContext.Current.Items[HttpContextItemKey] as MyDataEntities; if (null == context || context.IsDisposed) { context = new MyDataEntities(); HttpContext.Current.Items[HttpContextItemKey] = context; } return context; } } /// <summary> /// Gets or sets a value indicating whether this object has been disposed. /// </summary> public bool IsDisposed { get; private set; } /// <summary> /// Releases the resources used by the object context. /// </summary> /// <param name="disposing">true to release both managed and unmanaged resources, flase to realse only unmanaged resources.</param> protected override void Dispose(bool disposing) { if (disposing && !this.IsDisposed) { this.EndChangeTracking(EntityState.Unchanged); if ((HttpContext.Current.Items[HttpContextItemKey] as MyDataEntities) == this) { HttpContext.Current.Items.Remove(HttpContextItemKey); } } base.Dispose(disposing); this.IsDisposed = true; } }
This works for related entities as well e.g. entity.RelatedItems.Add(relatedEntity);

