我如何映射派生的 class 具有 Entity Framework 的实体集合

How can I map a derived class having a collection of entities with Entity Framework

如何映射 DogsCats 以收集 Toys 而不是 Rats 因为它们不是宠物?

public abstract class Animal {
    public int Id { get; set; }
}

public class Cat : Animal {
    public virtual ICollection<Toy> Toys { get; set; }
}

public class Dog : Animal {
    public virtual ICollection<Toy> Toys { get; set; }
}

public class Rat : Animal {

}

public class Toy {
    public int Id { get; set; }

    public string Name { get; set; }

    public Animal Owner { get; set; }

    public int OwnerId { get; set; }
}

我试过,用流畅的映射,做:

public class CatEntityConfiguration : EntityConfiguration<Cat> {
    public CatEntityConfiguration() {
        HasMany(c => c.Toys).WithRequired(t => t.Owner);
    }
}

但是 Owner 必须是 Cat 类型,而不是 Animal,当然 Dog 希望 OwnerDog,而不是 AnimalCat。我可以在 Toy 上制作一个 Dog 和一个 Cat 属性,但这似乎是一个 hack,而不是一个解决方案。

Entity Framework 的问题在于它想要在实体之间建立 非常紧密的关系 ,它确实如此。如果你说'Toy:你可以拥有任何 Animal 作为所有者,但只有一些 Animal 类型会让你成为 child' 然后 Entity Framework 读作“我不能允许 Toy 隐含地引用 Animal,因为 Animal 不知道任何 关于 Toy,而Toy实际上与CatDog有关。'

解决此问题的最简单方法是使 Pet 成为 class 和 Toys,然后使 CatDog 继承 Pet class,其中 Rat 仍然是 Animal:

public abstract class Animal
{
    public int Id { get; set; }
}

public abstract class Pet : Animal
{
    public virtual ICollection<Toy> Toys { get; set; }
}

public class Cat : Pet
{
    public string Name { get; set; }
}

public class Dog : Pet
{
    public int CollarColor { get; set; }
}

public class Rat : Animal
{

}

public class Toy
{
    public int Id { get; set; }
    public Pet Owner { get; set; }
}

EF 然后将创建一个 Pets table,其中包含一个 Discriminator 列和 both 我们的 objects 的属性( DogCat 不再是数据库实体):

CREATE TABLE [dbo].[Pets] (
    [Id]            INT            IDENTITY (1, 1) NOT NULL,
    [Discriminator] NVARCHAR (128) NOT NULL,
    [Name]          NVARCHAR (MAX) NULL,
    [CollarColor]   INT            NULL,
    CONSTRAINT [PK_dbo.Pets] PRIMARY KEY CLUSTERED ([Id] ASC)
);

CreateTable(
    "dbo.Pets",
    c => new
        {
            Id = c.Int(nullable: false, identity: true),
            Discriminator = c.String(nullable: false, maxLength: 128),
            Name = c.String(),
            CollarColor = c.Int(),
        })
    .PrimaryKey(t => t.Id);

当我们添加一个名为 FrankCat 时,我们得到:

Id  Discriminator   Name    CollarColor
1   Cat             Frank   NULL

如果你不喜欢这个数据库结构,另一种选择是创建一个PetAttributes class,每个Pet得到一个,然后Toys是那个class的一部分,每个玩具都属于一个PetAttribute,然后生活还要继续。唯一的问题是您的导航变为 Cat.Attributes.Toys,但您可以构建一个仅 get 的 属性 来解决这个问题。

要删除 Discriminator 列,我们可以添加:

modelBuilder.Ignore<Pet>();

然后构建一个新的,但仍然是 non-optimal 数据库结构:

CreateTable(
    "dbo.Toys",
    c => new
        {
            Id = c.Int(nullable: false, identity: true),
            Cat_Id = c.Int(),
            Dog_Id = c.Int(),
        })
    .PrimaryKey(t => t.Id)
    .ForeignKey("dbo.Cats", t => t.Cat_Id)
    .ForeignKey("dbo.Dogs", t => t.Dog_Id)
    .Index(t => t.Cat_Id)
    .Index(t => t.Dog_Id);

所以,最后一个也是最后一个选项,用 Toys:

创建一个 PetAttributes class
public abstract class Pet : Animal
{
    public PetAttributes Attributes { get; set; }
}

public class PetAttributes
{
    [Key]
    public int OwnerId { get; set; }

    [ForeignKey(nameof(OwnerId))]
    public Pet Owner { get; set; }

    public virtual ICollection<Toy> Toys { get; set; }
}

public class Toy
{
    public int Id { get; set; }
    public PetAttributes Owner { get; set; }
}

我们覆盖OnModelCreating:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);

    modelBuilder.Ignore<Pet>();
    modelBuilder.Entity<Pet>().HasRequired(p => p.Attributes).WithRequiredDependent(a => a.Owner);
}

我们得到一个新的 table 结构:

CreateTable(
    "dbo.Cats",
    c => new
        {
            Id = c.Int(nullable: false, identity: true),
            Name = c.String(),
            Attributes_OwnerId = c.Int(),
        })
    .PrimaryKey(t => t.Id)
    .ForeignKey("dbo.PetAttributes", t => t.Attributes_OwnerId)
    .Index(t => t.Attributes_OwnerId);

CreateTable(
    "dbo.PetAttributes",
    c => new
        {
            OwnerId = c.Int(nullable: false, identity: true),
        })
    .PrimaryKey(t => t.OwnerId);

CreateTable(
    "dbo.Toys",
    c => new
        {
            Id = c.Int(nullable: false, identity: true),
            Owner_OwnerId = c.Int(),
        })
    .PrimaryKey(t => t.Id)
    .ForeignKey("dbo.PetAttributes", t => t.Owner_OwnerId)
    .Index(t => t.Owner_OwnerId);

然后,我们可以将更多属性移动到 PetPetAttributes,并为它们创建 get-only 属性,如果它们在 PetAttributes:

public abstract class Pet : Animal
{
    public string Name { get; set; }

    public PetAttributes Attributes { get; set; }

    public ICollection<Toy> Toys => Attributes.Toys;
}
CreateTable(
    "dbo.Cats",
    c => new
        {
            Id = c.Int(nullable: false, identity: true),
            Name = c.String(),
            Attributes_OwnerId = c.Int(),
        })
    .PrimaryKey(t => t.Id)
    .ForeignKey("dbo.PetAttributes", t => t.Attributes_OwnerId)
    .Index(t => t.Attributes_OwnerId);

CreateTable(
    "dbo.Dogs",
    c => new
        {
            Id = c.Int(nullable: false, identity: true),
            CollarColor = c.Int(nullable: false),
            Name = c.String(),
            Attributes_OwnerId = c.Int(),
        })
    .PrimaryKey(t => t.Id)
    .ForeignKey("dbo.PetAttributes", t => t.Attributes_OwnerId)
    .Index(t => t.Attributes_OwnerId);

正如我们所见,EF 使得将一个实体映射为 多个 其他实体关系的多个部分变得困难但并非不可能 .这主要是由于类型限制:EF also 使得在 type 错误的关系中很难出错。