Das Problem mit Entity Framework ist, dass es eine sehr enge Beziehung zwischen Entitäten will, tut es wirklich. Wenn Sie sagen 'Toy
: Sie dürfen Animal
als Besitzer haben, aber nur einige Animal
Arten haben Sie als Kind' dann liest Entity Framework das als 'Ich kann Toy
nicht erlauben, eine Referenz auf Animal
implizit zu haben , weil Animal
nichts weiß überToy
und Toy
ist eigentlich mit einem Cat
oder einem Dog
verbunden.
Der einfachste Weg, dies zu beheben, ist Pet
eine Klasse mit Toys
zu machen, dann Cat
machen und Dog
erben die Pet
Klasse, wo Rat
noch ein 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 wird dann erstellen Pets
Tisch mit einer Discriminator
Spalte und den Eigenschaften sowohl unsere Objekte (Dog
und Cat
sind nicht mehr die Datenbank-Entities):
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);
Und wenn wir einen Cat
namens Frank
hinzufügen, erhalten wir:
Id Discriminator Name CollarColor
1 Cat Frank NULL
Wenn Sie diese DB-Struktur nicht mögen, die andere Option ist eine PetAttributes
Klasse zu erstellen, von was jedes Pet
bekommt eins, und dann ist Toys
ein Teil dieser Klasse, jedes Spielzeug ist im Besitz eines PetAttribute
, und dann geht das Leben weiter. Das einzige Problem ist Ihre Navigation wird Cat.Attributes.Toys
, aber Sie könnten eine get
-only-Eigenschaft erstellen, um das zu umgehen.
Um diese Discriminator
Spalte loszuwerden, könnten wir hinzufügen:
modelBuilder.Ignore<Pet>();
diese dann eine neue, bauen aber immer noch nicht optimal DB-Struktur:
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);
So, die letzte und letzte Option, erstellen Sie eine PetAttributes
Klasse mit der Toys
:
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; }
}
Wir außer Kraft setzen OnModelCreating
:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Ignore<Pet>();
modelBuilder.Entity<Pet>().HasRequired(p => p.Attributes).WithRequiredDependent(a => a.Owner);
}
Und wir bekommen eine neue Tabellenstruktur:
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);
Dann können wir mehr Attribute in Pet
oder PetAttributes
, bewegen und schaffen get
-nur Eigenschaften für sie, wenn sie in PetAttributes
sind:
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);
Wie wir sehen können, EF macht es schwierig, aber nicht unmöglich eine Einheit als viele Teil mehrere andere Entitätsbeziehungen abzubilden. Dies ist weitgehend auf die Typabhängigkeiten zurückzuführen: EF auch macht es schwierig, einen Fehler in Typ Fehler zu machen.
Die Antwort hängt davon ab, wie Sie die Vererbung abbilden möchten. Möchten Sie auch die Basisklasse 'Animal' abbilden, oder möchten Sie EF davon abhalten, so dass jeder Subtyp seine eigene unabhängige Zuordnung hat? –
@GertArnold Die Basisklasse und die abgeleiteten Klassen sind bereits zugeordnet. Ich nehme an, ich hätte Animal als abstrakt markieren sollen. Aber ja, Animal ist eine Entität, die EF kennt. –