2014-01-22 7 views
6

Ich habe eine Methode, die intern verschiedene Unteroperationen in einer Reihenfolge ausführt und bei Ausfall der Unteroperation möchte ich die gesamte Operation zurücksetzen.Wie kann sichergestellt werden, dass eine Operation auf Systemebene atomar ist? irgendein Muster?

Mein Problem ist die Unter-Operationen sind nicht alle Datenbankoperationen. Dies sind hauptsächlich Änderungen auf Systemebene wie das Hinzufügen von etwas in der Windows-Registrierung, das Erstellen eines Ordners auf einem angegebenen Pfad und das Festlegen von Berechtigungen usw. Die Untervorgänge können mehr als das sein.

möchte etwas so machen;

Wenn der letzte Vorgang fehlschlägt, möchte ich alle vorherigen Operationen zurücksetzen.

Also, was ist der Standard Weg, dies zu tun? Gibt es ein Design-Muster, um damit umzugehen?

Hinweis: Ich verwende C#.net

+1

hast du nachgeschlagen ** transactionscope **? -> http://msdn.microsoft.com/en-us/library/system.transactions.transactionscope%28v=vs.110%29.aspx –

Antwort

4

Hier ist eine Möglichkeit, es zu tun:

die command pattern verwenden, können Sie undoable Aktionen erstellen. Mit jeder Operation registrieren Sie die zugehörigen Befehle, sodass Sie die ausgeführten Befehle rückgängig machen können, wenn eine Fehlerbedingung auftritt.

Zum Beispiel könnte dies alles in ein transaktionsähnliches Kontextobjekt gehören, das IDisposable implementiert und in einen using Block einfügt. Die rückgängig zu machenden Aktionen würden in diesem Kontextobjekt registriert. Bei der Entsorgung wird, wenn nicht festgeschrieben, für alle registrierten Befehle "rückgängig machen" ausgeführt. Ich hoffe es hilft. Der Nachteil ist, dass Sie möglicherweise einige Methoden in Klassen konvertieren müssen. Dies könnte jedoch ein notwendiges Übel sein.

Codebeispiel:

using(var txn = new MyTransaction()) { 
    txn.RegisterCommand(new CreateUserFtpAccountCommand()); 
    txn.RegisterCommand(new CreateUserFolderCommand()); 
    txn.RegisterCommand(new SetUserPermissionCommand()); 
    txn.RegisterCommand(new CreateVirtualDirectoryForUserCommand()); 
    txn.Commit(); 
} 

class MyTransaction : IDisposable { 
    public void RegisterCommand(Command command){ /**/ } 
    public void Commit(){ /* Runs all registered commands */ } 
    public void Dispose(){ /* Executes undo for all registered commands */ } 
} 

class UndoableCommand { 
    public Command(Action action) { /**/ } 
    public void Execute() { /**/ } 
    public void Undo{ /**/ } 
} 

Update:

Sie erwähnten, dass Sie Hunderte solcher reversibler Operationen haben. In diesem Fall können Sie einen funktionelleren Ansatz wählen und UndoableCommand vollständig loswerden. Sie würden stattdessen Delegierten registrieren, wie folgt aus:

using(var txn = new MyTransaction()) { 
    txn.Register(() => ftpManager.CreateUserAccount(user), 
       () => ftpManager.DeleteUserAccount(user)); 
    txn.Register(() => ftpManager.CreateUserFolder(user, folder), 
       () => ftpManager.DeleteUserFolder(user, folder)); 
    /* ... */ 
    txn.Commit(); 
} 

class MyTransaction : IDisposable { 
    public void Register(Action operation, Action undoOperation){ /**/ } 
    public void Commit(){ /* Runs all registered operations */ } 
    public void Dispose(){ /* Executes undo for all registered and attempted operations */ } 
} 

Als Anmerkung, müssen Sie würde mit closures mit diesem Ansatz vorsichtig sein.

+0

Ich habe Hunderte von solchen reversiblen Befehlen (Methoden in Diff-Klassen), so kann jede Methode eine separate Klasse machen. ich denke, ich muss einfach mit if-else/try-catch Logik gehen. irgendwelche anderen Gedanken? – user3223708

+0

Bitte beachten Sie mein Update. – henginy

+0

nice, ich würde Func Delegat anstelle von Aktion in Register-Methode verwenden, weil meine Methoden immer ein Objekt (DTO) akzeptieren und zurückgeben, was Action nicht erlaubt, denke ich. oder tut es? – user3223708

0

Ich bin nicht bekannt, dass Standardmuster für diese Art der Sache, aber ich würde wahrscheinlich tut es mit verschachtelten try/catch-Blöcken selbst - mit dem entsprechenden Code für den Rollback von Nicht-Datenbank-Operationen im Catch-Block. Verwenden Sie ein TransactionScope, um sicherzustellen, dass alle Datenbankvorgänge Transaktionsaktionen sind.

zB:

using (TransactionScope scope) 
{ 
    try 
    { 
    DoOperationOne(); 

    try 
    { 
     DoOperationTwo(); 

     DoDataBaseOperationOne(); // no need for try/catch surrounding as using transactionscope 

     try 
     { 
     DoOperationThree(); 
     } 
     catch 
     { 
     RollBackOperationThree(); 
     throw; 
     } 
    } 
    catch 
    { 
     RollBackOperationTwo(); 
     throw; 
    } 
    } 
    catch 
    { 
    RollbackOperationOne(); 
    throw; 
    } 

    scope.Complete(); // the last thing that happens, and only if there are no errors! 
} 
+0

Ja das ist meine letzte Option, die ich versuche, einen Weg zu finden, zu vermeiden . – user3223708

3

Ich denke, Ihre beste Wette wäre die Verkapselung der Ausführung und Umkehrung jedes Schrittes des Prozesses. Es wird viel leichter Code lesen als verschachtelte Try-Catch-Blöcke.Etwas wie:

public interface IReversableStep 
{ 
    void DoWork(); 
    void ReverseWork(); 
} 

public void DoEverything() 
{ 
    var steps = new List<IReversableStep>() 
    { 
     new CreateUserFTPAccount(), 
     new CreateUserFolder(), 
     ... 
    } 
    var completed = new List<IReversableStep>(); 
    try 
    { 
     foreach (var step in steps) 
     { 
       step.DoWork(); 
       completed.Add(step); 
     } 
    } 
    catch (Exception) 
    { 
     //if it is necessary to undo the most recent actions first, 
     //just reverse the list: 
     completed.Reverse(); 
     completed.ForEach(x => x.ReverseWork()); 
    } 
} 
+0

Bitte beachten Sie die mögliche Ausnahme, die in 'ReverseWork' auftritt. In diesem Fall wird nur ein teilweiser Rollback durchgeführt. – Caramiriel

+1

Wahrscheinlich möchten Sie auch die Umkehrschritte in umgekehrter Reihenfolge durchführen, nicht in der gleichen Reihenfolge, in der die Befehle ausgeführt wurden. Aber mir gefällt das Konzept! –

1

Beide NTFS und die Registry Unterstützung Einschreibung in KTM und MS DTC-Transaktionen (und durch Erweiterung, TransactionScope). Das Transaktionsdateisystem wurde aufgrund der Komplexität jedoch nicht weiter unterstützt und ist möglicherweise in einigen zukünftigen Windows-Versionen nicht mehr vorhanden.

Wenn nicht alles passt in einer Transaktion, würde ich den Befehl Geschichte Muster aussehen sicherlich auf diese Frage in anderen Antworten präsentiert.

Verwandte Themen