多个ORM的灵活存储库体系结构

时间:2018-11-05 12:12:30

标签: c# asp.net asp.net-core repository data-access-layer

我以Dapper作为我们的ORM开始了我的项目,因为我们希望快速,简单地启动并运行。我现在处于要升级的阶段,因此我们不打算通过连接等在代码中编写SQL负载。我决定要使用Entity Framework(我不想讨论您是否应该或不应使用带有EF的存储库)。但是,我没有时间一次性重构所有存储库。我正在使用接口,但是到目前为止,接口已经非常特定于每个POCO对象,使用诸如GetFooUpdateFooGetFooByBarId之类的方法并不是很好。我想将CRUD操作归类为通用方法名称(选择,更新,删除)。

这给我带来了第一个问题,即如果我更改存储库方法的名称,那么我必须去更改所有项目中的所有引用并部署所有听起来不太有趣或不安全的应用程序我。我希望过渡是逐步的。

如果将来我想保留Dapper或任何其他ORM的功能,而又不想永远限制自己使用EF,那么我也想保留。

我现在开始创建EF信息库,我的项目结构如下:

  • 核心(POCO模型和其他通用代码)。
  • DAL(存储库接口)
  • DAL.Dapper(存储库的Dapper实现)
  • DAL.EntityFramework(存储库的EF实现)
  • 使用回购实现的各种项目/应用程序

我已经在基本DAL项目中创建了一个基本存储库,如下所示:

public interface IBaseRepository<T> where T : class
{
    Task<T> Get(object id);
    Task<IQueryable<T>> GetMany(Expression<Func<T, bool>> where, params RepositoryFlags[] flags);
    Task<IQueryable<T>> GetMany(Expression<Func<T, bool>> where, Expression<Func<T, T>> fields, params RepositoryFlags[] flags);
    Task<int> Upsert(T item);
    Task Delete(T item);
}

问题在于,GetMany方法适用于EF,但不适用于Dapper(将表达式转换为SQL并不是我真正想要的东西)。我希望只能将这些方法用于EF存储库。我花了一些时间来思考如何执行此操作,但是随后我还没有完全致力于新的EF实现,因为我不想强迫自己在不需要的Dapper存储库中实现方法。

我刚刚想到了一个新主意,我认为该主意将使我能够通过DAL基本接口共享通用方法,同时还允许我在需要时拥有额外的ORM特定功能,并且可以与之一起使用依赖注入。

DAL项目

IBaseRepository

public interface IBaseRepository<T>
    where T : class
{
    Task<T> Get(object id);
    Task<int> Upsert(T item);
    Task Delete(T item);
}

IExtendedBaseRepository

public interface IExtendedBaseRepository<TModel, TExtensions> : IBaseRepository<TModel>
    where TModel : class
    where TExtensions : class
{
    TExtensions Extensions { get; set; }
}

DAL.Dapper项目

FooRepository

public class FooRepository : IBaseRepository<Foo>
{
    // CRUD implementations...
    // ...
}

DAL.EntityFramework项目

FooExtensions

public class SupplementRepositoryExtensions
{
    private readonly TapContext context;

    public SupplementRepositoryExtensions(TapContext context)
    {
        this.context = context;
    }

    public IEnumerable<Foo> GetAll()
    {
        ...
    }

    public Task<IQueryable<Foo>> GetMany(Expression<Func<Foo, bool>> where)
    {
        ...
    }
}

FooRepository

public class FooRepository : EFBaseRepository<Foo>, IExtendedBaseRepository<Foo, FooExtensions>
{
    public FooExtensions Extensions { get; set; }

    public FooRepository(DbContext context, FooExtensions extensions) : base(context)
    {
        this.Extensions = extensions;
    }
}

(EFBaseRepository包含EF CRUD实现)

应用程序1中的DI

services.AddTransient<IBaseRepository<Foo>, DAL.Dapper.Repostories.FooRepository>();

应用程序2中的DI

services.AddTransient<FooExtensions>();
services.AddTransient<IExtendedBaseRepository<Foo, DAL.EntityFramework.RepositoryExtensions.FooExtensions>, DAL.EntityFramework.Repositories.FooRepository>();

在应用程序2中的使用

public class FooService
{
    private readonly IExtendedBaseRepository<Foo, FooExtensions> fooRepository;

    public FooService(IExtendedBaseRepository<Foo, FooExtensions> fooRepository)
    {
        this.fooRepository = fooRepository;
    }

    public async Task Bar()
    {
        var foos = await this.fooRepository.Extensions.GetAll();

        ...
    }
}

我相信这给了我想要的灵活性,同时保持了ORM之间的一定程度的通用性。我只是在寻找有关此体系结构的意见。有什么好处吗?我是否忽略了任何明显的问题,或者这是一种可怕的反模式?欢迎提出建议或改进。

2 个答案:

答案 0 :(得分:4)

这并不是真正的答案,但我决定提出一个答案,因为它远远超过了注释的字符数限制。

说实话,我认为Dappers和EFs的抽象查询级别差异太大,无法通过公共层以不会过多影响(并使您的应用程序逻辑复杂化)的方式进一步抽象它们。我们的总体应用程序体系结构与您的总体体系结构非常相似(Core <= Data <=(Data.Impl1,Data.Impl2,...)),并且我们还考虑了支持OR-Mappers的不同功能级别。故事的结尾是,我们只同意支持建立在IQueryable<T>之上的OR-Mappers,以保持其可维护性。因此,我们的存储库/ UoW抽象大致如下:

public interface IDataSet<T> : IQueryable<T>
    where T : class, new()
{
    T AddOrUpdate(T entity);
    void Remove(T entity);
    void Commit();
}

public class EntityFrameworkDataSet<T> : IDataSet<T>
    where T : class, new()
{
    private readonly IDbSet<T> _underlyingSet;

    public EntityFrameworkDataSet(DbContext ctx)
    {
        _underlyingSet = ctx.Set<T>();
    }

    //Implement IDataSet<T> 
    //Implement IQueryable<T> redirecting to IDbSet<T>
}

我们只是不支持类似Dapper的框架,而纯粹依靠LINQ来查询数据。

  

我是否忽略了任何明显的问题

我想您已经解决了这个问题,但是:每个OR-Mapper都为您的域模型带来了自己的一套限制-主要是M:N或主键/外键。因此,支持多个OR-映射器意味着将多个限制集引入您的域模型。您必须决定是否仍可以在这些边界内将需求映射到合理的(并且仍然是面向对象的)域模型。

答案 1 :(得分:2)

主要问题是您在两个不同级别上工作。 Dapper是所谓的“微型ORM”,但是到最后,您仍然要处理许多直接的SQL,因此它实际上受益于诸如存储库模式之类的东西。但是,实体框架却没有。您可以通过存储库层成功处理这两个问题的唯一方法是,放弃EF购买的许多功能,基本上是Dapper的最小公分母。这可以归结为存储库模式的核心不足,无法解决您的特定问题。我知道您说过您不想谈论这一点,但这就是生活,我们并不总是能得到想要的东西。问题的真相是,这是错误的方法。

相反,您应该研究替代的抽象模式,例如CQRS(命令查询责任分离),服务层模式或微服务。这三种模式的统一特征是它们将数据库工作完全从应用程序中抽象出来。您需要对某些数据执行的每个数据集或每个操作都被整齐地放入特定的命令/查询,服务方法或API调用中。然后,您可以随意进行实际的数据库工作,并且可以立即更改它,而根本不会影响您的应用程序。您可以将Dapper用于一件事,而EF用于另一件事,