Как реализовать Unit of Work, который работает с EF и NHibernate
Я работал над реализацией Unit of Work, которая работает как в 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 Framework 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();
3 ответа
В качестве побочного решения, не поддерживающего различные возможности, на вершине linq - это путь к катастрофе. Линк и IQueryable
являются неплотными абстракциями - каждый провайдер Linq может иметь свои "особенности" и ограничения. Более того, сам EF добавляет некоторую логику с помощью пользовательских методов расширения для IQueryable
(лайк Include
или же AsNoTracking
в EFv4.1). Эти методы внутренне преобразуют IQueryable
для конкретных классов ORM.
Если вы хотите иметь универсальное решение, вы должны отказаться от Linq и добавить третий шаблон для формирования абстракции. В дополнение к шаблонам репозитория и единицы работы вам нужен собственный шаблон спецификации. Как правило, вы будете переопределять API критериев NHibernate.
С точки зрения IoC и стремления к элегантности ваш путь - это путь. Тем не менее, все, что я читал о linq-провайдере NHibernate, это то, что он все еще "бета-иш", потому что в первую очередь чертовски трудно писать провайдеров Linq. Так что вполне может быть, что вы просто столкнулись с ошибкой здесь. В настоящее время я бы очень неохотно писал производственный код с Linq2Nhibernate. Новая функция QueryOver гораздо более мощная. Но, к сожалению, QueryOver не вписывается в вашу архитектуру, потому что вам придется использовать синтаксис NHibernate полностью. Сложные запросы Linq вне вашего репо будут бесполезны, потому что они никогда не будут переведены в SQL.
Я боюсь, что это поцелуй смерти к элегантности вашего дизайна, потому что, для начала, было бы бесполезно позволять хранилищу возвращать IQueryable<T>
, Но возвращаясь IEnumerable<T>
нанесет вред вашей реализации EF. Итак, к чему сводится, я думаю, что для запроса обе реализации слишком различны, чтобы соответствовать одному аккуратному универсальному интерфейсу.
Вот очень полезный пост о QueryOver и Linq.
Кстати: это очень интересный вопрос и дизайн. Я хотел бы дать больше, чем один голос!
Помимо технических трудностей с QueryOver, упомянутых Ладиславом, могут быть проблемы с дизайном. У вас не будет этой проблемы, если вы подойдете к ней с точки зрения доменного дизайна, где интерфейс репозитория основан на вездесущем языке и не раскрывает такие вещи, как IQueryable
которая является чистой концепцией доступа к данным. Этот ответ содержит информацию и ссылки, которые могут вас заинтересовать.