Предотвращение рекурсии во время DataContext.SubmitChanges () - PullRequest
1 голос
/ 14 сентября 2009

Это немного проблематично, и я надеюсь, что вы найдете эту сложную проблему такой же интересной, как и я ...:)

У меня есть подкласс DataContext, называемый 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

Пока все хорошо. Но есть небольшая проблема, которая возникает, если реализация MyClass вызывает SubmitChanges() в своем методе BeforeUpdate() или AfterUpdate(). Теперь у нас есть рекурсия, которая может привести к переполнению стека.

Один из способов обойти это - иметь переменную, блокирующую рекурсию, в начале SubmitChanges(). Но что делать, если сохранение заблокировано? Я не могу раскрутить это в новую ветку; вызывающий поток может потребовать, чтобы вызов SubmitChanges() был синхронным, например, если ему нужен доступ к свойству авто-номера сразу после сохранения.

Дополнительный фактор, который необходимо учитывать, заключается в том, что если какие-либо объекты изменяются в ходе предварительной или последующей обработки, я также хочу, чтобы были вызваны их BeforeSave() и AfterSave() методы.

Есть ли какой-нибудь умный способ сделать все это аккуратно и правильно?

Ответы [ 3 ]

2 голосов
/ 22 сентября 2009

Один из способов обойти это иметь рекурсивную блокировку переменная в начале Отправить изменения(). Но что делать, если сохранение заблокировано?

Бросьте NotSupportedException . Вы не должны поддерживать еще один SubmitChanges , происходящий на BeforeChanges ... именно это вы и делаете, позволяя некоторым изменениям произойти до вызова SubmitChanges.

Что касается обновленных объектов, для которых вызывается их BeforeUpdate, вы можете проверить, есть ли новые обновленные объекты непосредственно перед SubmitChanges после того, как вы вызвали BeforeUpdate в исходном списке, и делать это до тех пор, пока не появятся дополнительные обновленные объекты.

То же самое касается AfterUpdate, что-то вроде этого - вносить изменения, которые происходят с объектами в памяти ... а не сохранять больше данных в БД.

Попытка добавить SubmitChanges к различным объектам в вашей системе - это то, что должно создать некоторые проблемы с производительностью в вашей системе.

1 голос
/ 21 сентября 2009

Моя единственная идея - создать своего рода буфер во время работы; хранить объекты, которые вы хотите сохранить.

Примерно так:

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

Надеюсь, это будет хорошо, я не проверял.

0 голосов
/ 22 сентября 2009

Итак, теперь этот ответный кредит уже дан, вот как я на самом деле реализовал решение:

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);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...