EF Core - 从每个类型 Table 迁移到每个层次结构 Table

EF Core - Migrate from Table Per Type to Table Per Hierarchy

我正在尝试从使用 TPT(每个子类一个 table)迁移到 TPH(所有子类一个 table)。

这是我的TPT起点:

实体:

[Serializable]
public abstract class VeganItem<TVeganItemEstablishment> : DomainEntity<int>
{
    [Required]
    public string Name { get; set; }
    [Required]
    public string CompanyName { get; set; }
    [Required]
    public string Description { get; set; }
    public string Image { get; set; }
    [Required]
    public int IsNotVeganCount { get; set; } = 0;
    [Required]
    public int IsVeganCount { get; set; } = 0;
    [Required]
    public int RatingsCount { get; set; } = 0;
    [Required]
    public int Rating { get; set; }
    [Required]
    public List<Option> Tags { get; set; }

    [PropertyName("veganItemEstablishments", Ignore = true)]
    public virtual ICollection<TVeganItemEstablishment> VeganItemEstablishments { get; set; }
}


[ElasticsearchType(RelationName = "groceryitem", IdProperty = "Id")]
public class GroceryItem : VeganItem<GroceryItemEstablishment>
{
}

[ElasticsearchType(RelationName = "menuitem", IdProperty = "Id")]
public class MenuItem : VeganItem<MenuItemEstablishment>
{
}

创建模型时:

modelBuilder.Entity<GroceryItem>(gi =>
{
    gi.HasIndex(e => new { e.CompanyName, e.Name }).IsUnique();
    gi.Property(u => u.CreatedDate)
        .HasDefaultValueSql("CURRENT_TIMESTAMP"); 
    gi.Property(u => u.UpdatedDate)
        .HasDefaultValueSql("CURRENT_TIMESTAMP"); 
    gi.HasKey(e => e.Id);
    gi.HasOne(q => q.UpdatedBy)
        .WithMany()
        .HasForeignKey(k => k.UpdatedById);
    gi.HasOne(q => q.CreatedBy)
        .WithMany()
        .HasForeignKey(k => k.CreatedById);
    gi.Property(e => e.Tags)
        .HasConversion(
            v => JsonSerializer.Serialize(v, null),
            v => JsonSerializer.Deserialize<List<Option>>(v, null),
            new ValueComparer<IList<Option>>(
                (c1, c2) => c1.SequenceEqual(c2),
                c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())),
                c => (IList<Option>)c.ToList()));
});

modelBuilder.Entity<MenuItem>(mi =>
{
    mi.HasIndex(e => new { e.CompanyName, e.Name }).IsUnique();
    mi.Property(u => u.CreatedDate)
        .HasDefaultValueSql("CURRENT_TIMESTAMP"); 
    mi.Property(u => u.UpdatedDate)
        .HasDefaultValueSql("CURRENT_TIMESTAMP"); 
    mi.HasKey(e => e.Id);
    mi.HasOne(q => q.UpdatedBy)
        .WithMany()
        .HasForeignKey(k => k.UpdatedById);
    mi.HasOne(q => q.CreatedBy)
        .WithMany()
        .HasForeignKey(k => k.CreatedById);
    mi.Property(e => e.Tags)
    .HasConversion(
        v => JsonSerializer.Serialize(v, null),
        v => JsonSerializer.Deserialize<List<Option>>(v, null),
        new ValueComparer<IList<Option>>(
            (c1, c2) => c1.SequenceEqual(c2),
            c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())),
            c => (IList<Option>)c.ToList()));
});

public DbSet<GroceryItem> GroceryItems { get; set; }
public DbSet<MenuItem> MenuItems { get; set; }

所以我只想要一个名为 VeganItems 的 table。究竟是什么导致了 2 tables - GroceryItemsMenuItems?因为我尝试了一些东西,但它们没有用。 EF Core 默认使用 TPH,所以我不确定它为什么使用 TPT。我想知道是不是因为我的基础实体是通用类型。

EF Core uses TPH by default so I'm unsure why it is using TPT. I'm wondering if it is because my base entity is a generic type.

通用类型是问题之一。另一个是它不是一个基础实体,而只是基础class。为了被视为实体,必须有 DbSet<T>ModelBuilder.Entity<T>() 调用,或应用 IEntityTypeConfiguration<T>,或某些发现的实体导航 属性(集合或引用)引用到它 - 见 Including types in the model.

你没有任何这些,所以模型甚至不是 TPT(包含共同属性的共同 table + 每个包含特定属性的派生实体的单个 table),而是某种TPC 的(Table-Per-Class,目前不受 EF Core 支持),其中没有通用的 table - 所有数据都在具体的 table 中每个派生实体。

因此,为了使用 TPT,您需要解决这两个问题。泛型class不能用作实体类型,因为它的类型不足以识别它(每个泛型实例化都是不同的类型,typeof(Foo<Bar>) != typeof(Foo<Baz>))。

首先提取将用作基本实体的非通用部分(为清楚起见,删除了非 EF Core 注释):

// Base class (code/data reuse only, not an entity)
public abstract class DomainEntity<TId>
{
    public TId Id { get; set; }
}

// Base entity
public abstract class VeganItem : DomainEntity<int>
{
    [Required]
    public string Name { get; set; }
    [Required]
    public string CompanyName { get; set; }
    [Required]
    public string Description { get; set; }
    public string Image { get; set; }
    [Required]
    public int IsNotVeganCount { get; set; } = 0;
    [Required]
    public int IsVeganCount { get; set; } = 0;
    [Required]
    public int RatingsCount { get; set; } = 0;
    [Required]
    public int Rating { get; set; }
    [Required]
    public List<Option> Tags { get; set; }

}

// Base class (code/data reuse only, not an entity)
public abstract class VeganItem<TVeganItemEstablishment> : VeganItem
{
    public virtual ICollection<TVeganItemEstablishment> VeganItemEstablishments { get; set; }
}

// Derived entity
public class GroceryItem : VeganItem<GroceryItemEstablishment>
{
}

// Derived entity
public class MenuItem : VeganItem<MenuItemEstablishment>
{
}

然后(可选)为其添加 DbSet

public DbSet<VeganItem> VeganItems { get; set; }

最后(强制)将基本实体成员的流畅配置移动到它自己的块中,并在派生中仅保留派生类型的特定成员的配置:


// Configure base entity
modelBuilder.Entity<VeganItem>(vi =>
{
    vi.HasIndex(e => new { e.CompanyName, e.Name }).IsUnique();
    vi.Property(u => u.CreatedDate)
        .HasDefaultValueSql("CURRENT_TIMESTAMP");
    vi.Property(u => u.UpdatedDate)
        .HasDefaultValueSql("CURRENT_TIMESTAMP");
    vi.HasKey(e => e.Id);
    vi.HasOne(q => q.UpdatedBy)
        .WithMany()
        .HasForeignKey(k => k.UpdatedById);
    vi.HasOne(q => q.CreatedBy)
        .WithMany()
        .HasForeignKey(k => k.CreatedById);
    vi.Property(e => e.Tags)
        .HasConversion(
            v => JsonSerializer.Serialize(v, null),
            v => JsonSerializer.Deserialize<List<Option>>(v, null),
            new ValueComparer<IList<Option>>(
                (c1, c2) => c1.SequenceEqual(c2),
                c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())),
                c => (IList<Option>)c.ToList()));
});

// Configure derived entities
modelBuilder.Entity<GroceryItem>(gi =>
{
});

modelBuilder.Entity<MenuItem>(mi =>
{
});