Как реализовать Единицу работы, которая работает с EF и NHibernate

Я работал над внедрением Unit of Work, который работает как в Entity Framework 4.1, так и в NHibernate. Найдите ниже скелета деталей моей реализации

Определение IUnitOfWork

public interface IUnitOfWork { IRepository LogInfos { get; } IRepository AppInfos { get; } void Commit(); void Rollback(); } 

Определение IRepository

 public interface IRepository where T : class, IEntity { IQueryable FindAll(); IQueryable FindWhere(Expression<Func> predicate); T FindById(int id); void Add(T newEntity); void Remove(T entity); } 

Внедрение UoW в NHibernate

 public class NHibernateUnitOfWork : IUnitOfWork, IDisposable { public ISession Session { get; private set; } public NHibernateUnitOfWork(ISessionFactory sessionFactory) { _sessionFactory = sessionFactory; Session = _sessionFactory.OpenSession(); _transaction = Session.BeginTransaction(); } public IRepository LogInfos { get { if (_logInfo == null) { _logInfo = new NHibernateRepository(Session); } return _logInfo; } } public void Commit() { if (_transaction.IsActive) _transaction.Commit(); } } 

Единица работы в структуре организации 4.1

 public class SqlUnitOfWork : IUnitOfWork { private readonly ObjectContext _context; public SqlUnitOfWork() { _context = new ObjectContext(connectionString); _context.ContextOptions.LazyLoadingEnabled = true; } private SqlRepository _logInfo = null; public IRepository LogInfos { get { if (_logInfo == null) { _logInfo = new SqlRepository(_context); } return _logInfo; } } public void Commit() { _context.SaveChanges(); } } 

Репозиторий с использованием NHibernate

 public class NHibernateRepository : IRepository where T : class, IEntity { protected ISession Session; public NHibernateRepository(ISession session) { Session = session; } public IQueryable FindAll() { return Session.Query(); } public IQueryable FindWhere(Expression<Func> predicate) { return Session.Query().Where(predicate); } public T FindById(int id) { return Session.Get(id); } public void Add(T newEntity) { Session.Save(newEntity); } public void Remove(T entity) { Session.Delete(entity); } } 

Репозиторий с использованием Entity Framework

 public class SqlRepository : IRepository where T : class, IEntity { protected ObjectSet ObjectSet; public SqlRepository(ObjectContext context) { ObjectSet = context.CreateObjectSet(); } public IQueryable FindAll() { return ObjectSet; } public IQueryable FindWhere(Expression<Func> predicate) { return ObjectSet.Where(predicate); } public T FindById(int id) { return ObjectSet.Single(i => i.Id == id); } public void Add(T newEntity) { ObjectSet.AddObject(newEntity); } public void Remove(T entity) { ObjectSet.DeleteObject(entity); } } 

С этой реализацией я мог бы получить большинство функций, таких как сохранение, удаление, транзакция, работающая как на EF, так и на NH. Но когда я начинаю писать сложные запросы LINQ в хранилищах NH, большинство из них приходится выполнять. Некоторые функции, такие как OrderBy и ToList, вызывают ошибки, когда Repository возвращает NhQueryable.

В следующем коде вызывается из ASP.NET MVC-controllerа, к которому я вставляю экземпляр IUnitOfWork с помощью StructureMap. Когда вводится NHibernateUnitOfWork, где условие не применяется, если оно работает так, как ожидается при вводе SqlUnitOfWork.

 var query = from a in _unitOfWork.AppInfos.FindAll() join l in _unitOfWork.LogInfos.FindAll() on a.Id equals l.ApplicationId where l.Level == "ERROR" || l.Level == "FATAL" group l by new { a.Id, a.ApplicationName } into g select new LogInfoSummaryViewModel() { ApplicationId = g.Key.Id, ApplicationName = g.Key.ApplicationName, ErrorCount = g.Where(i => i.Level == "ERROR").Count(), FatalCount = g.Where(i => i.Level == "FATAL").Count() }; return query.AsEnumerable(); 

    Поскольку стороннее не решение для строительства, поддерживающее разные возможности на вершине linq, является способом катастрофы. Linq и IQueryable являются протекающими абстракциями – каждый поставщик Linq может иметь свои «функции» и ограничения. Более того, сам EF добавляет некоторую логику с помощью пользовательских методов расширения для IQueryable (например, Include или AsNoTracking в EFv4.1). Эти методы внутренне преобразуют IQueryable в ORM.

    Если вы хотите иметь универсальное решение, вы должны отказаться от Linq и добавить третий шаблон для формирования абстракции. В дополнение к шаблонам Repository и Unit of Work вам нужен специальный шаблон спецификации . Как правило, вы переопределяете API критериев NHibernate.

    С точки зрения IoC и стремления к элегантности ваш путь – это путь. Тем не менее, все, что я прочитал о провайдере linq NHibernate, заключается в том, что он по-прежнему «бета-иш», потому что на самом деле так сложно записывать провайдеров Linq. Поэтому вполне может быть, что вы просто сталкиваетесь с ошибкой здесь. В настоящее время я бы очень не хотел писать производственный код с Linq2Nhibernate. Новая функция QueryOver намного мощнее. Но, к сожалению, QueryOver не вписывается в вашу архитектуру, потому что вам придется использовать синтаксис NHibernate. Сложные запросы Linq за пределами вашего репо были бы бесполезны, потому что они никогда не будут переведены на SQL.

    Я боюсь, что это действительно поцелуй смерти в элегантность вашего дизайна, потому что для начала было бы бесполезно позволить репозиторию возвращать IQueryable . Но возврат IEnumerable приведет к искажению вашей реализации EF. Итак, что сводится к тому, что я думаю, что для запросов обеих реализаций слишком разные, чтобы соответствовать одному опрятному универсальному интерфейсу.

    Вот очень полезный пост в QueryOver и Linq.

    Кстати: это очень интересный вопрос и дизайн. Хотел бы я дать более одного голоса!

    В дополнение к техническим трудностям с QueryOver, упомянутым Ladislav, может возникнуть проблема с дизайном. У вас не было бы этой проблемы, если вы подходите к ней с точки зрения Domain Driven Design, где интерфейс репозитория основан на языке Ubiquitous Language и не раскрывает такие вещи, как IQueryable которая является чистой концепцией доступа к данным. Этот ответ содержит информацию и ссылки, которые могут оказаться интересными.

    Давайте будем гением компьютера.