现代 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);
}
你的方法很好,但注意事项很少
AsList<TEntity>()
方法是一种滥用方法,许多新开发人员可能会使用此方法来获取数据列表,并且可能会在内存中进行过滤
- 如何处理交易。
我个人会使用 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 就足以实现这一点。
我阅读了很多关于实体 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);
}
你的方法很好,但注意事项很少
AsList<TEntity>()
方法是一种滥用方法,许多新开发人员可能会使用此方法来获取数据列表,并且可能会在内存中进行过滤- 如何处理交易。
我个人会使用 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 就足以实现这一点。