Сейчас мы активно используем ORM LLBLGen Pro в своих проектах. Я уже рассказывал, как мы решили проблему с тестированием DataAccessApater и запросов на Linq, которые по своему реализованы в этой ORM.
С тех пор прошло уже пол года и мы усовершенствовали этот подход. Проблема осталась та же: использование LLBLGen Pro в проекте и его уживаемость с модульным тестированием. В прошлый раз у нас получился такой результат:
1: using (var adapter = DataAccessAdapterFactory.Create())
2: {
3: ILinqMetaDataAdapter linqMetaData = LinqMetaDataFactory.Create(adapter);
4: result = linqMetaData.Query<realtyentity>().ToList();
5: }
Этот вариант оказался очень практичен и успешно используется на одном из проектов. Обе фабрики внутри себя содержали вызов ServiceLocator и возвращали разные реализации для тестов и нормального запуска.
Итак, мы начали новый проект и захотелось облегчить себе жизнь. Писать каждый раз создание двух объектов IDataAccessAdapter и ILinqMetadataAdapter через фабрики и потом в тесках делать для них заглушки довольно утомительно. Поэтому мы пришли к единому интерфейсу для доступа к данным:
1: public interface IDataContext : IDataAccessAdapter
2: {
3: IQueryable<T> Query<T>() where T : IEntityCore;
4: }
Как видно, он уже сочетает в себе и непосредственные функции адаптера, и доступ к IQueryable для запросов на Linq. Реальная реализация этого интерфейса выглядит довольно просто:
1: internal class LLBLGenDataContext : IDataContext
2: {
3: private readonly DataAccessAdapter adapter;
4: private readonly Dictionary<Type, Func<IQueryable>> entityLookup;
5: private readonly LinqMetaData linq;
6:
7: public LLBLGenDataContext()
8: {
9: adapter = new DataAccessAdapter();
10: linq = new LinqMetaData(adapter);
11: entityLookup = new Dictionary<Type, Func<IQueryable>>
12: {
13: {typeof (AccountEntity), () => linq.Account}
14: };
15: }
16:
17: #region IDataContext Members
18:
19: public IQueryable<T> Query<T>() where T : IEntityCore
20: {
21: IQueryable<T> result;
22: try
23: {
24: result = (IQueryable<T>) entityLookup[typeof (T)].Invoke();
25: }
26: catch (KeyNotFoundException ex)
27: {
28: throw new KeyNotFoundException(GlobalResources.LinqMetadataAdapterNotContainsNecessaryKeyExceptionMessage, ex);
29: }
30: return result;
31: }
32:
33: public void Dispose()
34: {
35: adapter.Dispose();
36: }
37:
38: public IPredicateExpression CreatePrimaryKeyFilter(IList primaryKeyFields)
39: {
40: return adapter.CreatePrimaryKeyFilter(primaryKeyFields);
41: }
42:
43: // Дальше идут функции обычного адаптера, которые передаются созданному внутри объекту adapter
Теперь в тестах мы пишем:
1: [Fact]
2: public void RemoveOldActivationsOnNewCreating()
3: {
4: mockDataContext.Setup(m => m.Query<AccountActivationEntity>())
5: .Returns(new List<AccountActivationEntity> {new AccountActivationEntity {AccountId = 1}, new AccountActivationEntity {AccountId = 1}}.AsQueryable());
6:
7: var repository = new AccountRepository();
8: repository.CreateActivation(new AccountEntity { Id = 1 });
9:
10: mockDataContext.Verify(d => d.DeleteEntity(It.IsAny<AccountActivationEntity>()), Times.Exactly(2));
11: }
Само хранилище AccountRepository использует интерфейс IDataContext для доступа к данным:
1: using(var context = Builder.Create<IDataContext>())
2: {
3: context.Query<AccountActivationEntity>().Where(...
4: }
Объект Builder работает через Ninject, опять же выдавая разные реализации для тестов и для реального использования.
Получилось на много удобнее, чем было раньше. Посмотрим во что это вырастет еще через пол года ;)
Я считаю, что не стоило делать IDataContext потомком IDataAccessAdapter, а вынести только необходимые методы, например CRUD :)
ОтветитьУдалить@hazzik
ОтветитьУдалитьДа, кстати, можно только нужные методы оставить. Ну исходники у тебя под рукой, можешь делать как тебе больше нравится ;)