如何使用Entity Framework映射具有实体集合的派生类

Jam*_*lls 2 c# entity-framework ef-fluent-api

我如何映射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; }
}
Run Code Online (Sandbox Code Playgroud)

我试着用流畅的映射做:

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

Owner必须是类型Cat,不是Animal,但当然,Dog想要Owner成为类型Dog,而不是AnimalCat.我可以创建一个Dog和一个Cat属性Toy,但这似乎是一个黑客,而不是一个解决方案.

410*_*one 5

实体框架的问题在于它希望实体之间的关系非常紧密,确实如此.如果你说' Toy:你被允许拥有任何Animal作为所有者,但只有一些Animal类型会让你作为一个孩子',那么实体框架将其视为'我不能允许隐式Toy引用Animal,因为Animal不知道什么有关 Toy,而Toy实际上与一个Cat或一个Dog".

解决这个问题的最简单方法是创建Pet一个类Toys,然后创建CatDog继承Pet该类,其中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; }
}
Run Code Online (Sandbox Code Playgroud)

然后,EF将创建一个Pets包含Discriminator列和两个对象属性的表(Dog并且Cat不再是数据库实体):

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);
Run Code Online (Sandbox Code Playgroud)

当我们添加一个Cat名字时Frank,我们得到:

Id  Discriminator   Name    CollarColor
1   Cat             Frank   NULL
Run Code Online (Sandbox Code Playgroud)

如果你不喜欢这个DB结构,另一个选择是创建一个PetAttributes类,每个类Pet都有一个,然后Toys是该类的一部分,每个玩具都由a拥有PetAttribute,然后生活继续.唯一的问题是你的导航变成了Cat.Attributes.Toys,但是你可以建立一个只get用来解决它的问题.

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

modelBuilder.Ignore<Pet>();
Run Code Online (Sandbox Code Playgroud)

然后,这将构建一个新的但仍然不是最佳的DB结构:

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);
Run Code Online (Sandbox Code Playgroud)

所以,最后一个和最后一个选项,创建一个PetAttributesToys:

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; }
}
Run Code Online (Sandbox Code Playgroud)

我们覆盖OnModelCreating:

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

    modelBuilder.Ignore<Pet>();
    modelBuilder.Entity<Pet>().HasRequired(p => p.Attributes).WithRequiredDependent(a => a.Owner);
}
Run Code Online (Sandbox Code Playgroud)

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

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);
Run Code Online (Sandbox Code Playgroud)

然后,我们可以将更多属性移动到Petor中PetAttributes,并get为它们创建-only属性(如果它们位于PetAttributes:

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

    public PetAttributes Attributes { get; set; }

    public ICollection<Toy> Toys => Attributes.Toys;
}
Run Code Online (Sandbox Code Playgroud)
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);
Run Code Online (Sandbox Code Playgroud)

正如我们所看到的,EF使得将一个实体映射为多个其他实体关系的许多部分变得困难但并非不可能.这很大程度上是由于类型限制:EF 很难在类型错误的关系中犯错误.