24

我使用实体框架数据库优先方法为 MVC 应用程序生成 DbContext / POCO 模型。我想避免在我的控制器中依赖 DbContext 以使我能够根据需要切换到另一个持久性提供程序(例如用于单元测试目的)。

为此,我想使用 Castle Windsor IoC 容器。我计划将 DbContext 注册为 IUnitOfWork 服务并注册一个通用 IRepository 服务,我将使用它的实现来访问和使用模型中的聚合根。

我是 Windsor 的新手,无法找到有关在 EF 中使用它的更多信息,我有几个问题:

  • 如果我想将 EF 与应用程序分离,这是一种合理的方法吗?
  • 如何安装/注册 IUnitOfWork 和通用 IRepository 服务?
4

1 回答 1

25

所以,一些结论。我想我会写下来,以防其他人尝试一起使用/单元测试 EF、Windsor 和 MVC。

首先,由于 DbContext 实现了 Repository 和 Unit of Work 模式,您需要考虑这些实现是否会提供服务,或者您是否需要创建自己的实现。

我选择创建自己的存储库,遵循 DDD 模式:每个聚合根一个。原因:封装查询代码,防止泄露到应用层,测试应用控制器时更容易mock。我创建了一个基于IRepository<TEntity>. 那里有很多例子。我发现这是一个很好的:http ://architects.dzone.com/articles/implementing-repository

另一方面,我决定放弃 IUnitOfWork 服务,而选择默认实现。但是,我创建了一个 IDbContext 抽象(不知道为什么微软自己不这样做),以便在测试 Repository 服务时可以模拟 DbContext。

我只给了 IDbContext 我想在存储库中使用的 DbContext 成员。所以:

public interface IDbContext: IDisposable
{
    Database Database { get; }
    DbEntityEntry Entry(object entity);
    IDbSet<TEntity> Set<TEntity>() where TEntity : class;
    int SaveChanges();
}

然后,我为我的 IDbContext 和 IRepository 服务创建了 Windsor 工具和安装程序:

public class EntityFrameworkFacility: AbstractFacility
{
    protected override void Init()
    {
        Kernel.Register(Component.For<IDbContext>()
                                 .ImplementedBy<MyEntities>()
                                 .LifestylePerWebRequest(),
                        Component.For(typeof(IRepository<>))
                                 .ImplementedBy(typeof(Repository<>))
                                 .LifestylePerWebRequest());
    }
}

public class PersistenceInstaller : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.AddFacility<EntityFrameworkFacility>();
    }
}

最后一步是扩展实体框架上下文类以实现 IDbContext,并隐藏 Set() 方法以返回 IDbSet 而不是 DbSet:

public partial class MyEntities : IDbContext
{
    public new IDbSet<TEntity> Set<TEntity>() where TEntity : class
    {
        return base.Set<TEntity>();
    }
}

有了所有这些(以及 Windsor 文档中说明的 ControllerFactory 注册),让 Windsor 根据需要将 IRepository 对象(或 IDbContext)注入控制器构造函数变得微不足道:

public ControllerBase(IRepository<Contact> repo)
{
    _repo = repo;
}

在存储库单元测试中,可以使用模拟 IDbContext 支持真实的存储库实例:

mocks = new MockRepository();
context = mocks.StrictMock<IDbContext>();
repo = new Repository<Contact>(context);

在 Controller 单元测试中,可以使用模拟存储库:

mocks = new MockRepository();
repo = mocks.StrictMock<IRepository<Contact>>();
ContactController controller = new ContactController(repo);
于 2013-03-28T20:06:35.840 回答