Как реализовать Единицу работы, которая работает с EF и NHibernate
Я работал над реализацией подразделения, работающего как в Entity Framework 4.1, так и в NHibernate. Найдите ниже скелета деталей моей реализации
Определение IUnitOfWork
public interface IUnitOfWork
{
IRepository<LogInfo> LogInfos { get; }
IRepository<AppInfo> AppInfos { get; }
void Commit();
void Rollback();
}
Определение IRepository
public interface IRepository<T> where T : class, IEntity
{
IQueryable<T> FindAll();
IQueryable<T> FindWhere(Expression<Func<T, bool>> 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<LogInfo> LogInfos
{
get
{
if (_logInfo == null)
{
_logInfo = new NHibernateRepository<LogInfo>(Session);
}
return _logInfo;
}
}
public void Commit()
{
if (_transaction.IsActive)
_transaction.Commit();
}
}
Единица работы в инфраструктуре Entity 4.1
public class SqlUnitOfWork : IUnitOfWork
{
private readonly ObjectContext _context;
public SqlUnitOfWork()
{
_context = new ObjectContext(connectionString);
_context.ContextOptions.LazyLoadingEnabled = true;
}
private SqlRepository<LogInfo> _logInfo = null;
public IRepository<LogInfo> LogInfos
{
get
{
if (_logInfo == null)
{
_logInfo = new SqlRepository<LogInfo>(_context);
}
return _logInfo;
}
}
public void Commit()
{
_context.SaveChanges();
}
}
Репозиторий с использованием NHibernate
public class NHibernateRepository<T> : IRepository<T> where T : class, IEntity
{
protected ISession Session;
public NHibernateRepository(ISession session)
{
Session = session;
}
public IQueryable<T> FindAll()
{
return Session.Query<T>();
}
public IQueryable<T> FindWhere(Expression<Func<T, bool>> predicate)
{
return Session.Query<T>().Where<T>(predicate);
}
public T FindById(int id)
{
return Session.Get<T>(id);
}
public void Add(T newEntity)
{
Session.Save(newEntity);
}
public void Remove(T entity)
{
Session.Delete(entity);
}
}
Репозиторий с использованием Entity Framework
public class SqlRepository<T> : IRepository<T> where T : class, IEntity
{
protected ObjectSet<T> ObjectSet;
public SqlRepository(ObjectContext context)
{
ObjectSet = context.CreateObjectSet<T>();
}
public IQueryable<T> FindAll()
{
return ObjectSet;
}
public IQueryable<T> FindWhere(Expression<Func<T, bool>> 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-контроллера, к которому я вставляю экземпляр 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();
Ответы
Ответ 1
В качестве стороннего не решения для построения, поддерживающего разные возможности на вершине linq, можно избежать катастрофы. Linq и IQueryable
являются негерметичными абстракциями - каждый поставщик Linq может иметь свои собственные "функции" и ограничения. Кроме того, сам EF добавляет некоторую логику с помощью пользовательских методов расширения для IQueryable
(например, Include
или AsNoTracking
в EFv4.1). Эти методы внутренне преобразуют IQueryable
в ORM определенные классы.
Если вы хотите иметь универсальное решение, вы должны отказаться от Linq и добавить третий шаблон для формирования абстракции. В дополнение к шаблонам Repository и Unit of Work вам нужен пользовательский шаблон Specification. Как правило, вы будете переопределять API-интерфейс NHibernate.
Ответ 2
С точки зрения IoC и стремления к элегантности ваш путь - это путь. Тем не менее, все, что я прочитал об NHibernate linq провайдере, это то, что он по-прежнему "бета-иш", потому что на самом деле так сложно записывать провайдеров Linq. Поэтому вполне может быть, что вы просто сталкиваетесь с ошибкой здесь. В настоящее время я бы очень не хотел писать производственный код с Linq2Nhibernate. Новая функция QueryOver намного мощнее. Но, к сожалению, QueryOver не вписывается в вашу архитектуру, потому что вам придется использовать синтаксис NHibernate. Сложные запросы Linq за пределами вашего репо были бы бесполезны, потому что они никогда не будут переведены на SQL.
Я боюсь, что это действительно поцелуй смерти в элегантность вашего дизайна, потому что для начала было бы бесполезно позволить репозиторию возвращать IQueryable<T>
. Но возврат IEnumerable<T>
приведет к искажению вашей реализации EF. Итак, что сводится к тому, что я думаю, что для запросов обеих реализаций слишком разные, чтобы соответствовать одному опрятному универсальному интерфейсу.
Здесь - очень полезный пост в QueryOver и Linq.
Кстати: это очень интересный вопрос и дизайн. Хотел бы я дать более одного голоса!
Ответ 3
В дополнение к техническим трудностям с QueryOver, упомянутым Ladislav, может возникнуть проблема с дизайном. У вас не было бы этой проблемы, если вы подходите к ней из Domain Driven Design, где Репозиторий основан на Вездесущий язык и не раскрывает такие вещи, как IQueryable
, который является чистой концепцией доступа к данным. Этот ответ содержит информацию и ссылки, которые могут оказаться интересными.