public interface IRepository<T> : IDisposable where T : class
{
IQueryable<T> Fetch();
IEnumerable<T> GetAll();
IEnumerable<T> Find(Func<T, bool> predicate);
T Single(Func<T, bool> predicate);
T First(Func<T, bool> predicate);
void Add(T entity);
void Delete(T entity);
void SaveChanges();
}
使用上述方法,我将像存储库一样,然后使用上面编写的方法实现存储库。 br />我认为这使我的测试摆脱了DbContext依赖。
思想2
public class RepositoryBase<TContext> : IDisposable where TContext : DbContext, new()
{
private TContext _DataContext;
protected virtual TContext DataContext
{
get
{
if (_DataContext == null)
{
_DataContext = new TContext();
}
return _DataContext;
}
}
public virtual IQueryable<T> GetAll<T>() where T : class
{
using (DataContext)
{
return DataContext.Set<T>();
}
}
public virtual T FindSingleBy<T>(Expression<Func<T, bool>> predicate) where T : class
{
if (predicate != null)
{
using (DataContext)
{
return DataContext.Set<T>().Where(predicate).SingleOrDefault();
}
}
else
{
throw new ArgumentNullException("Predicate value must be passed to FindSingleBy<T>.");
}
}
public virtual IQueryable<T> FindAllBy<T>(Expression<Func<T, bool>> predicate) where T : class
{
if (predicate != null)
{
using (DataContext)
{
return DataContext.Set<T>().Where(predicate);
}
}
else
{
throw new ArgumentNullException("Predicate value must be passed to FindAllBy<T>.");
}
}
public virtual IQueryable<T> FindBy<T, TKey>(Expression<Func<T, bool>> predicate,Expression<Func<T, TKey>> orderBy) where T : class
{
if (predicate != null)
{
if (orderBy != null)
{
using (DataContext)
{
return FindAllBy<T>(predicate).OrderBy(orderBy).AsQueryable<T>(); ;
}
}
else
{
throw new ArgumentNullException("OrderBy value must be passed to FindBy<T,TKey>.");
}
}
else
{
throw new ArgumentNullException("Predicate value must be passed to FindBy<T,TKey>.");
}
}
public virtual int Save<T>(T Entity) where T : class
{
return DataContext.SaveChanges();
}
public virtual int Update<T>(T Entity) where T : class
{
return DataContext.SaveChanges();
}
public virtual int Delete<T>(T entity) where T : class
{
DataContext.Set<T>().Remove(entity);
return DataContext.SaveChanges();
}
public void Dispose()
{
if (DataContext != null) DataContext.Dispose();
}
}
我现在可以拥有
CustomerRepository : RepositoryBase<Mycontext>,ICustomerRepository
ICustomerRepository
给了我定义GetCustomerWithOrders
之类的东西的选项。 我对采用哪种方法感到困惑。首先需要对实施进行回顾,然后再提出评论,就测试和可扩展性而言,哪一种将是更容易的方法。
#1 楼
您应该在接口上使用Expression<Func<T, bool>>
作为谓词,并让RepositoryBase
实现该接口。仅通过使用
Func
,您就不会转换为L2E等,但是必须先枚举整个数据库表,然后才能评估Func
。该接口可以被模拟,因此可以在没有物理数据库的情况下进行单元测试,也可以与其他ORM一起使用。
通常更喜欢将
SaveChanges
和DbContext
保留在单独的IUnitOfWork
实现中。您可以将UnitOfWork
传递给存储库构造函数,后者可以访问UOW上的内部属性,从而公开DbContext
。这样做,您可以在存储库之间共享相同的
DbContext
,并批量更新多个实体根不同类型的。您还将保留较少的开放连接,这是涉及数据库时最昂贵的资源。不要忘记它们可以共享事务。 :)
因此,您不应在存储库中放置DbContext,存储库实际上根本不需要是一次性的。但是UnitOfWork / DbContext必须以某种方式处理。
也将
OrderBy
谓词抛弃到FindBy
。由于您返回IQueryable
,并将Expression
用作谓词,因此您可以在调用之后继续构建Queryable。例如repo.FindBy(it => it.Something == something).OrderBy(it => it.OrderProperty)
。枚举时,它仍将转换为“从[表]的[谓词]顺序中选择[表]中的[字段]。否则看起来不错。
这里是几个很好的例子:
http://blog.swink.com.au/index.php/c-sharp/generic-repository-for-entity-framework-4-3-dbcontext- with-code-first /
http://www.martinwilley.com/net/code/data/genericrepository.html
http://www.mattdurrant.com / ef-code-first-with-repository-and-of-work-work-patterns /
这是我的操作方法:业务组装
除了BCL和其他可能的模型(接口/ dtos)之外,没有引用。
public interface IUnitOfWork : IDisposable
{
void Commit();
}
public interface IRepository<T>
{
void Add(T item);
void Remove(T item);
IQueryable<T> Query();
}
// entity classes
业务测试程序集
仅引用模型/通用/业务程序集
[TestFixture]
public class BusinessTests
{
private IRepository<Entity> repo;
private ConcreteService service;
[SetUp]
public void SetUp()
{
repo = MockRepository.GenerateStub<IRepository<Entity>>();
service = new ConcreteService(repo);
}
[Test]
public void Service_DoSomething_DoesSomething()
{
var expectedName = "after";
var entity = new Entity { Name = "before" };
var list = new List<Entity> { entity };
repo.Stub(r => r.Query()).Return(list.AsQueryable());
service.DoStuff();
Assert.AreEqual(expectedName, entity.Name);
}
}
实体框架实现程序集
参考模型和实体框架/ System.Data.Entity程序集
public class EFUnitOfWork : IUnitOfWork
{
private readonly DbContext context;
public EFUnitOfWork(DbContext context)
{
this.context = context;
}
internal DbSet<T> GetDbSet<T>()
where T : class
{
return context.Set<T>();
}
public void Commit()
{
context.SaveChanges();
}
public void Dispose()
{
context.Dispose();
}
}
public class EFRepository<T> : IRepository<T>
where T : class
{
private readonly DbSet<T> dbSet;
public EFRepository(IUnitOfWork unitOfWork)
{
var efUnitOfWork = unitOfWork as EFUnitOfWork;
if (efUnitOfWork == null) throw new Exception("Must be EFUnitOfWork"); // TODO: Typed exception
dbSet = efUnitOfWork.GetDbSet<T>();
}
public void Add(T item)
{
dbSet.Add(item);
}
public void Remove(T item)
{
dbSet.Remove(item);
}
public IQueryable<T> Query()
{
return dbSet;
}
}
集成测试程序集
引用所有内容
[TestFixture]
[Category("Integrated")]
public class IntegratedTest
{
private EFUnitOfWork uow;
private EFRepository<Entity> repo;
[SetUp]
public void SetUp()
{
Database.SetInitializer(new DropCreateDatabaseAlways<YourContext>());
uow = new EFUnitOfWork(new YourContext());
repo = new EFRepository<Entity>(uow);
}
[TearDown]
public void TearDown()
{
uow.Dispose();
}
[Test]
public void Repository_Add_AddsItem()
{
var expected = new Entity { Name = "Test" };
repo.Add(expected);
uow.Commit();
var actual = repo.Query().FirstOrDefault(e => e.Name == "Test");
Assert.IsNotNull(actual);
}
[Test]
public void Repository_Remove_RemovesItem()
{
var expected = new Entity { Name = "Test" };
repo.Add(expected);
uow.Commit();
repo.Remove(expected);
uow.Commit();
Assert.AreEqual(0, repo.Query().Count());
}
}
评论
\ $ \ begingroup \ $
表达式v / s函数..nice catch .. RepositoryBase具有正确的谓词。 OrderBy在我看来也没用。将删除它。根据传递上下文或传递上下文和实体类型作为Type参数,您提供的链接具有两种不同的实现。哪一个更好,或者两者相同,只是样式问题。我莫名其妙地无法解决这个问题。
\ $ \ endgroup \ $
– ashutosh raina
2012年11月27日在18:06
\ $ \ begingroup \ $
DbContext本身就是一个UoW,那么拥有一个单独的UoW有什么好处呢?
\ $ \ endgroup \ $
– ashutosh raina
2012年11月27日18:09
\ $ \ begingroup \ $
@ashutoshraina可能抽象出UoW本身。人们认为,他们最终将不得不更改其ORM或数据存储。以我的经验,它永远不会发生,因此抽象越少越好。
\ $ \ endgroup \ $
– maxbeaudoin
2012年11月27日在21:24
\ $ \ begingroup \ $
通常最好模拟一个除了BCL本身之外不依赖库的接口。即使您不会更改ORM,也可以在不依赖BCL的情况下运行测试。因此,需要包装DbContext。反正不会太多。为了说明这一点,我们对EF 1和来自Codeplex的EF Poco Adapter项目进行了组合。迁移到EF 4时,我们不得不将旧的上下文和dbset实现换成新的,并从拥有自己的UOW和Repos中受益。
\ $ \ endgroup \ $
– Lars-Erik
2012年11月28日10:59
\ $ \ begingroup \ $
@ Lars-Erik参见我的编辑2。
\ $ \ endgroup \ $
– ashutosh raina
2012年11月28日14:13
#2 楼
重塑轮子如果需要通用存储库类,请对基本类型使用IObjectSet并在需要时实施(在生产代码中不需要,在测试中,您可以使用IList <>作为后端)。
关于抽象的知识
由于其他人处于共享上下文中的事务性时刻,因此存储库不应具有Save方法,因为您(或另一个人)可以互相欺骗。请为此创建一个独立的IUnitOfWork。
interface IUnitOfWork : IDisposable {
void Commit();
void Rollback();
}
interface IDataStore {
IUnitOfWork InUnitOfWork();
IObjectSet<T> Set<T>() where T : class;
}
using (var uow = _data.InUnitOfWork()) {
_data.Set<Order>.AddObject(order);
_data.Set<SomethingElse>.Remove(otherObject);
uow.Comit();
}
棘手的部分是如何处理事务。我用一些方法(BeginTransaction(),InTransaction,Commit(),Rollback())创建了一个ISession接口,并创建了一个默认实现。在我的默认IUnitOfWork实现中,构造函数接收一个ISession实例并使用该实例开始事务。 ISession实现有一个内部计数器,用于计算有多少其他参与者启动了自己的事务。如果共享存储中的IUnitOfWork已提交,则如果有人说RollBack,则计数器递减1,然后所有内容都会回滚。行为:默认值为Rollback()而不是Commit()
这样,您可以轻松地测试代码。
评论
\ $ \ begingroup \ $
IObjectSet是特定于MS api的接口,不是吗?如果您也没有对存储库进行抽象,则交换为NHibernate或RavenDB,就无法重用它。
\ $ \ endgroup \ $
– Lars-Erik
2012年12月3日在1:19
\ $ \ begingroup \ $
+1的回滚@ Lars-Erik如果我们在UnitOfWork中有Save,就不应该回滚吗?
\ $ \ endgroup \ $
– ashutosh raina
2012年12月3日,下午3:43
\ $ \ begingroup \ $
@ Lars-Erik:System.IDisposable是Microsoft特有的东西,具有.NET的所有功能。 IObjectSet
\ $ \ endgroup \ $
– Peter Kiss
2012年12月3日,下午6:39
\ $ \ begingroup \ $
AFAIK,除非您创建一个transactionscope实例并对此进行回滚,否则DbContext不会回滚。我使用UOW Dispose进行回滚,因为无论如何我可能都不希望继续进行转换。 @Peter我想我应该研究IObjectSet,但是我仍然很喜欢将它称为IRepository,因为它是一个已知且经过验证的模式名称。 IObjectSet不是。这是20行代码,那么谁真正在乎呢?
\ $ \ endgroup \ $
– Lars-Erik
2012年12月3日,18:50
\ $ \ begingroup \ $
20行不必要的代码会在代码库中造成极大混乱,为什么要编写加上代码(如果已经存在)? .NET的另一处是任何.NET应用程序中都有一个IObjectSet
\ $ \ endgroup \ $
– Peter Kiss
2012年12月3日在20:24
评论
为什么不同时拥有这两个资源,并让您的RepositoryBase实现IRepository接口?@dreza然后,IRepository应该具有诸如add,save,delete之类的方法,还是应该具有find,get,fetch方法?我猜两者都在解决不同的问题,其中一组方法是查询,另一种是事务逻辑,接下来的问题是使RepositoryBase实现IRepository接口的好处/缺点是什么?
我将使存储库具有SingleOrDefault(),GetAll(),FirstOrDefault()之类的方法,而不必执行.Fetch()。SingleOrDefault()等
关于编辑2:由于DbContext不是抽象的或没有任何好的接口,因此必须“集成” RepositoryBase的测试,但是一个不错的技巧是在这些测试中使用Database.SetInitializer(new DropCreateDatabaseAlways
@ Lars-Erik因此,将通过与数据库的集成测试来测试RepositoryBase。但是,我应该能够通过单元测试来测试我的业务逻辑。我的POCO类位于单独的程序集中,存储库位于单独的程序集中。我的单元测试项目只是对存储库的引用。那是正确的结构吗?