如果没有数据上下文,如何保存Linq对象?

时间:2009-10-20 14:23:38

标签: c# linq linq-to-sql

我有一个Linq对象,我想对它进行更改并保存它,如下所示:

public void DoSomething(MyClass obj) {
  obj.MyProperty = "Changed!";
  MyDataContext dc = new MyDataContext();
  dc.GetTable<MyClass>().Attach(dc, true); // throws exception
  dc.SubmitChanges();
}

例外是:

System.InvalidOperationException: An entity can only be attached as modified without original state if it declares a version member or does not have an update check policy.

看起来我有几个选择:

  1. 在我的每个Linq课程中放置一个版本成员&amp;我需要以这种方式使用的表格(100+)。
  2. 找到最初创建对象的数据上下文,并使用它来提交更改。
  3. 在每个类中实现OnLoaded并保存此对象的副本,我可以将其传递给Attach()作为基线对象。
  4. 要进行并发检查;在附加之前加载DB版本并将其用作基线对象(不是!!!)
  5. 选项(2)似乎是最优雅的方法,特别是如果我能找到一种在创建对象时存储对数据上下文的引用的方法。但是 - 怎么样?

    还有其他想法吗?

    修改

    我试图遵循Jason Punyon的建议并在桌面上创建一个并发字段作为测试用例。我在dbml文件中的字段上设置了所有正确的属性(Time Stamp = true等),现在我有一个并发字段...和一个不同的错误:

    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.
    

    那么,如果不是现有的实体,我应该附加什么呢?如果我想要一个新记录,我会做一个InsertOnSubmit()!那么你应该如何使用Attach()?

    编辑 - 完全披露

    好的,我可以看到是时候全面披露为什么所有标准模式都不适合我。

    我一直试图通过隐藏“消费者”开发人员的DataContext来变得聪明并使我的界面更清晰。我通过创建基类

    来完成
    public class LinqedTable<T> where T : LinqedTable<T> {
      ...
    }
    

    ...我的每个表都有其生成版本的“另一半”声明如下:

    public partial class MyClass : LinqedTable<MyClass> {
    }
    

    现在LinqedTable有许多实用方法,尤其是:

    public static T Get(long ID) {
      // code to load the record with the given ID
      // so you can write things like:
      //   MyClass obj = MyClass.Get(myID);
      // instead of:
      //   MyClass obj = myDataContext.GetTable<MyClass>().Where(o => o.ID == myID).SingleOrDefault();
    }
    public static Table<T> GetTable() {
      // so you can write queries like:
      //   var q = MyClass.GetTable();
      // instead of:
      //   var q = myDataContext.GetTable<MyClass>();
    }
    

    当然,正如您可以想象的那样,这意味着LinqedTable必须能以某种方式访问​​DataContext。直到最近,我才通过在静态上下文中缓存 DataContext来实现这一目标。是的,“直到最近”,因为“最近”是我发现你不是真的应该坚持使用DataContext超过一个单位的工作,否则各种各样的小鬼开始从木制品出来。学过的知识。

    所以现在我知道我不能长时间坚持这个数据上下文...这就是为什么我开始尝试按需创建一个DataContext,只缓存在当前的LinqedTable实例上。这导致了新创建的DataContext与我的对象无关的问题,因为它“知道”它对创建它的DataContext不忠。

    有没有办法在创建或加载时将DataContext信息推送到LinqedTable上?

    这真的是一个装腔作势者。我绝对不想在我放入LinqedTable基类的所有这些便利函数中妥协,我需要能够在必要时放弃DataContext并在需要时继续使用它。

    还有其他想法吗?

6 个答案:

答案 0 :(得分:5)

使用LINQ to SQL进行更新是有趣的。

如果数据上下文消失了(在大多数情况下应该是这样),那么您将需要获取新的数据上下文,并运行查询以检索要更新的对象。在LINQ to SQL中,你必须检索一个对象来删除它,这是一个绝对的规则,而且你应该检索一个对象来更新它。有一些解决方法,但它们很丑陋,通常还有很多方法可以帮助你解决问题。所以,再次获取记录并完成它。

获得重新获取的对象后,使用包含更改的现有对象的内容对其进行更新。然后对新数据上下文执行SubmitChanges()。而已! LINQ to SQL将通过将记录中的每个值与原始(在重新获取的)记录中进行比较来生成相当严厉的乐观并发版本。如果在获取数据时更改了任何值,LINQ to SQL将引发并发异常。 (因此,您无需为版本控制或时间戳更改所有表。)

如果您对生成的更新语句有任何疑问,则必须打破SQL事件探查器并观察更新是否发送到数据库。这是一个好主意,直​​到你对生成的SQL有信心。

关于事务的最后一点注意事项 - 如果没有环境事务,数据上下文将为每个SubmitChanges()调用生成一个事务。如果您要更新多个项目并希望将它们作为一个事务运行,请确保为所有项目使用相同的数据上下文,并等待调用SubmitChanges(),直到您更新了所有对象内容。

如果这种交易方法不可行,那么请查看TransactionScope对象。这将是你的朋友。

答案 1 :(得分:2)

我认为2不是最好的选择。这听起来好像你要创建一个DataContext并在程序的整个生命周期中保持活着,这是一个坏主意。 DataContexts是轻量级对象,可在您需要时进行旋转。试图保持参考文献也可能会紧密结合你的程序区域,而不是将它们分开。

一次运行一百个ALTER TABLE语句,重新生成上下文并保持架构简单和分离是优雅的答案......

答案 2 :(得分:0)

  

找到最初创建对象的数据上下文并使用它来提交更改

你的datacontext去了哪里?为什么这么难找?你在任何时候都只使用一个吗?

  

那么,如果不是现有的实体,我应该附加什么呢?如果我想要一个新记录,我会做一个InsertOnSubmit()!那么你应该如何使用Attach()?

你应该附加一个代表现有记录的实例......但是没有被另一个datacontext加载 - 不能在同一个实例上有两个跟踪记录状态的上下文。如果您生成一个新实例(即克隆),那么您将会很高兴。

您可能需要查看this article及其更新和删除部分的并发模式。

答案 3 :(得分:0)

您可以重新连接到新的DataContext。在正常情况下,唯一阻止您这样做的是属性更改了EntitySet<T>EntityRef<T>类中发生的事件注册。要允许在上下文之间传输实体,首先必须通过删除这些事件注册将实体与DataContext分离,然后使用DataContext.Attach()方法重新连接到新上下文。

这是一个很好的example

答案 4 :(得分:0)

实体只能在没有原始状态的情况下附加,如果它声明版本成员”附加具有时间戳成员的权限时将出现错误(仅应该)如果实体具有没有“通过电线”旅行(阅读:已被序列化并再次反序列化)。如果您正在使用未使用WCF的本地测试应用程序或将导致实体序列化和反序列化的其他内容进行测试,那么它们仍将通过entitysets / entityrefs(associations / nav。属性)保留对原始datacontext的引用。

如果是这种情况,您可以通过在调用datacontext的.Attach方法之前在本地序列化和反序列化来解决它。 E.g:

internal static T CloneEntity<T>(T originalEntity)
{
    Type entityType = typeof(T);

    DataContractSerializer ser =
        new DataContractSerializer(entityType);

    using (MemoryStream ms = new MemoryStream())
    {
        ser.WriteObject(ms, originalEntity);
        ms.Position = 0;
        return (T)ser.ReadObject(ms);
    }
}

或者你可以通过将所有entitysets / entityrefs设置为null来分离它,但这更容易出错,所以虽然有点贵,但我只想在本地模拟n层行为时使用上面的DataContractSerializer方法... < / p>

(相关主题:http://social.msdn.microsoft.com/Forums/en-US/linqtosql/thread/eeeee9ae-fafb-4627-aa2e-e30570f637ba

答案 5 :(得分:0)

首先检索数据时,请在执行检索的上下文中关闭对象跟踪。这将阻止在原始上下文中跟踪对象状态。然后,在保存值时,附加到新上下文,刷新以从数据库设置对象的原始值,然后提交更改。当我测试它时,以下内容对我有用。

MyClass obj = null;
using (DataContext context = new DataContext())
{
    context.ObjectTrackingEnabled = false;
    obj = (from p in context.MyClasses
            where p.ID == someId
            select p).FirstOrDefault();
}

obj.Name += "test";

using (DataContext context2 = new ())
{
    context2.MyClasses.Attach(obj);
    context2.Refresh(System.Data.Linq.RefreshMode.KeepCurrentValues, obj);
    context2.SubmitChanges();
}