Steve Michelotti

A .NET Developer's Toolbox

  Home  |   Contact  |   Syndication    |   Login
  201 Posts | 0 Stories | 1110 Comments | 51 Trackbacks

News

View Steve Michelotti's profile on LinkedIn

profile for Steve Michelotti at Stack Overflow, Q&A for professional and enthusiast programmers




Google My Blog

What I'm Reading:

Shelfari: Book reviews on your book blog

Tag Cloud


Archives

Post Categories

Code

Publications

This exception using the Linq Attach() method is somewhat perplexing at first:

System.NotSupportedException: 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.

This blog post here *sort of* pointed me in the right direction. But I found the *WHY* confusing and I found the example confusing.  The following is my implementation of the suggested solution from the previous post.  First, consider the following diagram:

The Contact is the primary object and it has a collection of Addresses.  The State and AddressType tables are simply master tables for reference data. Next we create a public method to handle database modifications:

   1:  public static void SaveContact(Contact contact)
   2:  {
   3:      using (PimDataContext dataContext = CreateDataContext())
   4:      {
   5:          if (contact.ContactID == 0)
   6:          {
   7:              dataContext.Contacts.InsertOnSubmit(contact);
   8:          }
   9:          else
  10:          {            
  11:              dataContext.Contacts.Attach(contact, true);
  12:          }
  13:          dataContext.SubmitChanges();
  14:      }
  15:  }

This code works fine for inserts of a Contact with or without any addresses in its child list of addresses.  However, it only works for updates if the contact does not have any child addresses.  If it does have addresses, then it throws the dreaded: "System.NotSupportedException: 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."  When I blindly followed the example from the previously referenced post, I essentially set everything in the Contact object to the default.  For example, this._Addresses = default(EntitySet<Address>);.  But this doesn't do me much good because if I actually made a modification to any addresses, then those changes are now lost as I'm setting the collection of Addresses back to a default empty EntitySet reference.  Additionally, I found myself asking the question, "WHICH entity is it complaining about?" Everything works fine when it's a stand-alone Contact object so it didn't seem feasible for it to be complaining about the Contact object. So I concluded it must be *something* in the Address object - but what?

A close examination of the generated code for the Address object showed that their were actually 3 EntityRef<> members - Contact, AddressType, and State.  So it seemed it was actually trying to attach my AddressType and State entity (which was never my intent).  Using that information, I found I could make everything work by setting just the EntityRef objects back to the default reference:

   1:  public partial class Contact
   2:  {
   3:      public void Detach()
   4:      {
   5:          foreach (Address address in this.Addresses)
   6:          {
   7:              address.Detach();
   8:          }
   9:      }
  10:  }
  11:   
  12:  public partial class Address
  13:  {
  14:      public void Detach()
  15:      {
  16:          this._AddressType = default(EntityRef<AddressType>);
  17:          this._State = default(EntityRef<State>);
  18:      }
  19:  }

This allowed my calling code to now just look like this and everything now worked perfectly:

   1:  public static void SaveContact(Contact contact)
   2:  {
   3:      using (PimDataContext dataContext = CreateDataContext())
   4:      {
   5:          if (contact.ContactID == 0)
   6:          {
   7:              dataContext.Contacts.InsertOnSubmit(contact);
   8:          }
   9:          else
  10:          {
  11:              contact.Detach();
  12:              
  13:              dataContext.Contacts.Attach(contact, true);
  14:              dataContext.Addresses.AttachAll(contact.Addresses, true);
  15:          }
  16:          dataContext.SubmitChanges();
  17:      }
  18:  }

Notice on line 11 I am calling the new Detach() method.  Also notice that I'm line 14, I am now explicitly calling the AttachAll() method for the collection of Addresses.

Looking at the final solution, it all now seems simple. But the troubleshooting that went in to getting there was not simple. While trivial, having the write Detach() methods for all my entity objects is not particularly appealing. Using Linq with stored procedures and explicit function calls would have eliminated much of the mystery as to what was going on behind the scenes trying to figure out why this exception was being thrown (though obviously more work writing stored procedures). These are the types of things that, so far, have me concluding two things: 1) Linq is extremely flexible and powerful, and 2) I still prefer using Linq with my own Stored Procedures.

Update: check out this post here for an example implementation that is even simplier and does not require Detach() methods at all.

posted on Tuesday, December 25, 2007 2:20 PM

Feedback

# re: Linq - Attach an entity that is not new, perhaps having been loaded from another DataContext. 12/26/2007 9:05 PM Christian
Hi!
And what if you changes the state (this is a change to a address, too!) ? If you save the customer objekt this change will get lost, right?

# re: Linq - Attach an entity that is not new, perhaps having been loaded from another DataContext. 12/26/2007 9:59 PM Steve
The above code works fine whether the change you are making is to the Customer object, one of its Address objects or both.

# re: Linq - Attach an entity that is not new, perhaps having been loaded from another DataContext. 1/10/2008 12:02 PM Fabricio
I'm facing the very same issue, but the code above has not solved my problem... I don't know why, but I'm still getting the same error when I try to attach an object got from another context.

# re: Linq - Attach an entity that is not new, perhaps having been loaded from another DataContext. 1/25/2008 12:44 PM Rob
Is this how it's actually meant to work? Dtaching everything seems so cumbersome and not particularly MS. Is it still the best way? I can't find any other references to this technique specifically for child entities. Has you found a better solution other than to remove relationships? :-)

Rob

# re: Linq - Attach an entity that is not new, perhaps having been loaded from another DataContext. 1/25/2008 2:43 PM Steve
Rob - check out my post here for a solution that does not require Detach(): http://geekswithblogs.net/michelotti/archive/2007/12/27/118022.aspx.

Additionally, you can click on the association "line" in the data context diagram and delete it.

# re: Linq - Attach an entity that is not new, perhaps having been loaded from another DataContext. 1/26/2008 2:02 AM Rob
Hi Steve - thanks, I've read over that - but that then removes the relationship so that I can't access the child entity which I need. I could create another DBML file which has no relationships and use that datacontext, but that removes the domain model that I'm trying to build.

# re: Linq - Attach an entity that is not new, perhaps having been loaded from another DataContext. 1/27/2008 1:50 PM Steve
Rob - It only removed the relationship between the reference data - the relationship between the Contact and the child Addresses still exists. Additionally, check out this post: http://geekswithblogs.net/michelotti/archive/2007/12/30/118076.aspx.

Hard for me to see what's going on without looking at the code. Feel free to email it to me if you want.

# re: Linq - Attach an entity that is not new, perhaps having been loaded from another DataContext. 1/7/2009 10:11 AM John Thompson
Hi. I think I am missing something here. I got this error message because I was trying to do something mind boglingly simple - to update a record!

LINQ goes to all the lengths to create nice diagrams and add associations, but when push comes to shove LINQ simple does not seem to work.

I'm struggling to do the simple stuff, what happens when I get to the complex requirements?

# re: Linq - Attach an entity that is not new, perhaps having been loaded from another DataContext. 3/22/2009 11:58 PM nico
How about a helper:

public static void Detach<T>(T entity)
{
Type t = entity.GetType();

System.Reflection.PropertyInfo[] properties = t.GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);

foreach (var property in properties)
{
string name = property.Name;

if (property.PropertyType.IsGenericType &&
property.PropertyType.GetGenericTypeDefinition() == typeof(EntitySet<>))
{
property.SetValue(entity, null, null);
}
}

System.Reflection.FieldInfo[] fields = t.GetFields(System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);

foreach (var field in fields)
{
string name = field.Name;

if (field.FieldType.IsGenericType &&
field.FieldType.GetGenericTypeDefinition() == typeof(EntityRef<>))
{
field.SetValue(entity, null);
}
}

System.Reflection.EventInfo eventPropertyChanged = t.GetEvent("PropertyChanged");
System.Reflection.EventInfo eventPropertyChanging = t.GetEvent("PropertyChanging");

if (eventPropertyChanged != null)
{
eventPropertyChanged.RemoveEventHandler(entity, null);
}

if (eventPropertyChanging != null)
{
eventPropertyChanging.RemoveEventHandler(entity, null);
}
}

# re: Linq - Attach an entity that is not new, perhaps having been loaded from another DataContext. 3/27/2009 6:56 AM Sam L
In my case I'm extending my autocreated Linq objects and using it in my application, saving in session, etc, so I only want to have one version of each Linq created object in my application. i.e. I don't want to have one set of dbml files for updates without relationships and another for selects with relationships. If I do attempt to take my select version I've been passing around in sessions et al with relationships, then attempt to cast to the update version, it fails.

So in short, the methodology discussed in this post works better for me than the simpler idea discussed in the following be 'mindful of your datacontext' post.

Having said that, it's a pain to write these detach methods, so I like the idea of the helper function in the comment above. In fact, I was just going to attempt to put together myself, when I discovered that you can simply call initialise the Initialize() method of the object before an attach, which creates default instances of all objects related by a foreign key, like this:

contact.Initialize();
dataContext.Contacts.Attach(contact, true);

Nice & simple. Obviously this will only update that object though & not any related objects, but that works fine for me.

Hope that helps someone.

# re: Linq - Attach an entity that is not new, perhaps having been loaded from another DataContext. 6/19/2009 2:38 PM Waleed Fahmy
ref other blog Linq Table Attach(), Thanks for your response

As you recommended I tried that but got the error below

"Cannot access a disposed object.
Object name: 'DataContext accessed after Dispose"

at the line
foreach (Address address in this.Addresses)

Basically the Addresses is not there, on the immediate window when trying this.Addresses.Count I get same error.

I do not have State or AddressType tables. I just need to detach the Addresses table?


# re: Linq - Attach an entity that is not new, perhaps having been loaded from another DataContext. 6/19/2009 2:52 PM Steve
@Waleed - The "Cannot access a disposed object" exception that you're getting is something entirely different. I'd have to see the entire code to diagnose this but it sounds like you're running intot he classic problem of trying to reference a child object from an object that was loaded for a data context that was most likely disposed from a "using" block from a previous methods. DataLoadOptions can fix this. I cover this very thing one of my presentations if you want to have a look: http://www.screencast.com/users/smichelotti/folders/Default/media/28292ba7-26ae-4d04-8c87-776cd6f6b57f

# re: Linq - Attach an entity that is not new, perhaps having been loaded from another DataContext. 6/19/2009 3:09 PM Waleed Fahmy
I got a working solution from the link http://community.omnicom.no/blogs/lea/archive/2008/04/25/updating-objects-with-linq-on-a-different-datacontext-instance.aspx

Basically set ObjectTrackingEnabled = false; when loading from the dataContext

public static TblContact GetContact(int contactID)
{
using (ContactsDataContext dataContext = new ContactsDataContext())
{
dataContext.ObjectTrackingEnabled = false;
return dataContext.GetContactById(contactID).Single();
}
}

I am not sure what the setting implies or what effect in the future will have. Another not recommended solution is to use a Singleton pattern to keep the DataContext. This worked for me as well but not sure why not recommended.

The bottom line I am sick of all of that, and need a recommedation either to continue with LINQ to SQL or just use classic ADO.Net access What do you think? What am I saving here some extra typing?!!

# re: Linq - Attach an entity that is not new, perhaps having been loaded from another DataContext. 6/19/2009 3:18 PM Steve
@Waleed - It still sounds to me like something else is not quite right is your code. You've shown me the code for the get below but wasn't your exception happening inside the save method? If so, I need to see that whole method too. It still sounds like you should be using DataLoadOptions in your "get" method.

You're saving more than just typing by using Linq2Sql (or any ORM for that matter) - but let's not forget, even saving typing is a noble goal. :) But you're also leveraging features of Linq2Sql like auto-mapper and auto-generated queries if you're not using sprocs.

I recommend having a look at the webcast in the link I provided above as it covers several of the scenarios that you're talking about.

# re: Linq - Attach an entity that is not new, perhaps having been loaded from another DataContext. 6/19/2009 5:33 PM Waleed Fahmy
Hi, thanks for the video link. I was able to get it working. The code I am using is exactly a copy and paste from your code here and the other page. The part the fixed the issue is the data load option below

DataLoadOptions options = new DataLoadOptions();
options.LoadWith<TblContact>(c => c.TblAddresses);
dataContext.LoadOptions = options;
return dataContext.GetContactById(contactID).Single();

In the GetContact method. I did not need the detach because I do not have the State and AddressType tables.

In your video I noticed that you did not use the Attach method with the sp option. Why is that?

# re: Linq - Attach an entity that is not new, perhaps having been loaded from another DataContext. 6/19/2009 5:38 PM Waleed Fahmy
Forgot to ask you where I can download the code in the webcast?

Thanks

# re: Linq - Attach an entity that is not new, perhaps having been loaded from another DataContext. 6/19/2009 5:42 PM Steve
@Waleed - The Attach() is not required with stored procedures because they are based in making function calls to invoke the stored procedures. The Attach() is required when you want to "add" the object to your data context so that, whenever SubmitChanges() is called, it will auto-generate the sql necessary for any entities that have been attached.

You can download the code from the web cast here: http://code.msdn.microsoft.com/Release/ProjectReleases.aspx?ProjectName=michelotti&ReleaseId=938.

# re: Linq - Attach an entity that is not new, perhaps having been loaded from another DataContext. 9/14/2009 3:24 PM Jerome Vernon
This was bugging me as well. I did some searching and found a lot of confusion and some sloppy work-arounds regarding detached entities. I then found a nice solution on codeplex that has solved the immediate issue and has greatly extended linq2sql functionality. Its a small class thats really easy to implement and forms an EntityBase for your projects dbml's.

Here is the offical description and link.
LINQ to SQL Entity Base is a simple base class that is primarily designed to support LINQ to SQL in a disconnected way, which is one of the shortcomings of the LINQ to SQL technology at the present time. This is highly useful in an n-Tier, distributed or ASP.NET environment where disconnected functionality is relavent.

http://linq2sqleb.codeplex.com/

Hope this helps anyone who is having similar.

Jerome Vernon

# re: Linq - Attach an entity that is not new, perhaps having been loaded from another DataContext. 7/16/2010 4:15 PM Marty
Oh and if you're stupid like me, make sure your datacontext is not static, else this error is likely

# re: Linq - Attach an entity that is not new, perhaps having been loaded from another DataContext. 10/4/2010 6:11 PM Eduardo Souto
Hello I implement a different solution and it work fine...

Here is my code:

Partial Public Class Comentarios
Private Shared dc As New ZonaCampingDataDataContext

Public Shared Sub Insert(ByVal obj As Comentarios)
With dc
.Comentarios.InsertOnSubmit(obj)
.SubmitChanges()
End With
End Sub

Public Shared Function [Select]() As IEnumerable(Of Comentarios)
With dc
Return .Comentarios.OrderBy(Function(e) e.com_CreationDate)
End With
End Function

Public Shared Function SelectByEstado(ByVal active As Boolean) As IEnumerable(Of Comentarios)
With dc
Return .Comentarios.Where(Function(c) c.com_Active = Convert.ToInt32(active))
End With
End Function

Public Shared Function GetById(ByVal com_Id As Integer) As Comentarios
With dc
Return .Comentarios.Where(Function(c) c.com_Id = com_Id).FirstOrDefault
End With
End Function

Public Shared Sub Update(ByVal modifiedObj As Comentarios)
With dc
Dim originalObj As Comentarios = .Comentarios.Single(Function(c) c.com_Id = modifiedObj.com_Id)
.Refresh(Data.Linq.RefreshMode.KeepChanges, modifiedObj)
.SubmitChanges(Data.Linq.ConflictMode.ContinueOnConflict)
End With
End Sub

Public Shared Sub Delete(ByVal objToDelete As Comentarios)
With dc
.Comentarios.DeleteOnSubmit(objToDelete)
.SubmitChanges()
End With
End Sub
End Class

# re: Linq - Attach an entity that is not new, perhaps having been loaded from another DataContext. 10/22/2010 3:21 AM Shahzad Ali
Error List & Solutions:
1- While using Attach and AttachAll methods.
Error : Either update Check or Add Rowversion to Update.
Solution :
1- Make all fields Update Check ==> Never on dbml
OR
2- Add Timestamp on tables.(Parent and Child)

For Applying the 2nd Solution On Update Code and Delete Code Error will be there like that
Error : DataContext not supported.
Solution : Load record from db on Temp and then assign in Update and Delete case.

# re: Linq - Attach an entity that is not new, perhaps having been loaded from another DataContext. 10/22/2010 3:41 AM Shahzad Ali
protected void Button1_Click(object sender, EventArgs e)
{
EntitySet<RolePage> erp = new EntitySet<RolePage>();
Role objRole = new Role();
foreach (DataGridItem item in this.dgPages.Items)
{
RolePage rp = new RolePage();
if (((CheckBox)item.Cells[2].FindControl("ckbSelect")).Checked == true && ((Label)item.Cells[2].FindControl("lblIsInuse")).Text == "0")
{
rp.RoleId = roleId;
rp.PageId = Convert.ToInt32(item.Cells[0].Text);
rp.IsNew = true;
rp.IsDeleted = false;
rp.CreatedOn = DateTime.Now;
rp.UpdatedOn = DateTime.Now;
erp.Add(rp);
}
else if (((CheckBox)item.Cells[2].FindControl("ckbSelect")).Checked == true && ((Label)item.Cells[2].FindControl("lblIsInuse")).Text == "1")
{
RolePage objTemp = new RolePage();
objTemp = new Helper().GetRolePagebyID(roleId, Convert.ToInt32(item.Cells[0].Text));
rp.RoleId = objTemp.RoleId;
rp.PageId = objTemp.PageId;
rp.RolePageId = objTemp.RolePageId;
rp.CreatedOn = objTemp.CreatedOn;
rp.Timestamp = objTemp.Timestamp;
//rp.RoleId = roleId;
//rp.PageId = Convert.ToInt32(item.Cells[0].Text);
//rp.RolePageId =Convert.ToInt32(((Label)item.Cells[2].FindControl("lblRolePageId")).Text.Trim());
rp.IsNew = false;
rp.IsDeleted = false;
rp.UpdatedOn = DateTime.Now;
erp.Add(rp);
}
else if (((CheckBox)item.Cells[2].FindControl("ckbSelect")).Checked == false && ((Label)item.Cells[2].FindControl("lblIsInuse")).Text == "1") // Adjust Delete yes
{
RolePage objTemp = new RolePage();
objTemp = new Helper().GetRolePagebyID(roleId, Convert.ToInt32(item.Cells[0].Text));

rp.RoleId = objTemp.RoleId;
rp.PageId = objTemp.PageId;
rp.RolePageId = objTemp.RolePageId;
rp.CreatedOn = objTemp.CreatedOn;
rp.UpdatedOn = objTemp.UpdatedOn;
rp.Timestamp = objTemp.Timestamp;
//rp.RoleId = roleId;
//rp.PageId = Convert.ToInt32(item.Cells[0].Text);
//rp.RolePageId =Convert.ToInt32(((Label)item.Cells[2].FindControl("lblRolePageId")).Text.Trim());
rp.IsNew = false;
rp.IsDeleted = true;
erp.Add(rp);
}

}

if (roleId != 0)
{
objRole = new Helper().GetRolebyID(roleId);
}

objRole.RoleId = roleId;
objRole.Name = txtName.Text.Trim();
objRole.RolePages.Assign(erp);

msg.Text = new Helper().Save(objRole);
//new Helper().Save(objRole);
//PopulatePages();
//FillRolePages();
// Response.Redirect("RoleList.aspx");

}

# re: Linq - Attach an entity that is not new, perhaps having been loaded from another DataContext. 10/22/2010 3:42 AM Shahzad Ali
public string Save(Role objRole)
{
string message = string.Empty;
System.Data.Common.DbTransaction trans = null;
try
{
Objdb.Connection.Open();
trans = Objdb.Connection.BeginTransaction();
Objdb.Transaction = trans;
if (objRole.RoleId == 0)
{
Objdb.Roles.InsertOnSubmit(objRole);
}
else
{
Objdb.Roles.Attach(objRole, true);
Objdb.RolePages.AttachAll(objRole.RolePages.Where(a => a.IsNew == false && a.IsDeleted == false), true);
Objdb.RolePages.InsertAllOnSubmit(objRole.RolePages.Where(a => a.IsNew == true && a.IsDeleted == false));
Objdb.RolePages.DeleteAllOnSubmit(objRole.RolePages.Where(a => a.IsNew == false && a.IsDeleted == true));
}
Objdb.SubmitChanges();
trans.Commit();
message = "Record saved successfully.";
}
catch (Exception ex)
{
trans.Rollback();
message = "Error : " + ex.Message;
}
return message;
}

# re: Linq - Attach an entity that is not new, perhaps having been loaded from another DataContext. 10/22/2010 3:45 AM Shahzad Ali
using System;
using System.Data;
using System.Configuration;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Linq;

/// <summary>
/// Summary description for RolePage
/// </summary>
public partial class RolePage
{
public bool IsNew
{
//get
//{
// return this.VERSIONSTAMP==null && !this.IsDeleted;
//}
get;
set;
}
public bool IsDeleted
{ get; set; }
}


# re: Linq - Attach an entity that is not new, perhaps having been loaded from another DataContext. 2/9/2011 2:45 PM Mike
Setting DataContext.DeferredLoadingEnabled = false; worked for me without having to use Detach().

# re: Linq - Attach an entity that is not new, perhaps having been loaded from another DataContext. 10/10/2012 12:32 AM Peter Hepp
I also had this issue with "Attach an entity that is not new, perhaps having been loaded from another DataContext". After pulling my hair out for a few hours I worked out that the problem was i was calling dataContext.clearcache() in an event handler before the entity had a chance to get submitted.

Post A Comment
Title:
Name:
Email:
Comment:
Verification: