【问题标题】:Modern ORM vs Repository/IoW pattern现代 ORM 与存储库/IoW 模式
【发布时间】:2015-04-26 15:52:47
【问题描述】:

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

过去,我的公司决定开发和使用自己的 ORM。现在完全是一场灾难。性能、稳定性(以及基本上其他一切)都很糟糕。我们想切换到另一个 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(...)));
    ...

我发现这种架构非常简单且使用起来非常方便。我(还)没有发现任何缺点/缺陷/错误。

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

感谢您的所有反馈...

问候, 塞巴斯蒂安

【问题讨论】:

  • session的管理不常用,什么样的应用会用到这个base?
  • SharePoint WebParts 将主要依赖这一层。此外,很少有应用程序也可能使用此层(用于不同数据源之间的同步目的)。 “管理会话”是什么意思?

标签: entity-framework nhibernate orm repository-pattern unit-of-work


【解决方案1】:

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

您的存储库和工作单元层的实际实现将特定于底层 ORM。但是您可以用 RespositoryNH 替换 RepositoryEF 类,反之亦然。 如果处理得当并与依赖注入一起使用,Core 应用程序永远不会知道 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);
  }

【讨论】:

  • 感谢您的所有意见。不过我还有一个小问题。我不明白为什么使用 UoW 模式是相关的。事实上,大多数时候,您最终会执行更新 > 保存等操作;添加>保存;删除 > 保存。那么在这里,使用 UoW 模式有什么好处呢?
  • 工作单元模式允许您在一次提交中合并多个存储库的更新。存储库模式通常只处理数据库中的 1 个表。因此,table2 中的 update1 table1 和 update2 使用工作单元 x 一起提交。立面 IUoW 允许模拟或替换 UOW 工具
【解决方案2】:

您的方法是合理的,但考虑的很少

  1. AsList&lt;TEntity&gt;() 方法是一种滥用方法,许多新开发人员可能会使用此方法来获取数据列表,并且可能会在内存中进行过滤
  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 就足以实现这一点。

【讨论】:

  • 您好,谢谢您的回答。 ActionFilter 似乎是一个非常干净和简单的处理事务的解决方案。但是,我确定我可以在面向 .net 3.5 的项目中使用 ActionFilters?
  • 动作过滤器不是一个新概念,从 MVC 的第一个版本开始就可以使用,如果你因为某种原因不能使用它们,你总是可以使用 httpmodule
猜你喜欢
  • 1970-01-01
  • 2014-03-17
  • 2011-06-28
  • 1970-01-01
  • 1970-01-01
  • 2010-09-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多