Link to home
Start Free TrialLog in
Avatar of jfrank79
jfrank79

asked on

LINQ Attach Error

I am attempting to save a record using Windows Forms when a "Save" button is clicked. My understanding is that it is best to use a new "datacontext" and attach the current record to it. I have tried everything I can (and others) can think of but nothing seems to work for me. My code is below. When I run this I get an error that states "An attempt has been made to Attach or Add an entity that is not new, perhaps having been loaded from another DataContext.  This is not supported." Any help is greatly appreciated.
private void btnSaveRecord_Click(object sender, EventArgs e)
{
	Quant.QuantDbDataContext db = new Quant.QuantDbDataContext();
	Quant.quantInfo currQi = (Quant.quantInfo)bsQuantInfo.Current;
 
	Quant.QuantDbDataContext db2 = new Quant.QuantDbDataContext();
	db2.DeferredLoadingEnabled = false;
	Quant.quantInfo prevQi = db2.quantInfos.Single(q => q.quantID == currQi.quantID);
	db2 = null;
 
	db.quantInfos.Attach(currQi,prevQi);
	db.SubmitChanges();
						
	btnSaveRecord.Enabled = false;
}

Open in new window

Avatar of novynov
novynov
Flag of United States of America image

Updating a "disconnected" object (like with an n-tier application) in Linq has some challenges due to the change tracking mechanisms and the default use of optimistic concurrency. In order to safely update an object, Linq needs to know the original state of an object and be able to detect what has changed. In an Attach()ed object, Linq doesn't have that info without some help. Note: this whole subject of Attach() is somewhat confusing, as evidenced by the chatter on the net.

That said, I notice that you are using LINQ in a Windows forms app. Is it safe to assume that you are retrieving the original object in the same form? If so, is there any reason you wouldn't just have one datacontext exist as a private member of your form? This context would be used for retrieve and update. If so, you wouldn't need to reattach your changed object to a different context - thus avoiding the problem.

If you must use a new datacontext (as happens in the case of a web service using linq), there are a number of approaches to doing updates:

- Add a timestamp field to the table and use the Update(obj, true) overload. This isn't always an option if your schema isn't controlled by you.
- Turn the UpdateCheck property on your column mappings to "Never." Basically, this turns off change tracking, and will "blast" all of the columns into the db. In this case, you call Attach(obj, true). Obviously, this method comes with risk, especially in a multi-user db.
- Maintain a copy of the original/unchanged object, attach it to your new datacontext using the Attach() overload and "play" the changes into it (e.g. origObj.PropName = changedObj.PropName).
- Retrieve the original object from the db with your new datacontext and play your changes into it.
-Detach the object from its original context (I believe this is only possible through serialization or some manual work), make your changes to this object, and then reattach it using Attach(changedObj, originalObj). It appears that you are trying to do something similar to this above. I believe the difference is that the changed object was not detached and the original object was retrieved from the same datacontext that you are updating against. I believe neither of these are supported. I'm still validating the details.

That said, while I still investigate the details on the last one and other possibilities, I thought I'd at least post this with some possible options that may quickly resolve your issue.

Also, here are some additional resources on the subject:

DataRetrieval and CUD Operations in N-tier Applications - http://msdn.microsoft.com/en-us/library/bb546187.aspx


Attach() if you have something detached - http://blogs.msdn.com/dinesh.kulkarni/archive/2007/10/08/attach-if-you-have-something-detached.aspx

How to attach object to a different datacontext - http://msmvps.com/blogs/omar/archive/2007/12/08/linq-to-sql-how-to-attach-object-to-a-different-data-context.aspx

A comment from the MSDN docs on Update() that may be of interest:

"Do not try to Attach an entity that has not been detached through serialization. Entities that have not been serialized still maintain associations with deferred loaders that can cause unexpected results if the entity becomes tracked by a second data context."

Please let me know if this helps.
Here's another post, this one regarding your specific error:

http://geekswithblogs.net/michelotti/archive/2007/12/25/117984.aspx

So, I did some experimentation trying to reproduce your exact scenario...and better understand the behavior of the 2 arg Attach() call.

Interestingly, if you have a single table with no FK relationships represented as associations in your dbml (note: they can still exist in the db - just not in your dbml), the approach you took does work (though it may not be supported - see previous refs). The link in my previous post gives one reason why and offers a solution - basically, a manual detach and re-attach, including "child" entities. However, I tried this in a number of scenarios, and it does not appear to work as advertised, at least not with 3.5 SP1.

Hope this helps. Let me know if you need more info.
Avatar of jfrank79
jfrank79

ASKER

I have tried most of the above solutions (timestamp, detach, etc...). I went as far as copying the updated record into a completely new detached object but still got the same error. I am curios about the "Update()" call as I have not seen this available. I was under the impression "Attach()" was the only way to update. Would be great if I was wrong.

I need to have a new datacontext because data can be changed outside of the form's datacontext (other users, background processes, etc...). I appreciate your help on this!
ASKER CERTIFIED SOLUTION
Avatar of novynov
novynov
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Something struck me that may help me understand better how to help find a solution. What is the source of the objects in bsQuantInfo.Current? How does it get populated?
 
I went the route of grabbing the latest object and putting the changes into the existing object. This worked when I was just changing the top-level. In my code below if I try to update just the quantName it works but if I try to update any child objects it breaks with the same error, so I'm guessing my problem has to do with child objects. I've got most of the code commented out just to focus on which area is breaking. Your help is greatly appreciated.
private void btnSaveRecord_Click(object sender, EventArgs e)
{
        Quant.QuantDbDataContext db = new Quant.QuantDbDataContext();
        Quant.quantInfo currQi = (Quant.quantInfo)bsQuantInfo.Current;
	Quant.quantInfo newQi = db.quantInfos.Single(q => q.quantID == currQi.quantID);
	newQi.quantCatVarLinks = currQi.quantCatVarLinks;
	//newQi.quantDailyDatas = currQi.quantDailyDatas;
	//newQi.quantID = currQi.quantID;
	//newQi.quantMonDatas = currQi.quantMonDatas;
	//newQi.quantMonthlyExposures = currQi.quantMonthlyExposures;
	newQi.quantName = currQi.quantName;
	//newQi.quantOtherIdentifier = currQi.quantOtherIdentifier;
	//newQi.quantPerTracID = currQi.quantPerTracID;
	//newQi.quantStataName = currQi.quantStataName;
	//newQi.quantType = currQi.quantType;
	//newQi.quantVarUpdates = currQi.quantVarUpdates;
	//newQi.quantWeeklyDatas = currQi.quantWeeklyDatas;
	db.SubmitChanges();
 
	btnSaveRecord.Enabled = false;
}

Open in new window

I appeared to have solved the problem (or at least found the culprit). It is not all child members that cause the problem, just the one I chose to test. I think I know why and am now finally able to proceed. Thanks for your help!