思想1

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之类的东西的选项。

我对采用哪种方法感到困惑。首先需要对实施进行回顾,然后再提出评论,就测试和可扩展性而言,哪一种将是更容易的方法。

评论

为什么不同时拥有这两个资源,并让您的RepositoryBase实现IRepository接口?

@dreza然后,IRepository应该具有诸如add,save,delete之类的方法,还是应该具有find,get,fetch方法?我猜两者都在解决不同的问题,其中一组方法是查询,另一种是事务逻辑,接下来的问题是使RepositoryBase实现IRepository接口的好处/缺点是什么?

我将使存储库具有SingleOrDefault(),GetAll(),FirstOrDefault()之类的方法,而不必执行.Fetch()。SingleOrDefault()等

关于编辑2:由于DbContext不是抽象的或没有任何好的接口,因此必须“集成” RepositoryBase的测试,但是一个不错的技巧是在这些测试中使用Database.SetInitializer(new DropCreateDatabaseAlways ())。这意味着您始终有一个用于集成测试的新数据库。将具体的Repository和UnitOfWork类和测试移到单独的程序集中也很有用,因此在业务逻辑测试和类中没有那些依赖项。否则,您总是可以购买TypeMock,它可以模拟或多或少的任何东西。

@ Lars-Erik因此,将通过与数据库的集成测试来测试RepositoryBase。但是,我应该能够通过单元测试来测试我的业务逻辑。我的POCO类位于单独的程序集中,存储库位于单独的程序集中。我的单元测试项目只是对存储库的引用。那是正确的结构吗?

#1 楼

您应该在接口上使用Expression<Func<T, bool>>作为谓词,并让RepositoryBase实现该接口。
仅通过使用Func,您就不会转换为L2E等,但是必须先枚举整个数据库表,然后才能评估Func
该接口可以被模拟,因此可以在没有物理数据库的情况下进行单元测试,也可以与其他ORM一起使用。

通常更喜欢将SaveChangesDbContext保留在单独的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如果我们在U​​nitOfWork中有Save,就不应该回滚吗?
\ $ \ endgroup \ $
– ashutosh raina
2012年12月3日,下午3:43

\ $ \ begingroup \ $
@ Lars-Erik:System.IDisposable是Microsoft特有的东西,具有.NET的所有功能。 IObjectSet 对TEntity没有特定的限制,只是一个简单的“成为一个类”。是的,您可以并且应该重复使用它。
\ $ \ 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