是否使用存储库抽象而不是实体框架良好实践?

Mod*_*ika 8 .net entity-framework

我即将开始一个新项目,它将使用实体框架(EF)作为ORM来链接SQL Server.我一直在做大量的阅读,以了解在抽象方面使用EF的最佳方法,并最终使我的代码可测试.

问题是我做的阅读越多,我就越困惑.

我认为这是一个很好的方式:

我打算沿着存储库路线走下去,制作它们,这样它们很容易通过接口注入,并且易于模拟用于测试目的,从我似乎发现的很多例子中,有很多人发誓这种方法.对于与EF交互的存储库部分,我只是要进行集成测试,因为我的Repos中没有业务逻辑,我会将其转移到服务/控制器类来处理它.

有人说存储库模式是一种无根据的抽象

http://ayende.com/blog/3955/repository-is-the-new-singleton

还有其他链接,但我不想用太多的问题充斥这个问题

我得到了这个,我明白EF和它的实现本质上是一个抽象,但对我来说它似乎是一个具体的(它击中了应用程序范围之外的东西......数据库)对我来说问题是它推动了测试问题(至少在初始架构中),我可能会在我的服务中与数据上下文和工作单元进行交互,但感觉就像是在混合数据访问登录与业务逻辑等级然后我怎么做单元测试呢?

我似乎已经阅读了很多,以至于我现在没有明确的方向和不可避免的编码器块已经开始.我正在努力寻找一种干净的方法,我可以测试.

更新 - 解决方案我将开始

我走了一条与simon给出的答案略有不同的路径,但感谢我读过的一篇文章,但他再次提交,所以我又重新审视了.首先我使用Code First,因为我有一个现有的数据库,并且出于某种原因不喜欢设计器工具.我创建了一些简单的POCO对象和一些映射,为了简单起见,我只是展示一个POCO/Mapping,什么不是.

POCO

public abstract class BaseEntity<T>
{
    [Key]
    public T Id { get; set; }
    public string Name { get; set; }
    public DateTime CreatedOn { get; set; }
}    

public class Project : BaseEntity<int>
{
    public virtual ICollection<Site> Sites { get; set; }
    public bool Active { get; set; }
}    
Run Code Online (Sandbox Code Playgroud)

工作单元接口

public interface IUnitOfWork
{
    IDbSet<Project> Projects { get; }

    void Commit();
}
Run Code Online (Sandbox Code Playgroud)

上下文

internal class MyContext : DbContext
{
    public MyContext(string connectionString)
        : base(connectionString)
    {

    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Configurations.Add(new ProjectMap());
    }
}
Run Code Online (Sandbox Code Playgroud)

具体单位工作

public class UnitOfWork : IUnitOfWork
{
    readonly MyContext _context;
    const string ConnectionStringName = "DBConn";

    public UnitOfWork()
    {
        var connectionString = ConfigurationManager.ConnectionStrings[ConnectionStringName].ConnectionString;
        _context = new FosilContext(connectionString);
    }

    public IDbSet<Project> Projects
    {
        get { return _context.Set<Project>(); }
    }

    public void Commit()
    {
        _context.SaveChanges();
    }
}
Run Code Online (Sandbox Code Playgroud)

基本测试代码

        var _repo = new UnitOfWork().Projects;
        var projects = from p in _repo
                       select p;

        foreach (var project in projects)
        {
            Console.WriteLine(string.Format("{0} - {1} - {2} - {3})", project.Id, project.Name, project.Active.ToString(), project.CreatedOn.ToShortDateString()));
        }

        Console.Read();
Run Code Online (Sandbox Code Playgroud)

UnitOfWork是自解释的,但基于具体的上下文,我可以创建一个假的IUnitOfWork并将其传递到一个假的IDBSet进行测试.对我来说,这可能会让我在建筑上咬我更多,但我没有在这个抽象的EF之外有一个回购(我想).正如文章解释的那样,IDbSet相当于一个Repo,所以我正在使用它.我有点隐藏在UoW背后的自定义上下文,但我将看到它是如何平移的.

我现在在想的是我可以在服务层中使用它,它将封装检索数据和业务规则,但应该是单元可测试的,因为我可以伪造EF特定项目.希望这会让我的控制器非常精简,但我们会看到:)

Sim*_*ger 8

不要忘记,如果你正在使用实体框架,DbContext 类似于一个的UnitOfWork并DbSet 类似于一个仓库.我认为,这个问题引发了个人意见,可能不适合Stack Overflow,但我认为如果你打算使用Entity Framework,你也可以接受这种模式.进一步包装DbContextDbSets实际上是为了便于单元测试以及在某种程度上依赖注入,在具体类上给出了非常薄的抽象.

MSDN(Testability and Entity Framework 4.0)上有一个主题可以为您提供一个很好的起点.这里和那里有一些资源建议如何在实体框架的上下文中实现这种模式.在大多数情况下,您将使用DbContext执行SaveChanges()DbSet<T>IQueryable<T>实现的帮助下执行CRUD操作DbSet<T>.您可以实现您的IUnitOfWork来模仿您需要的内容DbContext和相同的内容DbSet.

在该方面的实施方式几乎是在方法1对1映射DbContextDbSet<T>,但也可以实现实现内存中基于自定义IUnitOfWork IRepository<T>(使用HashSetS表示例如)便于对查询逻辑的单元测试可查询组件.存储库模式是个好主意吗?这是值得商榷的.这实际上取决于项目和您需要的灵活性以及您希望不同层执行的操作(以及您不想要的内容).

编辑以回答评论: 您的UnitOfWork实现不应继承,DbContext而是创建实例,将其保持私有并将其自己的方法调用委托给上下文.您的Repository<T>实现可以合理地采用DbSet<T>内部构造函数.模型优先方法的一大优势是给予装配组织的自由.我通常最终做的是将模型和DbContext分成两个单独的程序集,后者引用前者.这种情况看起来像:

1 - 您的Models.dll程序集包含您的POCO

2 - 您的Data.dll程序集包含您的接口和实现.您还可以(例如)Data.Core.dll包含您的数据访问层的接口和常用实用程序,并具有可互换的Data.Entity.dll(或Data.List.dll)供您实际实施.如果我们选择第一个选项,它可能看起来像:

public interface IUnitOfWork { /* Your methods */ }

public interface IRepository<T> where T : class { /* Your methods */ }

internal class YourDbContext : DbContext { /* Your implementation */ }

public class YourDatabaseContext : IUnitOfWork
{
    private readonly YourDbContext dbContext;

    public YourDatabaseContext()
    {
        // You could also go with the Lazy pattern here to defer creation
        dbContext = new YourDbContext();
    }
}

internal class DbSetRepository<T> : IRepository<T> where T : class
{
    private readonly DbSet<T> dbSet;

    public DbSetRepository(DbSet<T> dbSet)
    {
        // You could also use IDbSet<T> for a toned down version
        this.dbSet = dbSet;
    }
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,只有您的接口和您的IUnitOfWork实现在程序集外部可见(例如,如果您想使用依赖注入).这并不重要,实际上是设计选择的问题.因为您DbContext的内部实现定义将与您的POCO"链接",所有内容都将在您的配置中连接起来.