在为 Person 类层次结构创建迁移时,我遇到了 Entity Framework Core 的意外行为。具体来说,当我为 Person 类生成迁移时,我在 Person 表中得到一个名为 Student_InstitutionId 而不是 HospitalId 的外键。
public abstract class Person : Aggregate<long>
{
// ... properties ...
}
public class Student : Person
{
// ... properties ...
public long InstitutionId { get; private set; }
public virtual Institution Institution { get; set; }
}
public class Institution : Aggregate<long>
{
// ... properties ...
public virtual ICollection<Student> Students { get; set; }
}
我的Fluent API配置如下:
public class PersonEntityConfig : BaseEntityConfig<Person, long>
{
public override void Configure(EntityTypeBuilder<Person> builder)
{
// ... configuration ...
}
}
public class StudentEntityConfig : BaseEntityConfig<Student, long>
{
public override void Configure(EntityTypeBuilder<Student> builder)
{
// ... configuration ...
builder.HasOne(a => a.Institution)
.WithMany(a => a.Students)
.HasForeignKey(a => a.InstitutionId)
.OnDelete(DeleteBehavior.NoAction);
}
}
public class InstitutionEntityConfig : BaseEntityConfig<Institution, long>
{
#region Fields
#endregion
#region Properties
#endregion
#region Methods
public override void Configure(EntityTypeBuilder<Institution> builder)
{
base.Configure(builder);
builder.Property(x => x.Name).HasMaxLength(50).IsRequired();
builder.Property(x => x.CenterNumber).HasMaxLength(50);
builder.Property(x => x.EMISNumber).HasMaxLength(50);
builder.Property(x => x.EmailAddress).HasMaxLength(50);
builder.HasOne(a => a.Address)
.WithOne(i => i.Institution)
.HasForeignKey<Institution>(x => x.AddressId)
.OnDelete(DeleteBehavior.Cascade);
}
#endregion
#region Constructors
#endregion
}
该问题似乎与 Entity Framework Core 如何处理 Person 表中的鉴别器属性和外键有关。如何确保外键正确命名为InstitutionId而不是Student_InstitutionId?
我希望Person表中的外键命名为InstitutionId,而不是Student_InstitutionId。 生成迁移时,Entity Framework Core 会在 Person 表中创建一个名为 Student_InstitutionId 的外键。
请提供有关如何解决此命名问题的指导,并确保外键正确命名为InstitutionId。
经过测试后发现,不,如果某些继承类具有关系而其他类没有关系,那么对于 TPH 继承结构、代码优先或模式优先,这将无法开箱即用。使用以下示例:
[Table("Animals")]
public abstract class Animal
{
[Key]
public int AnimalId { get; set; }
public string Name { get; set; }
}
public class Dog : Animal
{
public virtual Owner Owner { get; set; }
}
public class Cat : Animal
{
public virtual Owner Owner { get; set; }
}
public class Wolf : Animal
{
}
配置:
modelBuilder.Entity<Animal>()
.HasDiscriminator<string>("AnimalType")
.HasValue<Dog>("Dog")
.HasValue<Cat>("Cat")
.HasValue<Wolf>("Wolf");
modelBuilder.Entity<Dog>()
.HasOne(x => x.Owner)
.WithMany()
.IsRequired(false)
.HasForeignKey("OwnerId")
.HasConstraintName("FK_Dogs_Owners");
modelBuilder.Entity<Cat>()
.HasOne(x => x.Owner)
.WithMany()
.HasForeignKey("OwnerId")
.HasConstraintName("FK_Cats_Owners");
如果我们想要猫狗的主人而不是狼的主人,那么即使尝试用单独的 FK 约束来“欺骗”它也是行不通的。它期望 Animal 表上有 Dog_OwnerId 和 Cat_OwnerId。 如果所有动物都有主人,这很好,我们可以将主人放在动物中,一切正常,但狼有主人。不管怎样,无论我们使用单个 OwnerId 还是 DogOwnerId/CatOwnerId,这些 FK 都需要可以为空,如果猫和狗需要一个所有者,那么从引用完整性 PoV 来看,这可能并不理想。对于 TPH,我们需要在 Animal 表中包含 DogOwnerId 和 CatOwnerId,否则我们可以稍微“破解”它:
[Table("Animals")]
public abstract class Animal
{
[Key]
public int AnimalId { get; set; }
public string Name { get; set; }
public virtual Owner? Owner { get; set; }
}
public class Dog : Animal
{
}
public class Cat : Animal
{
}
public class Wolf : Animal
{
public new virtual Owner? Owner { get { return null; }; set { if (value != null) throw new InvalidOperationException("Wolves have no owner."); } }
}
这将 Owner 移至 Animal 以使 FK 关联满意,但我们在 Wolf 中重载了 Owner,因此如果有任何东西试图设置所有者,我们会抛出异常。这不会阻止数据库为狼分配所有者,并且 EF 甚至会很乐意按所有者查询狼。
如果某些继承类具有关系而其他类没有关系,并且要正确强制引用完整性,更好的解决方案是切换到 TPT 继承。在这里,我们将删除 Animal 表的 OwnerId 和 Discriminator。我们添加一个 Dog 表,AnimalId 为 PK + FK 为 Animal,OwnerId FK 为 Owner。我们将 AnimalId 为 PK + FK 的 Cat 表添加到 Animal,将 OwnerId FK 添加到 Owner。我们将包含 Animal 的 Wolf 表作为 PK + FK 添加到 Animal,没有 OwnerId。可以稍微更新实体的表名称:
[Table("Animals")]
public abstract class Animal
{
[Key]
public int AnimalId { get; set; }
public string Name { get; set; }
}
[Table("Dogs")]
public class Dog : Animal
{
public virtual Owner Owner { get; set; }
}
[Table("Cats")]
public class Cat : Animal
{
public virtual Owner Owner { get; set; }
}
[Table("Wolves")]
public class Wolf : Animal
{
}
...以及映射:
modelBuilder.Entity<Animal>()
.ToTable("Animals")
modelBuilder.Entity<Dog>()
.HasOne(x => x.Owner)
.WithMany()
.HasForeignKey("OwnerId");
modelBuilder.Entity<Cat>()
.HasOne(x => x.Owner)
.WithMany()
.HasForeignKey("OwnerId");
这里的问题是我们可以对子类表使用
[Table("Dogs")]
属性,但我们仍然需要对基类显式 .ToTable("Animals")
。这似乎是 TPT 得到认可的必要条件。仅使用 [Table]
属性似乎让 EF 期望使用 TPC 继承。