【问题标题】:Flexible repository architecture for multiple ORMs [closed]用于多个 ORM 的灵活存储库架构 [关闭]
【发布时间】:2019-04-08 19:13:33
【问题描述】:

我以 Dapper 作为我们的 ORM 开始了我的项目,因为我们希望快速、简单地启动和运行。我现在正处于我们想要升级的阶段,所以我们不会在带有连接等的代码中编写大量 SQL。我已经决定我要使用实体框架(我真的不想讨论你是否应该或者不应该将存储库与 EF 一起使用)。但是,我没有时间一次性重构我所有的存储库。我正在使用接口,但到目前为止,接口对于每个 POCO 对象都是相当特定的,使用 GetFooUpdateFooGetFooByBarId 等方法,这并不是很好。我想将 CRUD 操作泛化为常用方法名称(Select、Upsert、Delete)。

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

如果我愿意,我还想保留将来使用 Dapper 或任何其他 ORM 的能力,而不会永远限制自己使用 EF。

我现在已经开始创建 EF 存储库,我的项目结构是这样的:

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

我已经在我的基础 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 之间的一定程度的通用性。我只是在寻找关于这个架构的意见。有什么好处吗?我是否忽略了任何明显的问题,或者这是一个可怕的反模式还是什么?接受建议或改进。

【问题讨论】:

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


    【解决方案1】:

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

    老实说,我认为 Dappers 和 EFs 抽象查询的级别差异太大,无法通过公共层进一步抽象它们,而不会过多地影响(和复杂化)您的应用程序逻辑。我们的整体应用程序架构与您的非常相似(Core IQueryable<T> 之上的 OR-Mappers 以保持其可维护性。所以我们的 Repository/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 或 Primary/Foreign-Key 的东西。因此,支持多个 OR-Mapper 意味着将 多个 限制集引入您的域模型。您必须决定是否仍然可以将您的需求映射到这些边界内的合理(并且仍然面向对象)域模型。

    【讨论】:

    • 谢谢@Peter!完全可以理解的方法,如果我一开始没有使用 Dapper,我想我也会这样做!随着我逐步淘汰 Dapper,这可能会成为我努力实现的长期目标。
    【解决方案2】:

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

    相反,您应该研究替代抽象模式,例如 CQRS(命令查询职责分离)、服务层模式或微服务。所有这三种模式的统一特征是它们完全从您的应用程序中抽象出数据库工作。您需要对某些数据执行的每组数据或每项操作都整齐地隐藏在特定的命令/查询、服务方法或 API 调用中。然后,实际的数据库工作可以随心所欲地完成,并且可以随心所欲地轻松更改,而不会影响您的应用程序。你可以用 Dapper 做一件事, EF 做另一件事,没关系。

    【讨论】:

    • 谢谢@chris。我想我只是想避免过多的重写,但我可能别无选择!我确实在某些地方使用了服务层模式,但它将存储库作为依赖项。我将如何改变它以与 Dapper 和 EF 一起工作,同时保持与 ORM 无关?还是该服务实际上不应该与 ORM 无关,例如,将 EF DbContext 作为依赖项?
    猜你喜欢
    • 2023-03-25
    • 2010-09-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-12-11
    • 1970-01-01
    相关资源
    最近更新 更多