Как реализовать Unit of Work, который работает с EF и NHibernate - PullRequest
15 голосов
/ 14 сентября 2011

Я работал над реализацией 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 ]

14 голосов
/ 14 сентября 2011

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

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

6 голосов
/ 14 сентября 2011

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

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

Здесь - очень полезная статья о QueryOver и Linq.

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

2 голосов
/ 14 сентября 2011

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

...