在DataContext.SubmitChanges()期间防止递归

时间:2009-09-14 11:32:20

标签: c# linq linq-to-sql

这有点夸张,我希望你会发现这个具有挑战性的问题和我一样有趣......:)

我有一个名为DataContext的{​​{1}}子类,其中我使用以下格式的代码覆盖了MyDataContext方法:

SubmitChanges()

到目前为止,这么好。但是,如果BeginTransaction(); // my own implementation IList<object> Updates = GetChangeSet().Updates; foreach (object obj in Updates) { MyClass mc = obj as MyClass; if (mc != null) mc.BeforeUpdate(); // virtual method in MyClass to allow pre-save processing } // This is followed by similar code for the Deletes and Inserts, then: base.SubmitChanges(); // Then do post-save processing... foreach (object obj in Updates) { MyClass mc = obj as MyClass; if (mc != null) mc.AfterUpdate(); // virtual method in MyClass to allow post-save processing } // similar code for Inserts and Deletes // ... CommitTransaction(); // obviously all enclosed in a try-catch block where the catch does a rollback 的实现在其MyClassSubmitChanges()方法中调用BeforeUpdate(),则会出现一些问题。现在我们有一个可能导致堆栈溢出的递归。

我想到解决这个问题的一种方法是在AfterUpdate()的开头有一个递归阻塞变量。但是如果保存被阻止该怎么办?我无法将其转换为新线程;调用线程可能要求SubmitChanges()调用是同步的,例如如果需要在保存后立即访问自动编号属性。

要考虑的另一个因素是,如果在预处理或后处理过程中更改了任何对象,我还需要他们的 SubmitChanges()BeforeSave()方法被召唤。

是否有一些聪明的教科书方式可以整齐而正确地完成这项工作?

3 个答案:

答案 0 :(得分:2)

  

我想到的一种解决方法   这是一个递归阻塞   变量在开头   的SubmitChanges()。但如果做什么该怎么办   保存被阻止了吗?

抛出 NotSupportedException 。你不应该支持在BeforeChanges上发生的另一个SubmitChanges ...正是你正在做的事情,允许在SubmitChanges被调用之前发生一些更改。

关于调用其BeforeUpdate的更新对象,您可以在原始列表中调用BeforeUpdate之后检查SubmitChanges之前是否有新的更新对象,并且直到没有额外更新的对象为止。

AfterUpdate也是如此,类似的事情是对内存中的对象进行更改...而不是将更多数据保存到数据库。

尝试在系统中的不同实体上添加SubmitChanges,必然会在系统中产生一些性能问题。

答案 1 :(得分:1)

我唯一的想法是在你工作的时候创造一种缓冲;存储您要保存的对象。

这样的事情:

class MyDataContext : DataContext
{
   private bool _WorkingFlag = false; // indicates whether we're currently saving

   private List<object> _UpdateBuffer = new List<object>();

   // ... other buffers here

   protected void BeginTransaction()
   {
      // implementation
   }

   protected void CommitTransaction()
   {
      // implementation
   }

   public override void SubmitChanges()
   {
      BeginTransaction();

      IList<object> updates = GetChangeSet().Updates;
      // also inserts and deletes
      if (_WorkingFlag)
      {
         _UpdateBuffer.AddRange(updates);
         // also inserts and deletes

         return;
      }

      _WorkingFlag = true;

      updates = updates.Concat(_UpdateBuffer).ToList(); // merge the updates with the buffer
      foreach (object obj in updates) // do the stuff here...
      {
         MyClass mc = obj as MyClass;
         if (mc != null)
            mc.BeforeUpdate(); // virtual method in MyClass to allow pre-save processing
      }
      _UpdateBuffer.Clear(); // clear the buffer

      // ... same for inserts and deletes ...
      base.SubmitChanges();

      // ... after submit, simply foreach ...

      CommitTransaction();
      _WorkingFlag = false;

      // of course all in try... catch, make sure _WorkingFlag is set back to false in the finally block
   }
}

我希望它能正常工作,我没有测试过。

答案 2 :(得分:0)

现在已经给出了答案信用,这是我实际实施解决方案的方式:

private bool _Busy = false;

public override void SubmitChanges(ConflictMode failureMode) {
  if (_Busy)
    return; // no action & no error; just let this SubmitChanges handle all nested submissions.
  try {
    _Busy = true;
    BeginTransaction();
    Dictionary<MyClass, bool> myUpdates = new Dictionary<MyClass, bool>();
    Dictionary<MyClass, bool> myInserts = new Dictionary<MyClass, bool>();
    Dictionary<MyClass, bool> myDeletes = new Dictionary<MyClass, bool>();

    SynchronizeChanges(myUpdates, GetChangeSet().Updates);
    SynchronizeChanges(myInserts, GetChangeSet().Inserts);
    SynchronizeChanges(myDeletes, GetChangeSet().Deletes);

    while (myInserts.Any(i => i.Value == false) || myUpdates.Any(u => u.Value == false) || myDeletes.Any(d => d.Value == false)) {
      List<MyClass> tmp = myInserts.Where(i => i.Value == false).Select(i => i.Key).ToList();
      foreach (MyClass mc in tmp) {
        mc.BeforeInsert();
        myInserts[lt] = true;
      }
      tmp = myUpdates.Where(u => u.Value == false).Select(u => u.Key).ToList();
      foreach (MyClass mc in tmp) {
        mc.BeforeUpdate();
        myInserts[lt] = true;
      }
      tmp = myDeletes.Where(d => d.Value == false).Select(d => d.Key).ToList();
      foreach (MyClass mc in tmp) {
        mc.BeforeDelete();
        myInserts[lt] = true;
      }
      // before calling base.SubmitChanges(), make sure that nothing else got changed:
      SynchronizeChanges(myUpdates, GetChangeSet().Updates);
      SynchronizeChanges(myInserts, GetChangeSet().Inserts);
      SynchronizeChanges(myDeletes, GetChangeSet().Deletes);
    }
    base.SubmitChanges(failureMode);
    // now the After- methods
    foreach (MyClass mc in mcInserts.Keys) {
      mc.AfterInsert();
    }
    foreach (MyClass mc in mcUpdates.Keys) {
      mc.AfterUpdate();
    }
    foreach (MyClass mc in mcDeletes.Keys) {
      mc.AfterDelete();
    }
    CommitTransaction();
  } catch {
    RollbackTransaction();
    throw;
  } finally {
    _Busy = false;
  }
  // now, just in case any of the After... functions triggered a change:
  if (GetChangeSet().Deletes.Count + GetChangeSet().Inserts.Count + GetChangeSet().Updates.Count > 0)
    SubmitChanges();
}

private void SynchronizeChanges(Dictionary<MyClass, bool> mcDict, IList<object> iList) {
  var q = iList.OfType<MyClass>().Where(i => !mcDict.ContainsKey(i));
  q.ToList().ForEach(i => mcDict[i] = false);
}