LLBLGen Pro - это O/R mapper, который генерирует .NET код, основываясь на схеме в базе данных.
Сейчас мы активно его используем для работы над нашим проектом. Надо отметить, что эта ORM имеет очень хороший интерфейс и настройку.
К тому же, они сделали поддержку запросов к базе данных через Linq. В LLBLGen вся работа происходит с помощью класса LinqMetaData.
К примеру, нужно найти все объекты RealtyEntity в базе:
1: using (var adapter = new DataAccessAdapter())
2: {
3: var linqMetaData = new LinqMetaData(adapter);
4: result = linqMetaData.Realty.ToList();
5: }
Отлично! Но здесь есть одна проблема. Весь код сгенерированный LLBLGen жестко завязан на типах используемых в этой ORM.
Заметили, как идёт обращение к IQueryable объекту для сущности RealtyEntity:
1: linqMetaData.Realty...
В сгенерированном классе LinqMetaData это выглядит примерно так:
1: public DataSource2<realtyentity> Realty
2: {
3: get { return new DataSource2<realtyentity>(_adapterToUse, new ElementCreator(), _customFunctionMappings, _contextToUse); }
4: }
Т.е. для каждой сущности генерируется свой getter, который и формирует DataSource2 (это расширенный IQueryable) объект для конкретного класса.
Проблемы, которые это влечет:
- сложно перейти на L2Q, EF и любую ORM, которая поддерживает дженерик объекты IQueryable.
- для тестирования кода, который обращается к базе данных нужно обязательно писать интеграционные тесты. Т.е. для того, чтобы проверить логику работы моего объекта RealtyRepository, мне нужно ждать, пока фиктивная база данных инициализирутся, заполнится нужными значениями и на ней пройдет тест. При хорошем темпе работы это отнимает слишком много времени.
Почему не сделать у объекта LinqMetaData дженерик поле IQueryable? Иначе мне нужно указывать название этого поля вместо типа сущности.
Что в итоге хочется получить:
1: using (var adapter = new DataAccessAdapter())
2: {
3: var linqMetaData = new LinqMetaData(adapter);
4: result = linqMetaData.Query<realtyentity>().ToList();
5: }
Именно так делается в Linq to SQL, Entity Framework. Даже в NHibernate Linq сделали подобным образом. К тому же, если есть возможность абстрагироваться от ORM, то надо это сделать.
Нигде не мог найти, как же LLBLGen Pro заботиться о TDD. Вот мой совет, как это сделать.
Проблема
Мы хотим использовать код типа:
1: linqMetaData.Query<realtyentity>().ToList();
При этом иметь возможность "подставлять" базу данных в памяти (in-memory DB) для быстрого запуска модульных тестов.
Решение
Нужно заменять объект LinqMetaData подобным mock-объектом. К примеру, использовать фабрику объектов LinqMetaData и в ней подставлять нужную реализацию LinqMetaData в зависимости от контекста. Т.е. LinqMetaDataFactory будет возвращать LinqMetaData при использовании приложения и MockLinqMetaData при модульном тестировании.
1: ??? linqMetaData = LinqMetaDataFactory.Create(adapter);
Нам нужен общий интерфейс или абстрактрый класс, который будет возвращаться фабрикой LinqMetaDataFactory. Но класс LinqMetaData не имеет интерфейса и постоянно перегенерируется, его трогать нельзя.
Здесь очень пригодится паттерн Adapter.
По нашей задумке интерфейс, который возвращается фабрикой, должен выглядеть так:
1: public interface ILinqMetaDataAdapter
2: {
3: IQueryable<t> Query<t>() where T : IEntityCore;
4: }
Осталось реализовать объект адаптера, который обернет LinqMetaData:
1: public class LinqMetaDataAdapter : ILinqMetaDataAdapter
2: {
3: private readonly Dictionary<type, func=""><iqueryable>> actions;
4: private readonly LinqMetaData innerLinq;
5:
6: public LinqMetaDataAdapter(IDataAccessAdapter adapter)
7: {
8: innerLinq = new LinqMetaData(adapter);
9:
10: // ставим в соответствие тип и DataSource2 объект для этого типа в сгенерированном классе LinqMetaData
11: actions = new Dictionary<type, func=""><iqueryable>>
12: {
13: {typeof (AddressCityEntity), () => innerLinq.AddressCity},
14: {typeof (SiteSectionEntity), () => innerLinq.SiteSection}
15: };
16: }
17:
18: #region ILinqMetaDataAdapter Members
19:
20: public IQueryable<t> Query<t>() where T : IEntityCore
21: {
22: return (IQueryable<t>) actions[typeof (T)].Invoke();
23: }
24:
25: #endregion
26: }
27: }
Теперь ничего не мешает нам использовать Unity, StructureMap или другой фремворк, для подстановки нашей реализации в фабрику:
1: public static class LinqMetaDataFactory
2: {
3: public static ILinqMetaDataAdapter Create(IDataAccessAdapter adapter)
4: {
5: return SomeIoCContainer.Resolve<ilinqmetadataadapter>(adapter);
6: }
7: }
Конечный результат:
1: using (var adapter = new DataAccessAdapter())
2: {
3: ILinqMetaDataAdapter linqMetaData = LinqMetaDataFactory.Create(adapter);
4: result = linqMetaData.Query<realtyentity>().ToList();
5: }
Комментариев нет:
Отправить комментарий