2009-09-14 3 views
1

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

У меня есть подкласс 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() был синхронным, например. если ему нужен доступ к свойству auto-number сразу после сохранения.

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

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

+0

Оно должно быть только меня, но я не вижу причин, почему вы не можете использовать переменная, блокирующая рекурсию ...? – ShdNx

+0

ShdNx - потому что, если есть рекурсия, это означает, что я на самом деле * хочу * сделать другое сохранение; Я не хочу блокировать второе спасение. Но я также не хочу бесконечной рекурсии ... –

+0

Хорошо, отправил ответ. – ShdNx

ответ

2

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

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

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

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

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

+0

+1 Что вы говорите, звучит правильно, как и мои предложенные изменения в ответе ShdNx. –

+0

+ Bounty - Я обсуждал вопрос о том, должен ли отвечать кредит вам или ShdNx, и я решил ответить на этот ответ из-за вашего второго абзаца, в котором кратко изложено решение, которое я в конечном итоге использовал (и какой ShdNx был близок, но узко пропустил!). Я отправлю свой собственный ответ, показывая, как я это сделал. –

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

Я надеюсь, что будет работать нормально, я не проверял.

+0

+1 - Я думаю, что вы на правильном пути, но не совсем там - этот алгоритм вызывает BeforeUpdate() другого класса ПОСЛЕ КОНКАТА, - который уже слишком поздно, чтобы поймать любые вызванные изменения. Помните, что BeforeUpdate() запускает рекурсивный вызов SubmitChanges. Но я думаю, что если мы сможем каким-то образом создать коллекцию всех модифицированных объектов и очистить их, как мы позвоним BeforeUpdate(), у нас будет решение! –

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); 
} 
Смежные вопросы