Projektowanie, Programowanie, Codzienność – BeniaminZaborski.com

6 Styczeń 2009

Ugryźć Spring.Net – (cz.3) Dostęp do danych – ADO.NET

Filed under: Ugryźć Spring.Net — Tags: , , , , , , — Beniamin Zaborski @ 07:04

Każda aplikacja biznesowa operuje na danych. Najczęściej są to dane z relacyjnej bazy danych. Jak się do nich dostać?
W zależności od przyjętego modelu architektonicznego naszej aplikacji sposobów jest kilka. Abstrahując od tego Spring.Net ułatwia nam dostęp do danych dostarczając pomocnych narzędzi zarówno dla ADO.NET jak i NHibernate.
Początkowo ten artykuł miał traktować o obu tych metodach dostępu do danych, jednak z kilku powodów zdecydowałem się podzielić to na dwa osobne artykuły. Ze względu na obszerność poruszanych przeze mnie tematów w tej serii artykułów i tak nie zdołam zadowolić wszystkich czytelników. Po szczegóły już tradycyjnie wszystkich zainteresowanych odeślę do dokumentacji projektu, ale to dopiero pod koniec artykułu;).

Spring dostarcza wielu pomocnych klas narzędziowych ułatwiających dostęp do danych przez ADO.NET.

Wymienię kilka najważniejszych z mojego punktu widzenia:
– integracja z mechanizmem zarządzania transakcjami
– zcentralizowane zarządzanie połączeniami, komendami, czy zbiorami danych
– proste mapowanie DataReader to Object
– prostsze dodawanie parametrów do zapytań
– szablony do wykonywania zapytań SQL

Główną zaletą, która wynika (mniej lub bardziej) z powyższej listy jest to, że pracujemy na wyższym poziomie abstrakcji w porównaniu z „czystym” kodem ADO.NET.
Zanim przejdziemy do wykonywania zapytań SQL, kilka słów o połączeniu do bazy danych bo tu Spring także przychodzi nam z pomocą. Mowa tu o interfejsie IDbProvider.

public interface IDbProvider
{
  IDbCommand CreateCommand();
  object CreateCommandBuilder();        
  IDbConnection CreateConnection();
  IDbDataAdapter CreateDataAdapter();
  IDbDataParameter CreateParameter();
  string CreateParameterName(string name);
  string CreateParameterNameForCollection(string name);
  IDbMetadata DbMetadata { get; }               
  string ConnectionString { set; get; }
  string ExtractError(Exception e);
  bool IsDataAccessException(Exception e);    
}

Jak widać jest to dość pomocna fabryka dla połączeń, komend, parametrów, itd. Klasa fabrykująca DbProviderFactory tworzy nam instancję IDbProvider dla wskazanego RDBMS. Lista RDBMS jest zadowalająca i chyba nikomu nie powinno niczego brakować. Wytworzenie naszego IDbProvider-a wygląda tak:

IDbProvider dbProvider = DbProviderFactory.GetDbProvider(„System.Data.SqlClient”);

Tak wygląda to w kodzie, a dla odmiany w pliku konfiguracyjnym (łącznie z użyciem AdoTemplate) tak:

<objects xmlns=’http://www.springframework.net’
         xmlns:db=”http://www.springframework.net/database”&gt;
  <db:provider id=”DbProvider”
      provider=”System.Data.SqlClient”
      connectionString=”Data Source=(local);Database=Bazka;User ID=jimmy;Password=secret;Trusted_Connection=False”/>
 
  <object id=”adoTemplate” type=”Spring.Data.AdoTemplate, Spring.Data”>  
    <property name=”DbProvider” ref=”DbProvider”/>                
  </object>
</objects>

 

 

Klasą która najbardziej nas zainteresuje i wykonuje całą czarną robotę za nas jest AdoTemplate. Opiera się ona na idei Inversion Of Control z główną metodą Execute. Istnieje zarówno tradycyjna jak i generyczna implementacja tej klasy. Kod wykonujemy poprzez przekazanie delegata:

adoTemplate.Execute<int>(delegate(DbCommand command)
{
  command.CommandText =
  „SELECT COUNT(*) FROM Abonenci WHERE CzyAktywny = @CzyAktywny”;
                                                    
  DbParameter p = command.CreateParameter();
  p.ParameterName = „@CzyAktywny”;
  p.Value = czyAktywny;
  command.Parameters.Add(p);
                                                         
  return (int)command.ExecuteScalar();
});

Klasa AdoTemplate obok Execute posiada pokaźną ilość metod, których znaczenia nie trzeba chyba tłumaczyć, np.: ExecuteNonQuery, ExecuteScalar, DataTableCreate, DataTableFill, DataTableUpdate, DataSetCreate, DataSetFill, itp.

Przykład wykonania ExecuteNonQuery i ExecuteScalar:

adoTemplate.ExecuteNonQuery(CommandType.Text, String.Format(„INSERT INTO Przekierowania(We) VALUES ({0})”, 112));int count = (int)adoTemplate.ExecuteScalar(CommandType.Text, „SELECT COUNT(*) FROM Przekierowania”);

Proste, zrozumiałe i o ile mniej kodu niż podczas użycia „czystego” ADO.NET, prawda?
A teraz coś ciekawszego. Wynik zapytania możemy zmapować równie niewielkim nakładem pracy na kolekcję naszych obiektów domeny. Brzmi świetnie, sprawdźmy.
Służą do tego metody z klasy AdoTemplate (których celowo wcześniej nie wymieniłem) z grupy QueryWith. Odpowiedzialne za wykonywania mapowania są trzy interfejsy i delegaty, które musimy obsłużyć:
– IResultSetExtractor / ResultSetExtractorDelegate – umożliwia iterowanie po zwróconym wyniku i zwrócenie odpowiedniego obiektu.
– IRowCallback / RowCallbackDelagete – umożliwia przetwarzanie bieżącego wiersza wyniku zapytania bez zwracania wyniku.
– IRowMapper / RowMapperDelegate – umożliwia przetwarzanie bieżącego wiersza wyniku zapytania ze zwróceniem wyniku.

Interfejsy IResultSetExtractor oraz IRowMapper występują zarówno w wersji klasycznej jak i generycznej. Przyjrzyjmy się teraz każdemu z osobna z wymienionych interfejsów, na przykładach.

adoTemplate.QueryWithResultSetExtractor(CommandType.Text, “SELECT * FROM Abonenci”, new NazwaAbonentaResultSetExtractor<List<string>>());internal class NazwaAbonentaResultSetExtractor <T> : IResultSetExtractor<T> where T : IList<string>, new()
{
        public T ExtractData(IDataReader reader)
        {
            T list = new T();
            while (reader.Read())
            {
                string nazwaAbonenta = reader.GetString(0);
                list.Add(nazwaAbonenta);
            }
            return list;
        }
}

W metodzie naszego extractora iterujemy po wynikach zapytania i pobieramy to co nas interesuje czyli nazwę abonenta zwracając kolekcję typów string. Oczywiście moglibyśmy tu zamiast prostego typu string mapować dane do naszej encji.

Teraz przykład użycia kolejnego dość pomocnego interfejsu czyli IRowCallback. Jak sama nazwa może sugerować, pozwala nam na przetwarzanie danych każdego wiersza wyniku zapytania. Sam w sobie nie zwraca danych, jednak operuje na obiekcie który posiada stan. Prześledźmy jego działanie na przykładzie. Załóżmy, że chcemy z danych abonentów utworzyć słownik w którym znajdą się abonenci pogrupowani po typie (oczywiście można te zrobić o wiele prostszą metodą już w SQL).

TypyAbonentowRowCallback rowCallback = new TypyAbonentowRowCallback ();AdoTemplate.QueryWithRowCallback(CommandType.Text, “SELECT * FROM ABONENCI”, rowCallback);Dictionary<string, IList<Abonent>> result = rowCallback.TypyAbonentow;

A oto definicja naszego TypyAbonentowRowCallback:

internal class TypyAbonentowRowCallback: IRowCallback
{
  private IDictionary<string, IList<string>> typyAbonentow =new Dictionary<string, IList<string>>();  public IDictionary<string, IList<string>> TypyAbonentow
  {
    get { return typyAbonentow; }
  }  public void ProcessRow(IDataReader reader)
  {
    Guid id = reader.GetString(0);
    string nazwa = reader.GetString(1);
    string adres = reader.GetString(2);
    bool czyAktywny = reader.GetString(3);
    string typAbonenta = reader.GetString(4);    Abonent abonent = new Abonent(id, nazwa, adres, czyAktywny, typAbonenta);

    if (!typyAbonentow.ContainsKey(typAbonenta))
      typyAbonentow.Add(typAbonenta, new List<Abonent>());
                
    IList<Abonent> listaAbonentow = typyAbonentow[typAbonenta]; listaAbonentow.Add(abonent);
  }
}

Widzimy, że metoda ProcessRow  klasy TypyAbonentowRowCallback przetwarza każdy wiersz wyniku zapytania, ale nie zwraca go bezpośrednio, a zapisuje w wewnętrznej kolekcji.

Ostatni z przykładów do IRowMapper. Działa podobnie do poprzedniego, jednak jest bezstanowy z reguły ponieważ przetworzone dane bieżącego rekordu są zwracane. Prosty przykład pobrania listy abonentów:

IList<Abonenct> listaAbonentow = AdoTemplate.QueryWithRowMapper<Abonent>(CommandType.Text, “SELECT * FROM ABONENCI”, new AbonentRowMapper<Abonent>());public class AbonentRowMapper <T> : IRowMapper<T> where T : Abonent, new()
{        
  public T MapRow(IDataReader dataReader, int rowNum)
  {
    T abonent = new T();
     abonent.Id = reader.GetString(0);
     abonent.Nazwa = reader.GetString(1);
     abonent.Adres = reader.GetString(2);
     abonent.CzyAktywny = reader.GetString(3);
     abonent.TypAbonenta = reader.GetString(4);
     return abonent;
   }
}

Powyższa implementacja interfesu IRowMapper chyba nie wymaga komentarza.

Jak widzimy Spring.NET daje nam sporo ciekawych i pomocnych narzędzi dostępu do danych poprzez ADO.NET. Użycie czystego ADO.NET wymagało by o wiele więcej linii kodu. Pomocnych narzędzi w AdoTemplate jest dużo więcej, a te tu przedstawione to subiektywnie wybrane przeze mnie. Zachęcam do zapoznania się z tą użyteczną klasą i jak obiecałem na wstępie odsyłam do dokumentacji. W kolejnej części napiszę o dostępie do danych przy pomocy NHibernate.

Dodaj komentarz »

Brak komentarzy.

RSS feed for comments on this post. TrackBack URI

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

Stwórz darmową stronę albo bloga na WordPress.com.

%d bloggers like this: