如何在Entity Framework Core cli工具中使用dotnet 6 minimal API的配置

7
我正在使用EF Core作为数据库访问构建API,目前在dotnet 6 RC1上进行。我想使用dotnet cli工具来管理迁移(创建、更新数据库等),但是这些工具与模板中的minimal 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中,最好的方法是什么?


1
“用dotnet 6怎么做是最好的方式?” 目前还没有net6和EFC6。只有一些预览版、RC或者其他他们称之为的版本。重要的是它们还没有发布。因此,你不能期望有文档或者EFC工具可用等。当它们发布时,很可能会更新支持的设计时DbContext创建模式的文档(如果有的话)。在那之前,就使用现有的吧。 - Ivan Stoev
1个回答

13

我使用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 版本,以匹配您的项目设计器/工具。

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

通过 ef 工具命令行界面创建迁移

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 工具 CLI 更新数据库

 dotnet ef database update -s "D:\Repositorios\MinimalApi\MinimalApi\MinimalApi.csproj" -p "D:\Repositorios\MinimalApi\Infra\Infra.csproj"
  • -s 是你的启动项目路径
  • -p 是目标项目路径(上下文配置所在的位置)

自动生成的Migrations文件夹

自动生成的Migrations文件夹

最终项目结构

最终项目结构

创建的数据库

创建的数据库

  • 注意,您将需要在软件包管理器上设置“包括预发行版”,以获取与 .Net 6 兼容的版本

  • 请注意,您正在使用PostgreSQL数据库,它必须具有与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]

1
我在命令行工具调用中缺少了“-p”和“-s”开关,非常感谢! - Krzysztof Skowronek
由于其他人开始问同样的问题(例如https://stackoverflow.com/questions/69474533/asp-net-6-new-programcs-structure-and-initialising-the-identity-entityframewo),在将他们重定向到这里之前,我有义务询问是否有人可以确认这确实有效(我不能用MS实验污染我的生产环境),这是非常可疑的。所有这些样板文件,包括“-p”和“-s”选项都不是新的,与最小API无关。SqlServer和MinimalApiDb听起来只是“合理的默认值”。 - Ivan Stoev
唯一的证明是,如果您硬编码了.UseSomeDb(...)并使用硬编码的“MyFooBar”数据库名称,这是一个不遵循任何命名约定或配置源的字符串,并且该工具创建/更新了这样的数据库。从逻辑和技术上讲,我不认为这可能发生(除非他们以某种方式使用Roslyn来真正分析您的源代码并提取DbContext配置代码)。采用“旧”方法,他们会搜索(使用反射)特定的静态方法,并执行它以从DI获取已配置的DbContext实例。 - Ivan Stoev
我认为诀窍也在于 <LangVersion>Preview</LangVersion>,因为运行时必须将主 Program.cs 识别为具有主方法的静态类,并以某种方式提取生成器(不确定其工作原理)。 - Krzysztof Skowronek
这个回答非常清晰完整。说到 ef 工具,我不喜欢你必须将与供应商相关的包安装到基础设施项目中,而这些包可能是与此无关的。 - Giovanni Patruno
实际上,我不得不安装它,因为当时包管理器控制台还没有准备好处理最小API,今天你可以在Visual Studio内正常使用,没有进一步的问题 :) - MestreDosMagros

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接