使用Repository / UoW / DI抽象EntityFramework.dll引用

时间:2012-04-26 11:05:57

标签: entity-framework dependency-injection repository repository-pattern unit-of-work

在使用EF(5.0 Beta Code First)作为我的ORM / DAL时,我一直在研究新项目的最佳实践。关键考虑因素是可测试性,松散耦合设计以及跨存储库的工作单元/事务支持。

据我所知,在EF DbContext中,UoW和DbSet是Repository,在服务层中,您可以在DbContexts SaveChanges()方法协调的多个存储库中进行更改。这是我的示例配置:

public interface IMyContext
{
    IDbSet<Foo> Foos{ get; }

    IDbSet<Bar> Bars { get; }

    int SaveChanges();

    void Dispose();
}

public class EFMyContext : DbContext, IMyContext
{
    private IDbSet<Foo> _foos;
    private IDbSet<Bar> _bars;

    public EFMyContext()
        : base("name=MyConnectionString")
    {
        Database.SetInitializer<EFMyContext>(null);
    }

    public IDbSet<Foo> Foos
    {
        get
        {
            if (_foos == null)
            {
                _foos = this.Set<Foo>();
            }

            return _foos;
        }
    }

    public IDbSet<Bar> Bars
    {
        get
        {
            if (_bars == null)
            {
                _bars = this.Set<Bar>();
            }

            return _bars;
        }
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Configurations.Add(new FooConfiguration());
        modelBuilder.Configurations.Add(new BarConfiguration());

        base.OnModelCreating(modelBuilder);
    }

到目前为止一切都很好。如果我想使用这是一个像这样的上层

public class MyService : IMyService
{
    private IMyContext _context;

    public MyService(IMyContext context)
    {
        _context = context;
    }

    public void DoSomethingWithMyContext()
    {
        // fancy implementation code
    }
}

这里我遇到了问题,因为我直接依赖EntityFramework.dll,因为MyContext暴露了IDBSet类型的属性。这似乎不对,与this问题非常相似。

所以我的问题是,如何抽象出对EF的直接依赖?我考虑引入自己的存储库和工作单元来传递实时上下文,但这会如何影响更改跟踪和所有其他整洁的EF功能?

我正在研究的另一件事是管理上下文生命周期的IoC机制。那时我可以取消UoW吗?

对于模糊不清,我对此处处于研究饱和点,需要在实施前开始确定。

3 个答案:

答案 0 :(得分:2)

问自己一个问题:我的架构应该解决什么?除非你能提供防弹答案,否则为什么从服务程序集中引用EntityFramework.dll是错误的,你就是在浪费时间。

引用EntityFramework.dll实际上没有错。为什么会这样?您的服务不会暴露与EF相关的任何内容,因此它完全可以模拟。如果您还想对服务代码进行单元测试,则必须确保您的服务以抽象方式使用EF。在这种情况下,存储库可以是可行的,但它将是繁重的专用存储库 - 而不是通常提出的a generic nonsense。存储库将隐藏与EF相关的所有内容,您的服务将不知道您的应用程序中存在任何类似EF的内容。它还回答了有关EF功能的问题 - 如果您想让它可测试,您的服务必须完全独立于这些功能。例如,您不应该使您的服务代码依赖于LINQ-to-Entities或延迟加载。它只会导致troubles in testing

即使您实现了存储库,如果它们与您的服务位于同一个程序集中,并且该程序集引用了EntityFramework.dll,那么仍然没有错。

答案 1 :(得分:1)

服务层中,您实际上不需要使用IQueryable<T>

因为这样你就会与支持 LINQ ORM 紧密结合。 所有 LINQ 查询都应封装在具体的 DAL 中。

更好的是,您可以在服务层中定义 DAL - 相关的存储库:

public interface IRepository<T>
{
    T GetById(int id);
    IEnumerable<T> GetAll();

    // Filter is some DTO that will be mapped to 
    // concrete LINQ query inside your DAL
    IEnumerable<T> GetFiltered(Filter<T> filter);

    void Add(T item);
    void Update(T item);
    void Remove(T item);
}

更新:如果你爱上了LINQ,你可以为ObjectContext和ObjectSet定义自己的接口:

public interface IObjectContext : IUnitOfWork
{
    IObjectSet<T> Set<T>() where T : class;
}

public interface IObjectSet<T> : IObjectQuery<T>
    where T : class
{
    void Remove(T entity);
    void Add(T entity);
}

public interface IObjectQuery<out T> : IQueryable<T>, IQueryableWrapper
     where T : class
{
    IObjectQuery<T> Include(string path);
}

然后你可以为EF编写适配器。 您还应该将查询转换层添加到适配器,因为EF无法理解表达式树中的接口。 以下是QueryTranslator的示例:https://stackoverflow.com/a/1846030/1275399

答案 2 :(得分:0)

你很可能在IMyContext上没有DbSet属性,并且只在实现上有dbsets。 IMyContext应仅用于了解保存上下文(并将其丢弃)以及其他任何内容。 因此,在存储库中,您可以拥有一个知道如何与sql通信的策略,比如说使用ef并让它处理dbsets的一部分。只有当你真的计划搬出EF并在将来用别的东西替换它时,你才需要这样做。

您可以找到有关此here

的方法的更多详细信息