Как реализовать Единицу работы, которая работает с 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, который является чистой концепцией доступа к данным. Этот ответ содержит информацию и ссылки, которые могут оказаться интересными.