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

时间:2021-08-31 14:28:40

I was working on a Unit of Work implementation that works both in Entity Framework 4.1 and NHibernate. Find below the skeleton of my implementation details

我正在开发一个工作单元实现,它在Entity Framework 4.1和NHibernate中都有效。在下面找到我的实现细节的框架

IUnitOfWork definition

IUnitOfWork定义

public interface IUnitOfWork
{
    IRepository<LogInfo> LogInfos { get; }
    IRepository<AppInfo> AppInfos { get; }
    void Commit();
    void Rollback();
}

IRepository definition

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

Implementation of UoW in NHibernate

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

Unit of Work in Entity Framework 4.1

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

Repository using NHibernate

使用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);
    }
}

Repository using 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);
    }
}

With this implementation I could get most of the features like saving, deleting, transaction working on both EF and NH. But when I start writing complex LINQ queries against Repositories NH fails most of the time. Some features like OrderBy and ToList throws errors when Repository is returning NhQueryable.

通过这种实现,我可以获得大部分功能,如保存,删除,事务处理EF和NH。但是当我开始针对存储库编写复杂的LINQ查询时,大多数时候NH都会失败。当Repository返回NhQueryable时,OrderBy和ToList等一些功能会引发错误。

In the following code is called from ASP.NET MVC controller to which I'm injecting instance of IUnitOfWork using StructureMap. When NHibernateUnitOfWork is injected Where condition does not get applied where as it works as expected when SqlUnitOfWork is injected.

在下面的代码是从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 个解决方案

#1


14  

As a side not building solution supporting different provides on top of the linq is way to disaster. Linq and IQueryable are leaky abstractions - each Linq provider can have its own "features" and limitations. Moreover EF itselfs adds some logic via custom extension methods for IQueryable (like Include or AsNoTracking in EFv4.1). These methods internally converts IQueryable to ORM specific classes.

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

If you want to have universal solution you must abandon Linq and add third pattern to form the abstraction. In addition to Repository and Unit of Work patterns you need custom Specification pattern. Generally you will reimplement NHibernate's Criteria API.

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

#2


6  

From an IoC point of view and a desire for elegance your way is the way to go. However, all I read about NHibernate's linq provider is that it is still "beta-ish", because it is so damn hard to write Linq providers in the first place. So it might well be that you're just running into a bug here. Currently I would be very reluctant to write production code with Linq2Nhibernate. The new QueryOver feature is much more powerful. But of course, sadly, QueryOver doesn't fit seamlessly into your architecture, because you would have to use NHibernate syntax all the way. Complex Linq queries outside your repo would be useless because they would never get translated to SQL.

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

I'm afraid this effectively is the kiss of death to the elegance of your design, because, to start with, it would be useless to let a repository return an IQueryable<T>. But returning IEnumerable<T> would cripple your EF implementation. So, what is boils down to, I think that for querying both implementations are too different to fit behind one neat generic interface.

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

Here is a very useful post on QueryOver and Linq.

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

BTW: this is a very interesting question and design. I wish I could give more than one vote!

顺便说一句:这是一个非常有趣的问题和设计。我希望我能投一票多票!

#3


2  

In addition to technical difficulties with QueryOver mentioned by Ladislav there may be a design issue. You would not have this problem if you approach it from Domain Driven Design perspective where Repository interface is based on Ubiquitous Language and does not expose things like IQueryable which is a pure data access concept. This answer has information and links that you may find interesting.

除了Ladislav提到的QueryOver的技术难题之外,可能还存在设计问题。如果您从Domain Driven Design的角度处理它,那么您就不会遇到这个问题,其中Repository接口基于Ubiquitous Language并且不会暴露像IQueryable这样的纯数据访问概念。这个答案包含您可能感兴趣的信息和链接。

#1


14  

As a side not building solution supporting different provides on top of the linq is way to disaster. Linq and IQueryable are leaky abstractions - each Linq provider can have its own "features" and limitations. Moreover EF itselfs adds some logic via custom extension methods for IQueryable (like Include or AsNoTracking in EFv4.1). These methods internally converts IQueryable to ORM specific classes.

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

If you want to have universal solution you must abandon Linq and add third pattern to form the abstraction. In addition to Repository and Unit of Work patterns you need custom Specification pattern. Generally you will reimplement NHibernate's Criteria API.

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

#2


6  

From an IoC point of view and a desire for elegance your way is the way to go. However, all I read about NHibernate's linq provider is that it is still "beta-ish", because it is so damn hard to write Linq providers in the first place. So it might well be that you're just running into a bug here. Currently I would be very reluctant to write production code with Linq2Nhibernate. The new QueryOver feature is much more powerful. But of course, sadly, QueryOver doesn't fit seamlessly into your architecture, because you would have to use NHibernate syntax all the way. Complex Linq queries outside your repo would be useless because they would never get translated to SQL.

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

I'm afraid this effectively is the kiss of death to the elegance of your design, because, to start with, it would be useless to let a repository return an IQueryable<T>. But returning IEnumerable<T> would cripple your EF implementation. So, what is boils down to, I think that for querying both implementations are too different to fit behind one neat generic interface.

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

Here is a very useful post on QueryOver and Linq.

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

BTW: this is a very interesting question and design. I wish I could give more than one vote!

顺便说一句:这是一个非常有趣的问题和设计。我希望我能投一票多票!

#3


2  

In addition to technical difficulties with QueryOver mentioned by Ladislav there may be a design issue. You would not have this problem if you approach it from Domain Driven Design perspective where Repository interface is based on Ubiquitous Language and does not expose things like IQueryable which is a pure data access concept. This answer has information and links that you may find interesting.

除了Ladislav提到的QueryOver的技术难题之外,可能还存在设计问题。如果您从Domain Driven Design的角度处理它,那么您就不会遇到这个问题,其中Repository接口基于Ubiquitous Language并且不会暴露像IQueryable这样的纯数据访问概念。这个答案包含您可能感兴趣的信息和链接。