如何让 EF Core 工具从控制台应用程序的服务提供者处获取 DbContext 实例?

Mon*_*mer 4 c# entity-framework entity-framework-core

我已经阅读了设计时 DbContext 创建,EF Core 工具(例如,迁移命令)有 3 种方式DbContext在设计时而不是在运行时从应用程序获取派生实例。

  • 来自应用服务提供商
  • 从任何无参数的ctor
  • 从一个类实现 IDesignTimeDbContextFactory<T>

这里我只对第一种方法感兴趣,它模仿了 Asp.net Core 中使用的模式。这段代码无法编译,因为我不知道如何让 EF Core 工具获取TheContext实例。

最小工作示例

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.IO;


public class TheContext : DbContext
{
    public TheContext(DbContextOptions<TheContext> options) : base(options) { }
    public DbSet<User> Users { get; set; }
}

public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
}


class Program
{
    private static readonly IConfiguration _configuration;
    private static readonly string _connectionString;

    static Program()
    {
        _configuration = new ConfigurationBuilder()
            .SetBasePath(Directory.GetCurrentDirectory())
            .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true).Build();

        _connectionString = _configuration.GetConnectionString("SqlServer");
    }

    static void ConfigureServices(IServiceCollection isc)
    {
        isc.AddSingleton(_ => _configuration);

        isc.AddDbContextPool<TheContext>(options => options
            .UseSqlServer(_connectionString));

        isc.AddSingleton<TheApp>();
    }

    static void Main()
    {
        IServiceCollection isc = new ServiceCollection();
        ConfigureServices(isc);

        IServiceProvider isp = isc.BuildServiceProvider();

        isp.GetService<TheApp>().Run();
    }
}

class TheApp
{
    readonly TheContext _theContext;

    public TheApp(TheContext theContext) => _theContext = theContext;

    public void Run()
    {
        // Do something on _theContext            
    }
}
Run Code Online (Sandbox Code Playgroud)

如何让 EF Core 工具从控制台应用程序的服务提供者处获取 DbContext 实例?

编辑:

我忘了提及appsettings.json以下内容:

{
  "ConnectionStrings": {
    "Sqlite": "Data Source=MyDatabase.db",
    "SqlServer": "Server=(localdb)\\mssqllocaldb;Database=MyDatabase;Trusted_Connection=True"
  }
}
Run Code Online (Sandbox Code Playgroud)

Iva*_*oev 8

尽管文档主题名为From application services,但它以

如果您的启动项目是 ASP.NET Core 应用程序,这些工具会尝试从应用程序的服务提供程序获取 DbContext 对象。

看起来他们不希望 ASP.NET Core 应用程序以外的项目类型使用应用程序服务提供程序:)

然后它继续

这些工具首先尝试通过调用 Program.BuildWebHost() 并访问 IWebHost.Services 属性来获取服务提供者。

例子

public static IWebHost BuildWebHost(string[] args) => ...

这是适用于当前(EF Core 2.1.3)位的技巧。该工具实际上是搜索包含你的入口点(通常是类Program)的静态(并不需要公开)调用的方法BuildWebHoststring[] args参数,以及重要的无证部分-返回类型并不需要是IWebHost!它可以是任何具有像这样的公共属性的对象

public IServiceProvider Services { get; } 
Run Code Online (Sandbox Code Playgroud)

这为我们提供了以下解决方案:

class Program
{
    // ...

    // Helper method for both Main and BuildWebHost
    static IServiceProvider BuildServiceProvider(string[] args)
    {
        IServiceCollection isc = new ServiceCollection();
        ConfigureServices(isc);
        return isc.BuildServiceProvider();
    }

    static void Main(string[] args)
    {
         BuildServiceProvider(args).GetService<TheApp>().Run();
    }

    // This "WebHost" will be used by EF Core design time tools :)
    static object BuildWebHost(string[] args) =>
        new { Services = BuildServiceProvider(args) };
}
Run Code Online (Sandbox Code Playgroud)

更新:从 v2.1 开始,您还可以使用新CreateWebHostBuilder模式,但恕我直言,它只是增加了此处不需要的另一层复杂性(仍然支持以前的模式)。它是相似的,但现在我们需要一个被调用的方法CreateWebHostBuilder,该方法返回一个具有公共方法的对象,该对象具有Build()返回具有公共Services属性的对象IServiceProvider。为了重用 from Main,我们不能使用匿名类型,必须创建 2 个类,而且这使得它的用法Main更加冗长:

class AppServiceBuilder
{
    public ServiceCollection Services { get; } = new ServiceCollection();
    public AppServiceProvider Build() => new AppServiceProvider(Services.BuildServiceProvider());
}

class AppServiceProvider
{
    public AppServiceProvider(IServiceProvider services) { Services = services; }
    public IServiceProvider Services { get; }
}

class Program
{
    // ...

    static void Main(string[] args)
    {
         CreateWebHostBuilder(args).Build().Services.GetService<TheApp>().Run();
    }

    // This "WebHostBuilder" will be used by EF Core design time tools :)
    static AppServiceBuilder CreateWebHostBuilder(string[] args)
    {
        var builder = new AppServiceBuilder();
        ConfigureServices(builder.Services);
        return builder;
    }
}
Run Code Online (Sandbox Code Playgroud)