如何将实体的主键与基 class 分开?

How to separate entity's primary key from base class?

我想用 EF Core 完成干净的架构。 我试图将主键与基 class 分开,但失败并出现以下异常。

System.InvalidOperationException: 'A key cannot be configured on 'ManagerEntity' because it is a derived type. The key must be configured on the root type 'Manager'. If you did not intend for 'Manager' to be included in the model, ensure that it is not referenced by a DbSet property on your context, referenced in a configuration call to ModelBuilder, or referenced from a navigation on a type that is included in the model.'

核心

public class Manager
{
    public Manager(Guid identifier, string email)
    {
        Identifier = identifier;
        Email = email;
    }

    public Guid Identifier { get; }
    public string Email { get; }

    public void FixPrinter(Printer printer)
    {
        printer.IsOutOfControl = true;
    }
}
public class Printer
{
    public Printer(Guid token)
    {
        Token = token;
        Manager = null;
        IsOutOfControl = false;
    }

    public Guid Token { get; }

    public Manager? Manager { get; set; }

    public bool IsOutOfControl { get; set; }
}

基础设施

public class ApplicationContext
    : DbContext
{
    // ...

    public DbSet<ManagerEntity> ManagerSet { get; set; }
    public DbSet<PrinterEntity> PrinterSet { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder.ApplyConfiguration(new ManagerEntityConfiguration(Database));
        modelBuilder.ApplyConfiguration(new PrinterEntityConfiguration(Database));
    }
}

配置Manager

public sealed class ManagerEntity
    : Manager
{
    public ManagerEntity(string email)
        : base(Guid.Empty, email)
    {
    }

    // Primary key for database.
    public long Id { get; }
}
internal sealed class ManagerEntityConfiguration
    : IEntityTypeConfiguration<ManagerEntity>
{
    private readonly DatabaseFacade _database;

    public ManagerEntityConfiguration(DatabaseFacade database)
    {
        _database = database;
    }

    public void Configure(EntityTypeBuilder<ManagerEntity> builder)
    {
        builder
            .Property(e => e.Id)
            .ValueGeneratedOnAdd();

        // The exception occurs here.
        builder
            .HasKey(e => e.Id);

        // ...
    }
}

配置Printer

public sealed class PrinterEntity
    : Printer
{
    public PrinterEntity()
        : base(Guid.Empty)
    {
    }

    // Primary key for database.
    public long Id { get; }
}
internal sealed class PrinterEntityConfiguration
    : IEntityTypeConfiguration<PrinterEntity>
{
    private readonly DatabaseFacade _database;

    public PrinterEntityConfiguration(DatabaseFacade database)
    {
        _database = database;
    }

    public void Configure(EntityTypeBuilder<PrinterEntity> builder)
    {
        builder
            .Property(e => e.Id)
            .ValueGeneratedOnAdd();

        builder
            .HasKey(e => e.Id);

        // ...
    }
}

网页API

app.MapPost("/printer", async (ApplicationContext context) =>
{
    PrinterEntity printer = new()
    {
        Manager = new ManagerEntity("master@google.com"),
    };

    await context.PrinterSet.AddAsync(printer);
    await context.SaveChangesAsync();

    return printer;
});

我应该使用接口而不是继承来构建它吗?

核心

public interface IPrinter
{
    public Manager? Manager { get; set; }
}

基础设施

public sealed class PrinterEntity : IPrinter
{
    // ...
}

Github source code

谢谢,@roji

https://github.com/dotnet/efcore/issues/27421#issuecomment-1034762908

这很可能发生,因为您在模型中映射了 Manager 和 ManagerEntity,这意味着您正在配置 inheritance mapping(即 EF 认为您打算将 Manager 和 ManagerEntity 实例都存储在数据库中) .使用继承映射,必须在根指定键。

但是,您似乎只想在 .NET 端进行 class 分离,而不需要任何实际的层次结构,因此请确保您没有映射基础 class(Manager in the多于)。请参阅下面的最小代码示例。

await using var ctx = new BlogContext();
await ctx.Database.EnsureDeletedAsync();
await ctx.Database.EnsureCreatedAsync();

public class BlogContext : DbContext
{
    // Uncomment the below to make the exception appear
    // public DbSet<Manager> Managers { get; set; }
    public DbSet<ManagerEntity> ManagerEntities { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => optionsBuilder
            .UseSqlServer(@"Server=localhost;Database=test;User=SA;Password=Abcd5678;Connect Timeout=60;ConnectRetryCount=0")
            .LogTo(Console.WriteLine, LogLevel.Information)
            .EnableSensitiveDataLogging();

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<ManagerEntity>().HasKey(b => b.Id);
    }
}

public class Manager
{
    public string Name { get; set; }
}

public class ManagerEntity : Manager
{
    public int Id { get; set; }
}

但是...如果目标只是不在 Manager 上公开 Id 属性,则有更简单的方法可以做到这一点,而不是引入 .NET 层次结构。您可以改用一个私有 _id 字段,该字段将由 EF Core 使用但不会以其他方式暴露在您的应用程序中,从而保持您的数据模型干净(see docs). Alternatively, you can have an Id shadow property,从您的 CLR 类型中完全删除 field/property。