Budując architekturę wielowarstwową, wyraźnie oddzielając logikę biznesową od warstwy prezentacji podstawowym problemem jest wielokrotne powtarzanie kodu związanego z operacjami create, read, update i delete czyli dodawania, pobierania, aktualizacji i usuwania obiektów z bazy danych.
Najprostszym rozwiązaniem jest tutaj wykorzystanie typów generycznych i użycie najbardziej ogólnych metod jakie daje nam nasza biblioteka do obsługi bazy danych… Jak wykonać generyczny CRUD w Business Logic Toolkit? Pokażę, na przykładzie zaczerpniętym wprost z naszego projektu.
Podstawą jest silne typowanie obiektów które mogą być w naszej generycznej klasie przetwarzane. Do tego celu na każdy obiekt z bazy danych nakładamy pusty interfejs IDatabaseObject, który jednoznacznie określi nam możliwość jego użycia.
public interface IDatabaseObject { }
Teraz część najważniejsza, czyli stworzenie generycznego repozytorium zawierającego metody do zapisu, aktualizacji i usuwania obiektów oraz propercję typu IQueryable pozwalającą wyciągać informacje z bazy danych i wykonywać dowolne zapytania. Repozytorium będzie klasą generyczną pozwalającą wybrać obsługiwany typ obiektu.
public class Repository<TDatabaseObject>
where TDatabaseObject : class, IDatabaseObject
{
protected readonly DataManager _db = DataManager.GetInstance();
virtual public IQueryable<TDatabaseObject> All
{
get { return _db.GetTable<TDatabaseObject>(); }
}
virtual public void Insert(TDatabaseObject entity)
{
_db.Insert(entity);
}
virtual public void Update(TDatabaseObject entity)
{
_db.Update(entity);
}
}
Dodatkowo możemy jeszcze założyć, że pewna pula tabel w bazie danych rozróżniana będzie według przypisanego im identyfikatora. Takie obiekty nazywać będziemy encjami i oznaczać kolejnym interfejsem IEntity.
public interface IEntity:IDatabaseObject
{
long Id { get; set; }
}
Jak widać IEntity to jednocześnie IDatabaseObject tylko uzupełniony właśnie o pole z identyfikatorem.
Dlaczego więc nie przypisać identyfikatora wszystkim obiektom w bazie? Tutaj łatwo się domyślić, że czasem przydatne nam będą specyficzne tabele służące np. jako łączniki w relacjach many-to-many. W takich tabelach kluczem głównym są identyfikatory obu połączonych rekordów, ale sam rekord z tabeli łączącej nie posiada swojego identyfikatora.
Ostatnim krokiem jest dodanie rozszerzonego repozytorium które doda nam bardzo przydatne metody służące do wyciągania i usuwania obiektów po identyfikatorze. Ważnym jest tutaj także nadpisanie metody insert w taki sposób, aby nowo zapisanemu obiektowi przyporządkowywała od razu jego Id.
public class EntityRepository<TEntity> : Repository<TEntity>
where TEntity : class, IEntity
{
override public void Insert(TEntity entity)
{
var id = _db.InsertWithIdentity(entity);
entity.Id = Convert.ToInt64(id);
}
virtual public TEntity Get(long id)
{
var result = from e in All
where e.Id == id
select e;
return result.SingleOrDefault();
}
virtual public void Delete(long id)
{
All.Delete(e => e.Id == id);
}
}
Tak przygotowane repozytoria możemy użyć bezpośrednio, lub rozszerzać o specyficzne metody które będą w nich ładnie zamykać wszelkie zapytania. Przykładem może być klasa Car i szczególne repozytorium z metodą do wyciągania listy samochodów według daty ich produkcji.
public class Car : IEntity
{
public long Id { get; set; }
public string Name { get; set; }
public int ProductionYear { get; set; }
}
Specyficzne repozytorium dziedziczy po repozytorium generyczny, ale uszczegóławia typ generyczny. Dzięki temu napisane wewnątrz zapytania będą odwoływać się typowo do obiektów klasy Car.
public class CarRepository : EntityRepository<Car>
{
public IEnumerable<Car> GetCarsByProductionYear(int yearFrom, int yearTo)
{
return this.All.Where(c => c.ProductionYear > yearFrom && c.ProductionYear < yearTo);
}
}
Tak stworzone repozytorium może zostać użyte w warstwie pośredniej lub od razu na warstwie widoku. W ładny sposób udostępnia nam ono logikę związaną ze specyficznymi zapytaniami pozwalając uniknąć niepotrzebnego powtarzania kodu operacji CRUD.
Oczywiście podobnie można zrealizować generyczne CRUDy używając innych niż BLToolkit mostków obiektowo relacyjnych jak na przykład w Entity Framework 4, Linq-To-Sql lub NHibernate.