Acg N. .
Temat: Repozytorium encji + RepoLocator - jak to zrobić lepiej?
Na potrzeby pisanego prototypu przygotowuję sobie "środowisko" pracy, czyli strukturę projektu. Zatrzymałem się dla przemyślenia przy kwestiach zw. z modelem. Tej nocy :) zaimplementowałem repozytorium encji i ich locatora.W przykładach mam typowe "podręcznikowe" encje: Employee, Store, Product, ale oczywiście w "realu" są inne i jest ich sporo więcej :) i dlatego chcę zautomatyzować tworzenie repozytoriów. Automatyczne tworzenie i dodawanie do locatora dotyczy "standardowych" encji, dla których wystarczy mi standardowy zestaw operacji z IRepository<T>. Oczywiście są także encje bardziej złożone i dotyczące ich repozytoria mają oferować dodatkowe funkcje. Wtedy trzeba je napisać ręcznie (dziedziczą po IRepository<T>) i trzeba je dodać ręcznie do kolekcji w locatorze.
Działa mi to poprawnie, natomiast to co skleciłem coś mi się wydaje przekombinowane i trochę "nie tego"... Czy mógłbym Was prosić o rzucenie okiem na całość i wyrażenie swoich sugestii (konstruktywnie) w zakresie ulepszenia tego rozwiązania, propozycji wykorzystania innych konstrukcji? Od dłuższego czasu (niedługo rok) praktycznie nie programuję na poważnie, więc - wstyd się przyznać - chwilowo nie rozwijam w tym kierunku no i mam trochę zaległości w nowościach (wiem, że są gotowe rozwiązania do takich zagadnień, np. Autofac).
1. Przykładowa encja
public class Employee : IModel
{
public virtual int Id { get; private set; }
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
public virtual Store Store { get; set; }
}
IModel jest tylko dla potrzeb wyszukiwania typu w pakiecie i jest pusty. Dla tych encji, które implementują IModel, będą utworzone i dodane standardowe repozytoria.
public interface IModel
{
}
2. Repozytorium.
a. Pusty interfejs dla potrzeb locatora
public interface IRepository
{
}
b. Standardowy interfejs CRUD
public interface IRepository<T> : IRepository
{
IList<T> GetByExample(T example);
... i takie tam inne
void Add(T item);
void Remove(T item);
void Update(T item);
}
c. Fabryka repozytoriów dla każdej z encji
public class RepositoryFactory<T> : IRepository<T>, IRepository
{
IDataContext dataContext;
public RepositoryFactory(IDataContext dataContext)
{
this.dataContext = dataContext;
}
public IList<T> GetByExample(T example)
{
IEnumerable<T> result = this.dataContext.GetByExample(example, typeof(T)).Cast<T>();
return result.ToList<T>();
}
..... etc.
}
d. Interfejs IDataContext
public interface IDataContext
{
IList GetByExample(object example, Type type);
.... i takie tam
void Add(object item);
void Remove(object item);
void Update(object item);
}
e. Repo locator - zwłaszcza o niego mi chodzi :)
public class RepositoryLocator
{
private Dictionary<Type, IRepository> repositories;
public RepositoryLocator(IDataContext context)
{
repositories = new Dictionary<Type, IRepository>();
// automatyczne odszukanie wszystkich encji, dla których ma być utworzone standardowe repozytorium
Assembly asm = Assembly.GetCallingAssembly();
IEnumerable<Type> entities = (from t in asm.GetTypes()
where t.GetInterface("IModel") != null
select t
);
foreach (Type m in entities)
{
// zbudowanie obiektu repozytorium.
Type repoFacType = typeof(RepositoryFactory<>);
// "ugenerycznienie" o typ encji
Type finalRepoFacType = repoFacType.MakeGenericType(new Type[1]{m});
object repository = Activator.CreateInstance(finalRepoFacType, new object[] { context });
repositories.Add(m, (IRepository) repository);
}
}
// wersja dla zwykłych repozytoriów - wskazanie przez typ encji
public IRepository<T> GetRepository<T>() where T : IModel // tylko dla std. encji
{
if (repositories.ContainsKey(typeof(T)))
return repositories[typeof(T)] as IRepository<T>;
else
return null;
}
// wersja dla osobnych repozytoriów - wskazanie przez typ repozytorium
public T GetRepositoryEx<T>() where T : class, IRepositoryEx // tylko dla niestd. repoz.
{
if (typeof(T).GetInterface("IRepositoryEx") != null)
{
Type t = ((IRepositoryEx)(Activator.CreateInstance(typeof(T), new object[] { null }))).EntityType;
if (repositories.ContainsKey(t))
return repositories[t] as T;
else
return null;
}
else
{
return null;
}
}
f. DataProvidera oszczędzę => NHibernate dla SQLite'a :)
g. Wykorzystanie w kodzie:
DataProvider dataProvider = new DataProvider(this.session);
RepositoryLocator repoLocator = new RepositoryLocator(dataProvider );
IList<Employee> employees =
repoLocator.GetRepository<Employee>().GetByExample(example);
albo
repoLocator.GetRepository<Employee>().Add(Janusz);
h. Specjalne repozytoria muszę napisać ręcznie
Najpierw specjalny interfejs dla nich dla potrzeb locatora:
public interface IRepositoryEx
{
Type EntityType { get; } // typ skojarzonej z nim encji
}
Potem interfejs tego repozytorium
public interface IProductRepository : IRepository<Product>
{
IList<Product> GetByMoreSophisticatedExample(Product example, string somethingStupid);
}
Wreszcie klasa repozytorium
public class ProductRepository : IRepositoryEx, IProductRepository
{
IDataContext dataContext;
public ProductRepository(IDataContext dataContext)
{
this.dataContext = dataContext;
}
public IList<Product> GetByMoreSophisticatedExample(Product example, string somethingStupid)
{
......... super-mega-wyrafinowane wyszukiwanie :]
return result.ToList<Product>();
}
....
public Type EntityType
{
get { return typeof(Product); }
}
}
dodać do kolekcji:
repoLocator.AddNewRepository(new ProductRepository(dataProvider), typeof(Product));
i wykorzystać potem:
IList<Product> products = ((ProductRepository)repoLocator.GetRepository<Product>()).GetByMoreSophisticatedExample(p, "coś tam, coś tam");
albo
IList<Product> products = repoLocator.GetRepositoryEx<ProductRepository>().GetByMoreSophisticatedExample(p, "cośtam");
PS: kod do nauki, nie ma obsługi błędów.
PS: pisane z przerwami między 1 a 4:40 dziś w nocy, więc bądźcie wyrozumiali :)Adrian Olszewski edytował(a) ten post dnia 23.07.10 o godzinie 04:48