现代 ORM 与 Repository/IoW 模式

Modern ORM vs Repository/IoW pattern

我阅读了很多关于实体 Framework/NHibernate(或基本上任何其他现代 ORM)与 repository/UnitOfWork 模式的用法的内容。显然社区是分裂的。有些人会说存储库模式几乎是强制性的,有些人会说这是浪费时间...... 好吧,我提出了我的 "own" 设计,我只是想与您分享它以获得一些反馈...

过去,我的公司决定开发和使用自己的 ORM。现在完全是一场灾难。性能、稳定性(以及其他一切)都很糟糕。我们想要切换到另一个 ORM,并且我们想要保持从一个 ORM 切换到另一个的能力。事实上,我们现在正在使用 Sharepoint 2010。这意味着 3.5,因此 NHibernate 3.4 和 Entity Framework 4。我们计划尽快迁移到 SharePoint 2013,以便能够依赖 .net 4.5/EF 6.1/...所以我们很快就必须切换到另一个 ORM。

为此,我开发了一套类实现"IDatabaseContext"接口。

public interface IDatabaseContext : IDisposable
{
    IQueryable<TEntity> AsQueryable<TEntity>()
        where TEntity : EntityBase;
    IList<TEntity> AsList<TEntity>()
        where TEntity : EntityBase;

    IList<TEntity> Find<TEntity>(Expression<Func<TEntity, bool>> predicate)
        where TEntity : EntityBase;
    long Count<TEntity>()
        where TEntity : EntityBase;

    void Add<TEntity>(TEntity entity)
        where TEntity : EntityBase;
    void Delete<TEntity>(TEntity entity)
        where TEntity : EntityBase;
    void Update<TEntity>(TEntity entity)
        where TEntity : EntityBase;
}

例如,我决定使用 NHibernate 作为原型:

public class NHibernateDbContext : IDatabaseContext
{
    private ISession _session = null;

    public NHibernateDbContext(ISessionFactory factory)
    {
        if (factory == null)
            throw new ArgumentNullException("factory");
        _session = factory.OpenSession();
    }

    public IQueryable<TEntity> AsQueryable<TEntity>()
        where TEntity : EntityBase
    {
        return _session.Query<TEntity>();
    }

    public IList<TEntity> AsList<TEntity>()
        where TEntity : EntityBase
    {
        return _session.QueryOver<TEntity>()
                       .List<TEntity>();
    }

    public IList<TEntity> Find<TEntity>(Expression<Func<TEntity, bool>> predicate)
        where TEntity : EntityBase
    {
        ...
    }

    public long Count<TEntity>() 
        where TEntity : EntityBase
    {
        return _session.QueryOver<TEntity>()
                       .RowCountInt64();
    }

    public void Add<TEntity>(TEntity entity)
        where TEntity : EntityBase
    {
        if (entity == null)
            throw new ArgumentNullException("entity");
        UseTransaction(() => _session.Save(entity));
    }

    public void Delete<TEntity>(TEntity entity)
        where TEntity : EntityBase
    {
        ...
    }

    public void Update<TEntity>(TEntity entity)
        where TEntity : EntityBase
    {
        ...
    }

    private void UseTransaction(Action action)
    {
        using (var transaction = _session.BeginTransaction())
        {
            try
            {
                action();
                transaction.Commit();
            }
            catch
            {
                transaction.Rollback();
                throw;
            }
        }
    }

    public void Dispose()
    {
        if (_session != null)
            _session.Dispose();
    }
}

最终,我的服务层(每个实体都与一个服务相关联)依赖于此接口,因此我不会引入对 ORM 技术的依赖。

public class CountryService<Country> : IService<Country>
    where Country : EntityBase
{
    private IDatabaseContext _context;

    public GenericService(IDatabaseContext context)
    {
        if (context == null)
            throw new ArgumentNullException("context");
        _context = context;
    }

    public IList<Country> GetAll()
    {
        return _context.AsList<Country>();
    }

    public IList<Country> Find(Expression<Func<Country, bool>> predicate)
    {
        return _context.Find(predicate);
    }

    ...
}

最终,从服务层调用一个方法,只需要两行代码:

    var service = new CountryService(new NHibernateDbContext(...)));
    or 
    var service = new CountryService(new TestDbContext(...)));
    ...

我觉得这个架构很简单,用起来也很方便。我(还)没有找到任何 drawback/flaws/errors。

那你怎么看?我错过了什么大事吗?有什么我可以改进的吗?

感谢您的所有反馈...

此致, 塞巴斯蒂安

我个人认为您的方法很可靠。但同样令我惊讶的是,您认为这种方法与存储库/工作单元模式非常不同。 您的 IService 层可直接与基本的工作单元层和通过接口实现的存储库层相结合。存储库层应该实现一个接口,并由核心层注入或发现,以避免对底层 ORM 的依赖。

存储库和工作单元层的实际实现将特定于底层 ORM。但是您可以将 RepositoryEF class 替换为 RespositoryNH,反之亦然。 如果做得好并与依赖注入一起使用,核心应用程序永远不知道 ORM 是什么。

问题在于某些人的存储库模式,它们通过允许直接访问 ORM 的代码或泄漏 ORM 结构来泄漏底层 orm。

例如,如果 IREPOSITORY 从 Entity Framework 公开 DBSet 或 Context,则整个应用程序可以锁定到 EF。

例如工作单元接口

 public interface ILuw  {
    IRepositoryBase<TPoco> GetRepository<TPoco>() where TPoco : BaseObject, new();
    void Commit(OperationResult operationResult=null, bool silent=false);
}

和一个 IRepositoryBase 接口

 public interface IRepositoryBase<TPoco> : IRepositoryCheck<TPoco>  where TPoco : BaseObject,new() {
    void ShortDump();
    object OriginalPropertyValue(TPoco poco, string propertyName);   
    IList<ObjectPair> GetChanges(object poco, string singlePropName=null);
    IQueryable<TPoco> AllQ();
    bool Any(Expression<Func<TPoco, bool>> predicate);
    int Count();
    IQueryable<TPoco> GetListQ(Expression<Func<TPoco, bool>> predicate);
    IList<TPoco> GetList(Expression<Func<TPoco, bool>> predicate);
    IList<TPoco> GetListOfIds(List<string>ids );
    IOrderedQueryable<TPoco> GetSortedList<TSortKey>(Expression<Func<TPoco, bool>> predicate,
                                                     Expression<Func<TPoco, TSortKey>> sortBy, bool descending);


    IQueryable<TPoco> GetSortedPageList<TSortKey>(Expression<Func<TPoco, bool>> predicate,
                                                  Expression<Func<TPoco, TSortKey>> sortByPropertyName, 
                                                  bool descending,
                                                  int skipRecords, 
                                                  int takeRecords);

    TPoco Find(params object[] keyValues);
    TPoco Find(string id); // single key in string format, must eb converted to underlying type first. 
    int DeleteWhere(Expression<Func<TPoco, bool>> predicate);
    bool Delete(params object[] keyValues);
    TPoco Get(Expression<Func<TPoco, bool>> predicate);
    TPoco GetLocalThenDb(Expression<Func<TPoco, bool>> predicate);
    IList<TPoco> GetListLocalThenDb(Expression<Func<TPoco, bool>> predicate);
    TU GetProjection<TU>(Expression<Func<TPoco, bool>> predicate, Expression<Func<TPoco, TU>> columns);
    /// <summary>
    /// To use the projection  enter an anonymous type like  s => new { s.Id , s.UserName});
    /// </summary>
    IList<TU> GetProjectionList<TU>(Expression<Func<TPoco, bool>> predicate, Expression<Func<TPoco, TU>> columns);


    bool Add(object poco,bool withCheck=true);
    bool Remove(object poco);
    bool Change(object poco, bool withCheck=true);
    bool AddOrUpdate(TPoco poco, bool withCheck = true);
  }

你的方法很好,但注意事项很少

  1. AsList<TEntity>() 方法是一种滥用方法,许多新开发人员可能会使用此方法来获取数据列表,并且可能会在内存中进行过滤
  2. 如何处理交易。

我个人会使用 phil soady 建议的存储库模式,但我不会在 IRepository 界面中使用那么多方法。

public interface IRepository<T> where T:IEntity
{
    T Single(long id);

    T Save(T entity);

    void Delete(T entity);

    IQueryable<T> FilterBy(Expression<Func<T, bool>> expression);
}

当需要特定类型的查询时,它会在自己的存储库中处理,例如

public interface IContactRepository : IRepository<Contact>
{
    IList<Contact> GetForUser(int userId);
}

和交易处理,我会选择使用 unit of work per request pattern,您不必在每次更新数据库时手动处理交易。一个全局 ActionFilter 就足以实现这一点。