如何在 Entity Framework Core cli 工具中使用来自 dotnet 6 最小 API 的配置

Posted

技术标签:

【中文标题】如何在 Entity Framework Core cli 工具中使用来自 dotnet 6 最小 API 的配置【英文标题】:How to use configuration from dotnet 6 minimal API in Entity Framework Core cli tools 【发布时间】:2021-09-21 21:35:01 【问题描述】:

我正在尝试构建一个使用 EF Core 作为数据库访问的 API,现在在 dotnet 6 RC1 上。我想使用 dotnet cli 工具来管理迁移(创建、更新数据库等),但这些工具与模板中的最小 API 不兼容。

这是我的 Program.cs:

void ConfigureApp(WebApplication webApplication)

    // Configure the HTTP request pipeline.
    if (webApplication.Environment.IsDevelopment())
    
        webApplication.UseSwagger();
        webApplication.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Eyespert.Server v1"));
    

    webApplication.UseHttpsRedirection();

    webApplication.UseAuthentication();
    webApplication.UseAuthorization();

    webApplication.MapControllers();


void RegisterServices(WebApplicationBuilder builder)

    builder.Services.AddControllers();
    builder.Services.AddSwaggerGen(c =>
    
        c.SwaggerDoc("v1", new()  Title = "App", Version = "v1" );
    );
    
    builder.Services.AddDbContext<MyContext>(opt =>
    
        string connectionString = builder.Configuration.GetConnectionString("MyConnectionString");
        opt.UseNpgsql(connectionString);
    );


WebApplicationBuilder builder = WebApplication.CreateBuilder(args);

RegisterServices(builder);

WebApplication app = builder.Build();

ConfigureApp(app);

app.Run();

如果该代码使用 Program/Startup 类组合和旧构建器,我可以输入控制台 dotnet ef migrations add InitialCreate,该工具将读取 appsettings.development.json(即使它与上下文不同的项目)并运行迁移在适当的数据库上。对于最小的 API 样式,情况并非如此。

作为解决方案,我做了一个设计时上下文工厂:

public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory<MyContext>
    
        public MyContextCreateDbContext(string[] args)
        
            DbContextOptionsBuilder<MyContext> dbContextOptionsBuilder =
                new();

            dbContextOptionsBuilder.UseNpgsql(@"myconnectionstring");

            Console.WriteLine("Creating default MyContext");
            
            return new MyContext(dbContextOptionsBuilder.Options);
        
    

如您所见,我对连接字符串进行了硬编码。我知道我可以构造ConfigurationBuilder并使用相对路径来找到正确的json文件并使用它来查找连接字符串,但感觉就像一个肮脏的hack。

使用 dotnet 6 的方法是什么?

【问题讨论】:

“用 dotnet 6 怎么办?” 目前还没有 net6 和 EFC6 这样的东西。只是一些预览、RC 或他们所说的任何东西。重要的是他们没有被释放。因此,您不能指望文档或 EFC 工具等工作。当他们发布它时,他们很可能会更新 supported Design-time DbContext Creation patterns 的文档(如果有)。在那之前,只要使用你所拥有的。 【参考方案1】:

我使用 EFCore Context 创建了一个 MinimalAPI 项目,它运行良好,没有出现任何重大问题,除了更新 ef 工具 cli 等,请参阅完整项目:

MinimalApi.csproj

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <LangVersion>Preview</LangVersion>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.0-rc.1.21452.10">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
    <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.0-rc.1.21452.10">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
    <PackageReference Include="Swashbuckle.AspNetCore" Version="6.1.5" />
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\Infra\Infra.csproj" />
  </ItemGroup>

</Project>

Program.cs

using Infra;
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDbContext<Context>(opts =>

    var connString = builder.Configuration.GetConnectionString("MyConnectionString");
    opts.UseSqlServer(connString, options =>
    
        options.MigrationsAssembly(typeof(Context).Assembly.FullName.Split(',')[0]);
    );
);

await using var app = builder.Build();

if (app.Environment.IsDevelopment())

    app.UseDeveloperExceptionPage();


app.MapGet("/", (Func<string>)(() => "Hello World!"));

await app.RunAsync();

Infra.csproj

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <LangVersion>Preview</LangVersion>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.0-rc.1.21452.10" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.0-rc.1.21452.10">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
    <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.0-rc.1.21452.10" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.0-rc.1.21452.10">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\Domain\Domain.csproj" />
  </ItemGroup>

</Project>


Infra.Context.cs

using Domain;
using Microsoft.EntityFrameworkCore;

namespace Infra

    public class Context : DbContext
    

        public Context(DbContextOptions options) : base(options)
        
        

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        
        

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        
        

        public DbSet<MyEntity> MyEntities  get; set; 
    


Domain.csproj

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <LangVersion>Preview</LangVersion>
  </PropertyGroup>

</Project>

实体示例 (MyEntity.cs)

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace Domain

    public class MyEntity
    
        [Key]
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public Guid Id  get; set; 
        public string Name  get; set; 
        public string Value  get; set; 
    


在 nuget 包管理器上创建迁移

PM> Add-Migration InitialMigration
Build started...
Build succeeded.
Microsoft.EntityFrameworkCore.Infrastructure[10403]
      Entity Framework Core 6.0.0-rc.1.21452.10 initialized 'Context' using provider 'Microsoft.EntityFrameworkCore.SqlServer:6.0.0-rc.1.21452.10' with options: MigrationsAssembly=Infra 

在 nuget 包管理器上更新数据库

PM> Update-Database
Build started...
Build succeeded.
Microsoft.EntityFrameworkCore.Infrastructure[10403]
      Entity Framework Core 6.0.0-rc.1.21452.10 initialized 'Context' using provider 'Microsoft.EntityFrameworkCore.SqlServer:6.0.0-rc.1.21452.10' with options: MigrationsAssembly=Infra 
Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (2,338ms) [Parameters=[], CommandType='Text', CommandTimeout='60']
      CREATE DATABASE [MinimalApiDb];
Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (933ms) [Parameters=[], CommandType='Text', CommandTimeout='60']
      IF SERVERPROPERTY('EngineEdition') <> 5
      BEGIN
          ALTER DATABASE [MinimalApiDb] SET READ_COMMITTED_SNAPSHOT ON;
      END;
Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (29ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      SELECT 1
Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (18ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      CREATE TABLE [__EFMigrationsHistory] (
          [MigrationId] nvarchar(150) NOT NULL,
          [ProductVersion] nvarchar(32) NOT NULL,
          CONSTRAINT [PK___EFMigrationsHistory] PRIMARY KEY ([MigrationId])
      );
Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (3ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      SELECT 1
Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (17ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      SELECT OBJECT_ID(N'[__EFMigrationsHistory]');
Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (5ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      SELECT [MigrationId], [ProductVersion]
      FROM [__EFMigrationsHistory]
      ORDER BY [MigrationId];
Microsoft.EntityFrameworkCore.Migrations[20402]
      Applying migration '20211001150743_InitialMigration'.
Applying migration '20211001150743_InitialMigration'.
Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (176ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      CREATE TABLE [MyEntities] (
          [Id] uniqueidentifier NOT NULL,
          [Name] nvarchar(max) NOT NULL,
          [Value] nvarchar(max) NOT NULL,
          CONSTRAINT [PK_MyEntities] PRIMARY KEY ([Id])
      );
Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (23ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion])
      VALUES (N'20211001150743_InitialMigration', N'6.0.0-rc.1.21452.10');
Done.
PM> 

要使用 dotnet ef 工具,您需要更新到最新的 rc 以匹配您的 projetc 设计器/工具

dotnet tool update --global dotnet-ef --version 6.0.0-rc.1.21452.10
请注意,这是您在项目包中使用的相同版本的包

通过 ef tools cli 创建迁移

PS D:\Repositorios\MinimalApi\MinimalApi> dotnet ef migrations add eftoolsmigration -s "D:\Repositorios\MinimalApi\MinimalApi\MinimalApi.csproj" -p "D:\Repositorios\MinimalApi\Infra\Infra.csproj"
Build started...
Build succeeded.
info: Microsoft.EntityFrameworkCore.Infrastructure[10403]
      Entity Framework Core 6.0.0-rc.1.21452.10 initialized 'Context' using provider 'Microsoft.EntityFrameworkCore.SqlServer:6.0.0-rc.1.21452.10' with options: MigrationsAssembly=Infra
Done. To undo this action, use 'ef migrations remove'
PS D:\Repositorios\MinimalApi\MinimalApi>

通过 ef tools cli 更新数据库

 dotnet ef database update -s "D:\Repositorios\MinimalApi\MinimalApi\MinimalApi.csproj" -p "D:\Repositorios\MinimalApi\Infra\Infra.csproj"
请注意,-s 是您的启动项目路径; 请注意,-p 是您的目标项目路径(配置上下文的位置);

自动创建的迁移文件夹

最终项目结构

创建的数据库

请注意,您必须在包管理器上设置“包含预发布”才能获得与 .Net 6 兼容的版本

请注意,您使用的是 Postgree DB,它必须有一个与 EF Core 6.xxx 兼容的客户端

安装的运行时和 SDK(.NET 6 仅为清晰起见)

PS C:\Users\Daniel> dotnet --list-runtimes
Microsoft.NETCore.App 6.0.0-rc.1.21451.13 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.WindowsDesktop.App 6.0.0-rc.1.21451.3 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]

PS C:\Users\Daniel> dotnet --list-sdks
6.0.100-rc.1.21463.6 [C:\Program Files\dotnet\sdk]

【讨论】:

我错过了 cli 工具调用上的 -p-s 开关,非常感谢! 由于其他人开始问同样的问题(例如***.com/questions/69474533/…),在将他们重定向到这里之前,我有义务询问是否有人可以确认这真的有效(我可以'不要用 MS 实验污染我的生产环境)这是非常值得怀疑的。所有这些样板文件,包括 -p-s 选项都不是新的,与最小 API 无关。 SqlServer 和 MinimalApiDb 听起来就像“合理的默认值”。 唯一的证明是如果您使用硬编码的“MyFooBar”数据库名称(一个不遵循任何命名约定或配置源的字符串)硬编码.UseSomeDb(...),并且该工具会创建/更新此类数据库。从逻辑和技术上讲,我看不出怎么会发生(除非他们以某种方式使用 Roslyn 来真正分析您的 source code 并提取 DbContext 配置代码)。使用“旧”方法,他们搜索(通过反射)特定的静态方法,并*执行它以便从 DI 获取配置的 DbContext 实例。 我认为诀窍也在&lt;LangVersion&gt;Preview&lt;/LangVersion&gt; 中,因为运行时必须将主Program.cs 识别为具有main 方法的静态类,并以某种方式提取构建器(不确定它是如何工作的) 这个答案真的很清楚很完整。谈到 ef 工具,我不喜欢您必须将与供应商相关的软件包安装到可能与此无关的 Infra 项目中。

以上是关于如何在 Entity Framework Core cli 工具中使用来自 dotnet 6 最小 API 的配置的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Entity Framework Core 2 中播种?

如何处置 Entity Framework Core 内存数据库

如何在 Entity Framework Core 中将连接字符串传递给 DBContext?

如何使用 Entity Framework Core 正确保存 DateTime?

如何在 .NET Core 3.0 Entity Framework 中执行组加入?

请问在 .NET Core 中如何让 Entity Framework Core 在日志中记录由 LINQ 生成的SQL语句?