9

After asking this question, where I was informed of how the Table<T>.Attach() method works, I have another question.

How do you detach a LINQ-to-SQL data object from the DataContext's state tracking mechanism? Basically, I want to pull a record and change the data on the record. But, when I call SubmitChanges() on the same DataContext instance, I do not want the record to be updated unless I have explicitly called Attach(). How is this accomplished?

Community
  • 1
  • 1
smartcaveman
  • 41,281
  • 29
  • 127
  • 212

5 Answers5

9

I strongly recommend that if you're going to use LINQ to SQL, you should change your design to accommodate LINQ to SQL's behavior of submitting changes on all attached modified entities. In my experience, attempting to work around this feature will only lead to pain.

shaunmartin
  • 3,849
  • 2
  • 25
  • 26
  • 6
    +1, because in general this is great advice (and not just with LINQ to SQL). However, this doesn't actually answer the question. – smartcaveman Mar 10 '11 at 04:01
7

From this site explaining how to detach a linq object add this method to the object you want to detach:

public void Detach()
{
    GetType().GetMethod("Initialize", BindingFlags.Instance | BindingFlags.NonPublic).Invoke(this, null);
}
user281806
  • 1,020
  • 9
  • 14
  • I had to use this hack because I was using a custom-built caching library for LINQ to SQL entities, based on Pete Montgomery's blog post at https://petemontgomery.wordpress.com/2008/08/07/caching-the-results-of-linq-queries/. I had multiple threads trying to serialize the same cached entity, and the DataContractSerializer was triggering a walk through the entity members that caused simultaneous access of the DataContext, which is not thread safe. So I was properly isolating my DataContext and not submitting changes, but I still got burned. This manual Detach method was the simplest fix. – Jordan Rieger Jul 15 '16 at 17:50
  • Big caveat: this requires that you're using SQLMetal for generating model objects, and use the `/serialization` option when doing so: https://learn.microsoft.com/en-us/dotnet/framework/data/adonet/sql/linq/serialization – Protector one Aug 13 '21 at 13:04
2

Unfortunately, you cannot explicitly detach entities from previous DataContext, without serializing and deserializing the entities.

What you can do for your purpose is to create a copy of the object you pulled out of the DB and work with the copy. In this case your original object is untouched. When the time comes to update the DB, you can simply attach the copy to your DataContext.

Andrei
  • 4,122
  • 3
  • 22
  • 24
  • 1
    I could be wrong, but I don't think this will work. If you pull an object on a context, clone it, and try to attach the clone *to the same context the original was queried with*, you'll get an error saying you can't attach an entity that is already on the context. – shaunmartin Mar 09 '11 at 19:31
  • Well, you are right. it would depend on the solution, though it will work if the clone outlives the DataContext object. – Andrei Mar 10 '11 at 15:07
  • 1
    If not, then you can copy the values back into the original from the clone. – Andrei Mar 10 '11 at 15:09
0

The simplest solution in many cases will be to serialize/deserialze the object to do an easy clone of it. Which serialize/clone method you use is up to you. This question has a bunch of suggestions in that regard.

I like to use Newtonsoft JSON.NET for serialization because it is super-easy to use, has minimal requirements (e.g. no compiler attributes required) and I already use it for other stuff in my projects. Depending on your use case (e.g. detaching a LINQ/SQL entity to use within a UI model) you may want to wipe off the database IDs. An easy way to do that is to pass a custom DefaultContractResolver class to JSON.NET that will exclude the ID properties:

    return JsonConvert.SerializeObject(oModel, new JsonSerializerSettings() { ContractResolver = new DNSConfigurationModel.DNSConfigSerializer() });

    /// <summary>
    /// Helper class to ensure that we do not serialize the domainAdvancedDNS child objects 
    /// (we will create our own child collections for serialization). We also suppress serialization of the key ID's.
    /// </summary>
    public class DNSConfigSerializer : DefaultContractResolver
    {
        protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
        {
            IList<JsonProperty> properties = base.CreateProperties(type, memberSerialization);
            return (from p in properties
                    where p.PropertyName != "DomainAdvancedDNS" &&
                          p.PropertyName != "domainAdvancedDNSSRVs" &&
                          !(p.DeclaringType.Name == "DomainAdvancedDN" && p.PropertyName == "domainAdvancedDNSConfigID") &&
                          p.PropertyName != "DomainAdvancedDNSID" &&
                          p.PropertyName != "domainAdvancedDNSSRVID"
                    select p).ToList();
        }
    }
Community
  • 1
  • 1
Jordan Rieger
  • 3,025
  • 3
  • 30
  • 50
0

I have cloned objects queried from a DataContext using Serialize/Deserialize and submitted the new object to the same DataContext without problems. If there are any attached entities, they will need to be requeried using the IDs before submitting the clone.

/// <summary>Used for serializing and de-serializing objects.</summary>
public static class Serializer
{
    /// <summary>Clones an object.</summary>
    /// <typeparam name="T">The type of object to be cloned.</typeparam>
    /// <param name="source">The object to be cloned.</param>
    /// <returns>A clone of the specified object.</returns>
    public static T Clone<T>(T source)
    {
        return Deserialize<T>(Serialize(source));
    }

    /// <summary>Serializes an object as an XML string.</summary>
    /// <param name="value">A System.Object representing the object to be serialized.</param>
    /// <returns>A System.String representing an XML representation of the specified object.</returns>
    public static string Serialize(object value)
    {
        if (value.GetType() == typeof(string))
        {
            return value.ToString();
        }

        StringWriter stringWriter = new StringWriter();
        using (XmlWriter writer = XmlWriter.Create(stringWriter))
        {
            DataContractSerializer serializer = new DataContractSerializer(value.GetType());
            serializer.WriteObject(writer, value);
        }

        return stringWriter.ToString();
    }

    /// <summary>Creates an object from an XML representation of the object.</summary>
    /// <typeparam name="T">The type of object to be created.</typeparam>
    /// <param name="serializedValue">A System.String representing an XML representation of an object.</param>
    /// <returns>A new object.</returns>
    public static T Deserialize<T>(string serializedValue)
    {
        Type type = typeof(T);
        using (StringReader stringReader = new StringReader(serializedValue))
        {
            using (XmlReader reader = XmlReader.Create(stringReader))
            {
                DataContractSerializer serializer = new DataContractSerializer(type);
                object deserializedValue = serializer.ReadObject(reader);
                return (T)deserializedValue;
            }
        }
    }
}
Damien
  • 1,463
  • 1
  • 18
  • 30