ADO.NET w praktyce


W ADO.NET 1.1 powyższa operacja wymagała napisania kodu, w którym w danym momencie dla danego połączenia (a więc i danej transakcji) był otwarty co najwyżej jeden zestaw rekordów. W .NET 2.0 jest możliwość korzystania z MARS (Multiple Active RecordSet). Dzięki temu kod zaprezentowany poniżej będzie działał prawidłowo:

using (SqlConnection cnn = new SqlConnection(CustomerEntry.Properties.Settings.Default.CnnTEST1)) {

SqlTransaction tr;

SqlCommand cmd,cmd1,cmdUpdt;

cmd = cnn.CreateCommand();

cmd.CommandText = "select Invoice.Dt from Invoice";

cmd1 = cnn.CreateCommand();

cmd1.CommandText="select * from InvoiceItem

where GidInvoice = @GidInvoice";

SqlParameter[] param = new SqlParameter[] {

new SqlParameter("@GidInvoice",SqlDbType.UniqueIdentifier)

};

cmd1.Parameters.AddRange(param);

cmdUpdt = cnn.CreateCommand();

cmdUpdt.CommandText = "update InvoiceItem set ItemValue= @ItemValue where Gid=@Gid";

param = new SqlParameter[] {

new SqlParameter("@Gid",SqlDbType.UniqueIdentifier)

new SqlParameter("@ItemValue",SqlDbType.Decimal)

};

cmdUpdt.Parameters.AddRange(param);

  1. cnn.Open();

    tr = cnn.BeginTransaction();

  2. cmd.Transaction = tr;

    cmd1.Transaction = tr;

    cmdUpdt.Transaction=tr;

    SqlDataReader dr,dr1;

  3. dr = cmd.ExecuteReader();

  4. while (dr.Read()) {

  5. cmd1.Parameters["@GidInvoice"].Value = dr["Gid"];

  6. dr1 = cmd1.ExecuteReader();

  7. while (dr1.Read()) {

  8. if (((DateTime)dr["Dt"])<=new DateTime(2005,01,01) && dr1["Measure"].ToString()=="szt") {

  9. cmdUpdt.Parameters["@Gid"].Value = dr1["Gid"];

  10. cmdUpdt.Parameters["@ItemValue"].Value = (decimal)dr1["ItemValue"] + 1m;

  11. cmdUpdt.ExecuteNonQuery();

  12. }

  13. }

  14. dr1.Close(); //Koniecznie

  15. }

  16. tr.Commit();

  17. cnn.Close();

  18. }

Konstruktory SqlParameter pozwalają precyzjnie określić własności parametrów polecenia SQL.

Konstruktory SqlParameter pozwalają precyzjnie określić własności parametrów polecenia SQL.

Najpierw definiujemy polecenia - cmd wybiera elementy z Invoice. cmd1 z InvoiceItem (do tego polecenia należy przekazać parametr określający GUID nagłówka faktury - @GidInvoice). SqlCommand cmdUpdt to operacja, która zmodyfikuje wartość danej pozycji na fakturze (trzeba przekazać nową wartość - @ItemValue oraz klucz główny danego rekordu w InvoiceItem - @Gid).

Potem otwieramy połączenie oraz transakcję, którą przypisujemy do 3 poleceń (bloki 1,2). Następnie można już uruchomić cmd i pobrać DataReader - proszę zauważyć, że ta operacja jest już dokonywana w ramach transakcji. Dla każdego pobranego rekordu (nagłówka faktury) pobierane są pozycje. Najpierw ustawiamy parametr (linia 5) dla cmd1, po czym otwieramy drugi SqlDataReader (w ramach tego samego połączenia i transakcji coś takiego nie było możliwe w ADO.NET 1.1). Następnie można sprawdzić warunek (linia 8) i ewentualnie wykonać aktualizację, używając cmdUpdt (ponownie w ramach danej transakcji).

Potem trzeba zamknąć DataReader dr1 (ale nie zamykać połączenia, dlatego przy ExecuteReader nie ma podanego parametru CommandBehavior.CloseConnection). Na koniec, po przejściu po wszystkich nagłówkach faktur, można potwierdzić transakcję (linia 16) i zamknąć połączenie.

Analogiczny kod w ADO.NET byłby znacznie dłuższy - nie można byłoby wykonać operacji w liniach 6 i 11. Przy użyciu jednego połączenia można było wykonywać jedną operację - nie można było otworzyć wielu aktywnych zbiorów danych i wykonywać na nich innych operacji. Trzeba by najpierw ściągnąć wszystkie nagłówki do jakiejś struktury pomocniczej i potem dopiero analizować kolejno wszystkie pozycje na danej fakturze. Dzięki MARS jest to znacznie prostsze i kod jest krótszy.

Oczywiście trzeba się w takim przypadku zastanowić, czy nie opłaca się bardziej przenieść tego kodu bezpośrednio na serwer SQL - przepisać go w TSQL lub dodać do serwera procedurę w .NET.

Operacje asynchroniczne w ADO.NET 2.0

Bardzo wygodną możliwością ADO.NET jest proste uruchamianie zapytań w tle. Dzięki temu można zlecić serwerowi obliczenie skomplikowanegowyrażenia SQL (czy procedury przechowywanej), kontynuować działanie aplikacji klienckiej, a gdy wynik jest potrzebny - odczytać go (lub jeszcze chwilę poczekać, aż proces serwerowy zakończy działanie).

SqlCommand ma też bardzo interesującą opcję uruchamiania asynchronicznego danej operacji na serwerze bazodanowym. Semantyka jest taka sama jak w przypadku delegatów i operacji asynchronicznych. Można albo używać 2 metod - jednej do rozpoczęcia operacji, drugiej do zakończenia, albo schematu callback, gdzie przy rozpoczęciu operacji przekazujemy delegata do funkcji, która ma być wywołana, gdy SQL Server zwróci wynik.

Załóżmy, że mamy jakąś długo wykonującą się procedurę TSQL (w tym przypadku po prostu procedura czeka 30 s, zanim wyśle zestaw rekordów z tabeli Customer):

CREATE PROCEDURE dbo.LongStoredProc AS WAITFOR DELAY '00:00:30'

select * from Customer

RETURN

Warto pamiętać, że przy definiowaniu połączenia trzeba określić, że używane będzie API asynchroniczne - należy dodać parametr async=true;. Uwaga! Dodanie tej opcji powoduje, że otworzenie połączenia zużywa trochę więcej zasobów niż zwykle. Warto ją stosować tylko wtedy, gdy naprawdę używamy tego schematu wywołania.