2013-03-15 16 views
12

Do użycia w moim bieżącym projekcie utworzyłem klasę, która pozwala mi wywoływać asynchronię SQL Server.Wywołanie wielu procedur składowanych SQL Server w transakcji

Mój kod wygląda następująco:

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"); 
     } 
    } 

} 

Może to wyglądać zbyt dużo kodu, ale to pozwala mi nazwać jak tak:

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

Powyższy kod działa dobrze dla mnie, Jestem w stanie wykonać każde połączenie asynchronicznie.

Problem, który mam teraz, to wykonywanie wielu połączeń jako transakcji - chciałbym zaktualizować 2 kody i dodać jedną nową

Normalnie chciałbym wywołać aktualizację, a następnie w drugiej aktualizacji obsługi handler, a w programie obsługi do drugiej aktualizacji chciałbym wywołać add, który zwróci nowy identyfikator.

Coś jak:

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

Ale chciałbym zaprosić wszystkich tych, jako transakcji, więc jeśli dodać kod złamie aktualizacje byłoby wycofać.

Pytanie:

Czy to możliwe, aby wywołać wiele async kwerendy jako transakcję?

Czy mogę nazwać moje powyższe metody jako transakcję lub czy muszę utworzyć osobną metodę wywoływania moich procedur jako jedną? (Chciałbym tego uniknąć, ponieważ to tylko kopiowanie tego samego kodu z jednej metody do drugiej)

Chciałbym dodać, że używam .NET 3.5, więc oczekujcie, a inne fajne funkcje nie są opcją.

+0

Niestety, aby zawrzeć wszystkie procedury w ramach jednej transakcji, należy wykonać je jeden po drugim. W przeciwnym razie będziesz miał transakcję na wykonanie. – LukeHennerley

+0

LukeHennerly - Czy mógłbyś pomóc mi stworzyć metodę, która będzie nazywała wiele procedur jednym? Najlepiej byłoby, gdyby lista kodów była aktualizowana i dodawana do kodu jako parametry i oczywiście powinna być nazywana asynchronicznie jak wyżej: – Misiu

Odpowiedz

7

Tak, jest to możliwe. Wystarczy zadzwonić pod numer SqlConnection.BeginTransaction przed pierwszym połączeniem, aby przypisać zwrócony obiekt SqlTransaction do każdego numeruw łańcuchu i zadzwonić pod numer SqlTransaction.Commit() na końcu.

+0

Sprawdzę to od razu :) – Misiu

+0

Próbowałem tego z usunięciem, które najpierw musi usunąć jakiś klucz wstępny (oddzielone procedury przechowywane) i nie uruchamia się, ale z zapisem i aktualizacją działa dobrze – 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

Dziękuję za odpowiedź w tak starym pytaniu :) W czasie wolnym sprawdzę Twoje rozwiązanie. Teraz migruję wszystko do .NET 4.5, więc zaimplementuję tę funkcję, a ja utworzę moją klasę dostępu do DB. – Misiu

+0

Również w tym miejscu wpisz 'SqlCommand.Transaction'' SqlCommand cmd1 = new SqlCommand ("sproc1", cnn, transaction); 'lub w ten sposób' cmd1.Transaction = transaction' w przypadku, gdy pojawi się komunikat o błędzie: _ExecuteNonQuery wymaga polecenia do mieć transakcję, gdy połączenie przypisane do polecenia jest w oczekującej transakcji lokalnej. Właściwość Transakcja polecenia nie została zainicjowana_. Zaczerpnięte z https://stackoverflow.com/a/10649035/1369235 – hims056

Powiązane problemy