【问题标题】:How to configure EF Core connection context to decouple packages dependencies?如何配置 EF Core 连接上下文以解耦包依赖关系?
【发布时间】:2018-09-12 22:26:52
【问题描述】:

我使用 Entity Framework Core 创建了一个库来处理持久性。 它由针对 SQL Server 数据库的 Web API 使用。 此外,我针对 SQLite 数据库对其进行了测试(单元测试)。

我使用配置文件以简单的方式完成了它。因此,在我扩展 DBContext 的类中,我使用类似这样的方式实现了 OnConfiguring:

if(isSqlServer) //don't mind where it comes from, it works fine
    optionsBuilder.UseSqlServer(connectionString);
else
    optionsBuilder.UseSqlite(connectionString);

它似乎工作正常,但效果不佳。假设我有这个项目:

  1. 持久层
  2. PersistenceLayerTests
  3. WebAPI

由于方法 UseSqlite 是包 Microsoft.EntityFrameworkCore.Sqlite 的类中的静态扩展方法,同样,方法 UseSqlServer 是包 Microsoft.EntityFrameworkCore.SqlServer 的类中的静态扩展方法,有两件事麻烦我。

首先,我必须在三个项目中包含这两个依赖项,以避免在运行时出现错误“System.IO.FileNotFoundException:无法加载文件或程序集 Microsoft.EntityFrameworkCore.SqlServer (...)”。

其次,如果我需要支持一个新的 DBMS(Oracle、MySQL),我必须在三个项目上包含新的依赖项。

我希望构建一个方案,其中 PersistentLayer 项目将没有这些依赖项,而 PersistentLayerTests 将仅依赖于 Microsoft.EntityFrameworkCore.Sqlite,而 WebAPI 将仅依赖于 Microsoft.EntityFrameworkCore.SqlServer。

是否有另一种方法来配置连接上下文以解耦这些包依赖关系?

注意事项:

  1. 我使用 NHibernate 做到了这一点,它为每个 DBMS 使用不同的驱动程序实现。
  2. 我尝试使用反射,但并不优雅,而且我注意到如果扩展方法具有不同的参数,它将无法正常工作。
  3. 我不需要完整的解决方案。如果有人能指出我的另一种方式,那将非常有帮助。

【问题讨论】:

  • 看来您需要将db上下文的配置移动到test/web项目中。
  • @ikkentim 可以接受,特别是测试项目。对于 WebAPI,它并不完美,因为我想完全抽象 EF 的使用(但我没有提到这个问题,抱歉)。但我可以轻松地将 WebAPI 项目拆分为 3 个项目来做到这一点。一个项目将拥有所有控制器,第二个项目将只是一个用于配置 NHibernate 的“外壳”,第三个将是另一个“外壳”来配置具有 DBContext 专业化的 EF。

标签: c# .net-core entity-framework-core


【解决方案1】:

这似乎有点矫枉过正,但在这种情况下,基本策略是放置一个通用接口并将它们分离到不同的项目中。不确定这是否是这种情况下的最佳解决方案。

那么不同的项目是独立的(通用接口除外),以后可以轻松扩展。

关键部分是在你的主项目中只引用接口组件而不是具体的一个。如果您想避免FileNotFoundException,则必须在运行时加载混凝土(组件):此外:您需要一种机制来选择合适的,通常称为factory

让我们考虑一下您拥有其中 3 个(sql、sqllite 和 oracle)的情况:

首先要做的是创建一个通用接口来与适当的 Db 引擎进行通信,例如:

//separate assembly
public interface IDbEningeSelector
{
    //added the option builder for simplicity: one could do better.
    void Configure(string connectionString,IOptionsBuilder optionsBuilder);
}

接下来,只需为具体实现创建 3 个单独的项目,一个 sql-lite、一个 sql 和一个 oracle。

下一步:在三个项目中创建一个实现这个的类:

//3 of these.
public class SqlEnging : IDbEngineSelector
{
    public void Configure(string connectionString,IOptionsBuilder optionsBuilder)
    {
         optionsBuilder.UseSqlServer(connectionString);
    }
}

好的,现在您可以选择其中之一。

//the "thing"
IDbEngineSelector selector = null;

selector = //resolve through factory, possibly based on a flag. 

selector.Configure(connectionString, optionsBuilder);

基本上你已经完成并留下了一个可扩展的、动态引用的数据库提供程序系统。

对于动态程序集解析器,您需要按照本文所述加载它们: https://www.codeproject.com/Articles/1194332/Resolving-Assemblies-in-NET-Core

这实际上会在运行时加载依赖项,我可能会为此保留一个放置这些程序集的特殊路径。

我遗漏了工厂实现细节,我需要先在 .net Core 中检查正确的方法,等等……

【讨论】:

  • 让每个客户端根据需要创建和配置 DbContextOptions 对象实际上更简单
  • 正确,但前提是 OP 明确且专门为每个客户端配置选项生成器。换句话说:如果 OP 想要在 SqlLite 和 Sql 程序集之间进行选择,该解决方案仍会将它们绑定在一起,据我所知,问题是关于 decouple these packages dependencies;也许我错过了什么,让我们看看分数在哪里。
  • 这并不过分。这是基于最有可能需要支持不同 DBMS 的 OP“第二点”的正确方法。如果你的数据库要随心所欲地改变,你需要解耦和隔离。
  • Stefan、Adam Simon 和 ikkentim 向我指出了新的有用方向。在这些方式中,这种方式听起来是最解耦的。谢谢大家的时间。
  • 感谢@AdamVincent,我最初也是这么读的。
【解决方案2】:

您只需让 DbContext 的用户(PersistenceLayerTests 和 WebApi 项目)负责配置它,而不是 DbContext 本身。

DbContext provides a constructor 接受 DbContextOptions。你需要公开这个构造函数:

public class MyDbContext : DbContext
{
    public MyDbContext(DbContextOptions<MyDbContext> options) : base(options) { }

    // ...
}

然后,您可以执行以下操作:

var optionsBuilder = new DbContextOptionsBuilder<MyDbContext>();

if (isSqlServer) //don't mind where it comes from, it works fine
    optionsBuilder.UseSqlServer(connectionString);
else
    optionsBuilder.UseSqlite(connectionString);

using (var context = new MyDbContext(optionsBuilder.Options))
{
    // ...
}

【讨论】:

    猜你喜欢
    • 2013-02-14
    • 2014-03-15
    • 2019-10-04
    • 2021-05-08
    • 1970-01-01
    • 2019-08-20
    • 1970-01-01
    • 1970-01-01
    • 2021-02-09
    相关资源
    最近更新 更多