如何实现与EF和NHibernate一起使用的工作单元

时间:2011-09-14 05:06:54

标签: linq nhibernate entity-framework-4 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);
}

在NHibernate中实现UoW

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();
    }
}

实体框架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);
    }
}

使用实体框架的存储库

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都会失败。当Repository返回NhQueryable时,OrderBy和ToList等一些功能会抛出错误。

以下代码是从ASP.NET MVC控制器调用的,我正在使用StructureMap注入IUnitOfWork的实例。注入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 个答案:

答案 0 :(得分:14)

作为一个没有建立解决方案的一方支持linq之上的不同提供是灾难的方式。 Linq和IQueryable是漏洞抽象 - 每个Linq提供者都有自己的“特性”和限制。此外,EF本身通过IQueryable的自定义扩展方法(如EFv4.1中的IncludeAsNoTracking)添加了一些逻辑。这些方法在内部将IQueryable转换为ORM特定的类。

如果您想拥有通用解决方案,则必须放弃Linq并添加第三个模式以形成抽象。除了Repository和Unit of Work模式之外,您还需要自定义Specification模式。通常,您将重新实现NHibernate的Criteria API。

答案 1 :(得分:6)

从IoC的角度来看,对优雅的渴望是你想要的方式。然而,我读到的关于NHibernate的linq提供商的所有内容都是它仍然是“beta-ish”,因为首先编写Linq提供程序真是太难了。所以很可能你刚刚遇到一个bug。目前我非常不愿意用Linq2Nhibernate编写生产代码。新的QueryOver功能更强大。但是,遗憾的是,QueryOver无法与您的架构无缝融合,因为您必须始终使用NHibernate语法。在您的仓库之外复杂的Linq查询将毫无用处,因为它们永远不会被转换为SQL。

我担心这实际上是你设计优雅的死亡之吻,因为,首先,让存储库返回IQueryable<T>是没用的。但是返回IEnumerable<T>会削弱您的EF实施。那么,归结为什么,我认为对于查询这两种实现都太不同了,无法适应一个简洁的通用接口。

Here是QueryOver和Linq上非常有用的帖子。

BTW:这是一个非常有趣的问题和设计。我希望我能投一票以上!

答案 2 :(得分:2)

除了Ladislav提到的QueryOver的技术困难之外,可能还存在设计问题。如果从Domain Driven Design角度处理Repository接口基于Ubiquitous Language,并且不公开IQueryable这样的纯粹数据访问概念之类的内容,则不会出现此问题。这个answer包含您可能感兴趣的信息和链接。