2013-03-15 14 views
12

Für die Verwendung in meinem aktuellen Projekt habe ich eine Klasse erstellt, mit der ich SQL Server async aufrufen kann.Mehrere gespeicherte SQL Server-Prozeduren in einer Transaktion aufrufen

Mein Code sieht wie folgt aus:

internal class CommandAndCallback<TCallback, TError> 
{ 
    public SqlCommand Sql { get; set; } 
    public TCallback Callback { get; set; } 
    public TError Error { get; set; } 
} 

class MyCodes:SingletonBase<MyCodes> 
{ 
    private static string _connString = @"Data Source=MyDB;Initial Catalog=ED;Integrated Security=True;Asynchronous Processing=true;Connection Timeout=0;Application Name=TEST"; 

    private MyCodes() { } 

    public void SetSystem(bool production) 
    { 
     _connString = 
      string.Format(@"Data Source=MyDB;Initial Catalog={0};Integrated Security=True;Asynchronous Processing=true;Connection Timeout=0;Application Name=TEST", production ? "ED" : "TEST_ED"); 
    } 

    public void Add(string newCode, Action<int> callback, Action<string> error) 
    { 
     var conn = new SqlConnection(_connString); 
     SqlCommand cmd = conn.CreateCommand(); 
     cmd.CommandTimeout = 0; 
     cmd.CommandType = CommandType.StoredProcedure; 
     cmd.CommandText = @"ADD_CODE"; 
     cmd.Parameters.Add("@NEW", SqlDbType.NVarChar).Value = newCode; 
     cmd.Parameters.Add("@NewId", SqlDbType.Int).Direction = ParameterDirection.Output; 

     try 
     { 
      cmd.Connection.Open(); 
     } 
     catch (Exception ex) 
     { 
      error(ex.ToString()); 
      return; 
     } 

     var ar = new CommandAndCallback<Action<int>, Action<string>> { Callback = callback, Error = error, Sql = cmd }; 
     cmd.BeginExecuteReader(Add_Handler, ar, CommandBehavior.CloseConnection); 
    } 

    private static void Add_Handler(IAsyncResult result) 
    { 
     var ar = (CommandAndCallback<Action<int>, Action<string>>)result.AsyncState; 
     if (result.IsCompleted) 
     { 
      try 
      { 
       ar.Sql.EndExecuteReader(result); 
       ar.Callback(Convert.ToInt32(ar.Sql.Parameters["@NewId"].Value)); 
      } 
      catch (Exception ex) 
      { 
       ar.Error(ex.Message); 
      } 
     } 
     else 
     { 
      ar.Error("Error executing SQL"); 
     } 
    } 

public void Update(int codeId, string newCode, Action callback, Action<string> error) 
    { 
     var conn = new SqlConnection(_connString); 
     SqlCommand cmd = conn.CreateCommand(); 
     cmd.CommandTimeout = 0; 
     cmd.CommandType = CommandType.StoredProcedure; 
     cmd.CommandText = @"UPDATE_CODE"; 
     cmd.Parameters.Add("@CODE_ID", SqlDbType.Int).Value = codeId; 
     cmd.Parameters.Add("@NEW", SqlDbType.NVarChar).Value = newCode; 

     try 
     { 
      cmd.Connection.Open(); 
     } 
     catch (Exception ex) 
     { 
      error(ex.ToString()); 
      return; 
     } 

     var ar = new CommandAndCallback<Action, Action<string>> { Callback = callback, Error = error, Sql = cmd }; 
     cmd.BeginExecuteReader(Update_Handler, ar, CommandBehavior.CloseConnection); 
    } 

    private static void Update_Handler(IAsyncResult result) 
    { 
     var ar = (CommandAndCallback<Action, Action<string>>)result.AsyncState; 
     if (result.IsCompleted) 
     { 
      try 
      { 
       ar.Sql.EndExecuteReader(result); 
       ar.Callback(); 
      } 
      catch (Exception ex) 
      { 
       ar.Error(ex.Message); 
      } 
     } 
     else 
     { 
      ar.Error("Error executing SQL"); 
     } 
    } 

} 

Das ist wie zu viel von Code aussehen kann, aber es lässt mich nennen wie so:

private void Add_Click(object sender, EventArgs e) 
{ 
    MyCodes.Instance.Add("Test",Success,Error) 
} 

private void Success(int newId) 
{ 
    MessageBox.Show(newId.ToString(), "Success", MessageBoxButtons.OK, MessageBoxIcon.Information); 
} 

private void Error(string error) 
{ 
    MessageBox.Show(error, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); 
} 

Above-Code für mich ganz gut funktioniert, Ich kann jeden Anruf asynchron ausführen.

Problem, das ich gerade habe, ist, mehrere Anrufe als Transaktion zu tun - ich möchte 2 Codes aktualisieren und eine neue hinzufügen.

Normalerweise würde ich update aufrufen, dann in der success handler call zweiten update, und in der handler zu zweiten update würde ich hinzufügen hinzufügen, die neue id zurückgeben würde.

Etwas wie:

-UPDATE CODE 
|-UPDATE CODE 
    |-ADD CODE (only this one return something) 

Aber ich möchte all jene, die als Transaktion nennen, so Code, wenn hinzufügen würde Updates brechen würde Rollback.

Frage:

Ist es möglich, mehrere asynchrone Abfragen als eine Transaktion zu nennen?

Kann ich meine oben genannten Methoden als Transaktion aufrufen oder muss ich eine separate Methode erstellen, um meine Prozeduren als eine aufzurufen? (Ich möchte dieses vermeiden, weil es nur den gleichen Code von einer Methode zu einem anderen kopiert)

Ich möchte hinzufügen, dass ich .NET 3.5 verwende, also warten und andere nette Eigenschaften sind keine Wahl.

+0

Leider müssen Sie alle Ihre Prozeduren in einer einzigen Transaktion umwandeln, die Sie nacheinander ausführen müssen. Sie werden sonst am Ende eine Transaktion pro Ausführung haben. – LukeHennerley

+0

LukeHennerly - Könnten Sie mir helfen, eine Methode zu entwickeln, die mehrere Prozeduren als eine bezeichnet? Im Idealfall würde es Liste der Codes zu aktualisieren und Code als Parameter hinzufügen und natürlich sollte es async wie oben genannt werden – Misiu

Antwort

7

Ja, es ist möglich. Rufen Sie einfach SqlConnection.BeginTransaction vor Ihrem ersten Anruf an, und weisen Sie das zurückgegebene Objekt SqlTransaction jedem in der Kette zu und rufen Sie am Ende SqlTransaction.Commit() an.

+0

Ich werde es sofort überprüfen :) – Misiu

+0

Ich versuchte dies mit einem Löschen, die zuerst einige Foreigen Schlüssel löschen muss (getrennt gespeicherte Prozeduren) und ich bekomme es nicht zu laufen, aber mit speichern und aktualisieren funktioniert es gut – WiiMaxx

16
string cnnString =WebConfigurationManager.ConnectionStrings["MyString"].ConnectionString; 
    SqlConnection cnn = new SqlConnection(cnnString); 
    SqlTransaction transaction; 

    cnn.Open(); 
    transaction = cnn.BeginTransaction(); 

    try 
    { 

     // Command Objects for the transaction 
     SqlCommand cmd1 = new SqlCommand("sproc1", cnn); 
     SqlCommand cmd2 = new SqlCommand("sproc2", cnn); 

     cmd1.CommandType = CommandType.StoredProcedure; 
     cmd2.CommandType = CommandType.StoredProcedure; 

     cmd1.Parameters.Add(new SqlParameter("@Param1", SqlDbType.NVarChar, 50)); 
     cmd1.Parameters["@Param1"].Value = paramValue1; 

     cmd1.Parameters.Add(new SqlParameter("@Param2", SqlDbType.NVarChar, 50)); 
     cmd1.Parameters["@Param2"].Value = paramValue2; 

     cmd2.Parameters.Add(new SqlParameter("@Param3", SqlDbType.NVarChar, 50)); 
     cmd2.Parameters["@Param3"].Value = paramValue3; 

     cmd2.Parameters.Add(new SqlParameter("@Param4", SqlDbType.NVarChar, 50)); 
     cmd2.Parameters["@Param4"].Value = paramValue4; 

     cmd1.ExecuteNonQuery(); 
     cmd2.ExecuteNonQuery(); 

     transaction.Commit(); 
    } 

    catch (SqlException sqlEx) 
    { 
     transaction.Rollback(); 
    } 

    finally 
    { 
     cnn.Close(); 
     cnn.Dispose(); 
    } 
+0

Danke für die Antwort in so alte Frage :) In der Freizeit werde ich Ihre Lösung überprüfen. Im Moment migriere ich alles auf .NET 4.5, also werde ich diese Funktion implementieren, während ich meine DB-Zugriffsklasse erstellen werde. – Misiu

+0

Erwähnen Sie auch 'SqlCommand.Transaction' auf diese Weise' SqlCommand cmd1 = neuer SqlCommand ("sproc1", cnn, transaction); 'oder auf diese Weise' cmd1.Transaction = transaction' falls Sie folgende Fehlermeldung erhalten: _ExecuteNonQuery benötigt den Befehl to eine Transaktion ausführen, wenn sich die dem Befehl zugewiesene Verbindung in einer ausstehenden lokalen Transaktion befindet. Die Transaction-Eigenschaft des Befehls wurde nicht initialisiert_. Entnommen von https://stackoverflow.com/a/10649035/1369235 – hims056