如何将LINQ-to-SQL数据对象从DataContext的跟踪机制中分离?

9
在我询问了这个问题并了解了Table<T>.Attach()方法的工作方式之后,我有另一个问题。
如何将LINQ-to-SQL数据对象从DataContext的状态跟踪机制中分离?基本上,我想拉出一条记录并更改记录上的数据。但是,当我在同一个DataContext实例上调用SubmitChanges()时,除非我已经显式地调用Attach(),否则我不希望记录被更新。如何实现这一点?
5个回答

9

如果你要使用LINQ to SQL,我强烈建议你改变设计以适应LINQ to SQL提交所有已修改实体的行为。根据我的经验,试图规避此功能只会带来麻烦。


6
+1,因为通常这是很好的建议(不仅适用于LINQ to SQL)。然而,这并没有真正回答问题。 - smartcaveman

7

从这个网站了解如何分离linq对象,将此方法添加到要分离的对象中:

public void Detach()
{
    GetType().GetMethod("Initialize", BindingFlags.Instance | BindingFlags.NonPublic).Invoke(this, null);
}

我不得不使用这个hack,因为我正在使用一个基于Pete Montgomery的博客文章https://petemontgomery.wordpress.com/2008/08/07/caching-the-results-of-linq-queries/的自定义缓存库来缓存LINQ到SQL实体。我有多个线程尝试序列化相同的缓存实体,而DataContractSerializer会触发遍历实体成员,从而导致同时访问DataContext,这是不安全的。所以我正确地隔离了我的DataContext并且没有提交更改,但我仍然受到了影响。这个手动Detach方法是最简单的修复方法。 - Jordan Rieger
重要提示:这需要使用 SQLMetal 生成模型对象,并在此过程中使用 /serialization 选项:https://learn.microsoft.com/en-us/dotnet/framework/data/adonet/sql/linq/serialization - Protector one

2

很遗憾,您无法直接将实体从先前的DataContext中分离,除非对实体进行序列化和反序列化。

为了实现您的目的,您可以创建一个从数据库中检索出来的对象的副本并与其一起工作。在这种情况下,原始对象不会受到影响。当更新数据库的时候,您只需将副本附加到您的DataContext即可。


1
我可能错了,但我认为这不会起作用。如果您在上下文中拉取一个对象,克隆它,并尝试将克隆体附加到与原始查询相同的上下文,则会出现错误,指示您无法附加已经在上下文中的实体。 - shaunmartin
好的,你说得对。这将取决于解决方案,但如果克隆对象的生命周期超过DataContext对象,则它将起作用。 - Andrei
1
如果没有,那么您可以将值从克隆中复制回原始数据。 - Andrei

0

我使用Serialize/Deserialize克隆了从DataContext查询的对象,并将新对象提交到同一个DataContext,没有出现任何问题。如果有任何附加实体,则需要在提交克隆之前使用其ID进行重新查询。

/// <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;
            }
        }
    }
}

这将导致一个 DuplicateKeyException 异常,并且错误信息为 不能添加已经在使用中的实体键。 - Kirk Broadhurst
这就是为什么我说任何关联实体都必须被“重新查询”。 - Damien

0
在许多情况下,最简单的解决方案是将对象序列化/反序列化以进行简单的克隆。您使用哪种序列化/克隆方法取决于您。这个问题提供了很多相关建议。
我喜欢使用Newtonsoft JSON.NET进行序列化,因为它非常易于使用,要求最少(例如,不需要编译器属性),而且我已经在我的项目中使用它来处理其他东西。根据您的用例(例如,从LINQ / SQL实体中分离出来以在UI模型中使用),您可能希望清除数据库ID。一种简单的方法是向JSON.NET传递一个自定义的DefaultContractResolver类,该类将排除ID属性:
    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();
        }
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接