Transakcje ADO.NET cz. 2

 

W poprzednim poście przybliżyłem nieco ideę zamieszczania w kodzie .NET transakcji. W tej odsłonie postaram się skupić na zagadnieniach nieco bardziej praktycznych. Z góry zapowiadam, że ze względu na ilość materiału ukaże się co najmniej jeszcze jedna odsłona. Jeśli chodzi o transakcje w ADO.NET, uwagę należy tutaj skupić na klasie SqlTransaction. Transakcję ropoczynamy wywołując na obiekcie SqlConnection metodę BeginTransaction(). W omawianym przeze mnie najprostszym przypadku wykorzystywany będzie konstruktor domyślny, aczkolwiek istnieją jeszcze trzy inne – oczekujący nazwy transakcji, bądź też podania poziomu izolacji oraz jednocześnie obu tych parametrów. Obiekt SqlConnection, jak dobrze wiemy, służy do otwarcia połączenia, jeśli oczywiście przekazany został uprzednio connection string do bazy. Po otwarciu transakcji, możemy ją, albo zatwierdzić (commit), albo wycofać (rollback). Szkic takiego wywołania można zatem przedstawić następująco:

SqlConnection connection = new SqlConnection("myConnectionString");
SqlTransaction transaction;

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

try
{
	// Czynnosci zwiazane z obsluga bazy danych
	// .....

	transaction.Commit();
}
catch
{
	transaction.Rollback();
	throw;
}
finally
{
	connection.Close();
}

Łatwo zauważyć, że przy takim podejściu nie ma chociażby obsługi błędów dla wywołania connection.Open(). Nie jest to jedyna wada. Szczególnie należy zwrócić uwagę na to, że transakcję można rozpocząć dopiero po otwarciu połączenia do bazy, czyli właśnie wywołania metody Open(). Odnośnie powyższego kodu, dla porządku parę słów wyjaśnienia – udane wykonanie sekwencji operacji w bloku try{} zatwierdzamy, jeśli zostanie przechwycony jakiś wyjątek w bloku catch{} cofamy transakcję, natomiast w bloku finally{} zamykamy połączenie do bazy, żeby nie zostawiać niepotrzebnie otwartych połączeń.

W podanym powyżej szkicu jest jednak jedna pułapka – nadal transakcją obejmujemy jedną metodę dostępu do danych, Odwołując się do wcześniejszego posta mielibyśmy taką sytuację:

public void CreateUser(...)
{
	// Poczatek transakcji 

	// ---------------------------

	// Ok
	// Wstawienie do tabeli tUsers

	// Źle !
	// Wstawienie do tabeli tTokensHistory

	// --------------------------

	// Koniec transakcji
}

Pisząc taki kod złamalibyśmy oczywiście zasadę, że jedna metoda powinna w zasadzie wykonywać jedną atomową operację, co doprowadziłoby do zaciemnienia kodu. 

Sytuacja do której, według założeń, dążymy to “wyniesienie” transakcji do wyżej położonej warstwy aplikacji. Nazywając warstwę dostępu do danych (zawierającą metody odwołujące się do bazy danych) warstwą DAL (Data Access Layer), tworzymy jednocześnie warstwę BLL (Business Logic Layer), która będzie grupowała pewne jednostki biznesowe realizujące logikę związaną z wstawieniem danych do bazy. Obrazując to na przykładzie, uzyskamy taką sytuację:

// Przyklad niepelny
public class UsersBll
    {
        UsersDAL _users;
        
        public UsersBll()
        {
			// Celowo wykasowana linia inicjalizujaca obiekt transaction...
            _users = new UsersDAL(transaction);
        }

        /// <summary>
        /// Creates user in proper tables in db
        /// </summary>
        public void CreateUser(UserModelExt user)
        {
            try
            {
                transaction.BeginTransaction();
                
                // Set of operations
                int? userId = _users.CreateUser(user);

                // Create token in tokens history table
                if (userId.HasValue)
                    _users.CreateTokenInHistory(userId.Value, Guid.NewGuid().ToString());
                else
                    throw new ArgumentNullException("UserId was null when trying to create token history entry");

                transaction.Commit();
            }
            catch
            {
                transaction.RollBack();
                throw;
            }
        }
    }

Przykład, jak to zostało napisane w komentarzu, jest niepełny, ponadto proszę nie sugerować się nazwą klasy Transaction (to nie jest wspomniana wcześniej klasa SqlTransaction), która została, pewnie dość niezgrabnie, zaimplementowana na moje potrzeby. 

W przykładzie powyższym otwieramy transakcję, wykonujemy ciąg operacji związanych wstawieniem użytkownika do bazy, czyli wstawienie do tabeli tUsers, następnie do tTokensHistory, a na koniec zamykamy transakcję, albo ją commit-ując, albo rollback-ując. Metoda CreateUser(…) umieszczona w warstwie BLL tym samym realizuje kilka operacji, z których jednak każda jest atomowa. Takie podejście jest jak najbardziej tym razem pożądane. W kolejnej odsłonie postaram się zaprezentować w jaki sposób zaimplementować klasę Transaction, aby wszystko ze sobą współgrało (jasne powinno być w tym momencie, że metody w warstwie DAL nie mogą już same ani tworzyć transakcji, ani zajmować się otwieraniem i zamykaniem połączenia – stąd właśnie wynika trudność w zaimplementowaniu takiego rozwiązania).

Reklamy

2 Responses to Transakcje ADO.NET cz. 2

  1. Pingback: dotnetomaniak.pl

Skomentuj

Wprowadź swoje dane lub kliknij jedną z tych ikon, aby się zalogować:

Logo WordPress.com

Komentujesz korzystając z konta WordPress.com. Log Out / Zmień )

Zdjęcie z Twittera

Komentujesz korzystając z konta Twitter. Log Out / Zmień )

Facebook photo

Komentujesz korzystając z konta Facebook. Log Out / Zmień )

Google+ photo

Komentujesz korzystając z konta Google+. Log Out / Zmień )

Connecting to %s

%d blogerów lubi to: