现代ORM与存储库/ IoW模式

时间:2015-04-25 11:26:30

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

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

过去,我的公司决定开发和使用它自己的ORM。它现在已经彻底发生了灾难。性能,稳定性(以及其他一切)都非常糟糕。我们想切换到另一个ORM,我们希望保持从ORM切换到另一个ORM的能力。实际上,我们现在正在使用Sharepoint 2010.它意味着3.5,因此NHibernate 3.4和实体框架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(...)));
    ...

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

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

感谢您的反馈......

此致 的Sebastien

2 个答案:

答案 0 :(得分:0)

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

您的存储库和工作单元层的实际实现将特定于基础ORM。但是你可以用RespositoryNH替换RepositoryEF类,反之亦然。 如果正确完成并与依赖注入一起使用,Core应用程序永远不会知道ORM是什么。

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

例如,如果IREPOSITORY从实体框架中暴露了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 :(得分:0)

您的方法合理,但几乎没有考虑因素

  1. AsList<TEntity>()方法是一种滥用方法,许多新开发人员可能会使用此方法获取数据列表,并可能会在内存中进行过滤
  2. 如何处理交易。
  3. 就个人而言,我建议使用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就足以实现这一目标。