Notes
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
EF Core offre beaucoup de flexibilité quand il s’agit de mapper des types d’entités à des tables dans une base de données. Cela devient encore plus utile lorsque vous devez utiliser une base de données qui n’a pas été créée par EF.
Les techniques ci-dessous sont décrites au moyen de tables, mais le même résultat peut également être obtenu par le mappage vers des vues.
Fractionnement de table
EF Core permet de mapper deux entités ou plus à une seule ligne. Il s’agit du fractionnement de table ou du partage de table.
Paramétrage
Pour utiliser le fractionnement de table, les types d’entités doivent être mappés à la même table, les clés primaires sont mappées aux mêmes colonnes et au moins une relation configurée entre la clé primaire d’un type d’entité et une autre dans la même table.
Un scénario courant de fractionnement de table est d'utiliser uniquement un sous-ensemble des colonnes de la table pour des performances améliorées ou une meilleure encapsulation.
Dans cet exemple Order
, il représente un sous-ensemble de DetailedOrder
.
public class Order
{
public int Id { get; set; }
public OrderStatus? Status { get; set; }
public DetailedOrder DetailedOrder { get; set; }
}
public class DetailedOrder
{
public int Id { get; set; }
public OrderStatus? Status { get; set; }
public string BillingAddress { get; set; }
public string ShippingAddress { get; set; }
public byte[] Version { get; set; }
}
En plus de la configuration requise, nous utilisons Property(o => o.Status).HasColumnName("Status")
pour mapper DetailedOrder.Status
à la même colonne que Order.Status
.
modelBuilder.Entity<DetailedOrder>(
dob =>
{
dob.ToTable("Orders");
dob.Property(o => o.Status).HasColumnName("Status");
});
modelBuilder.Entity<Order>(
ob =>
{
ob.ToTable("Orders");
ob.Property(o => o.Status).HasColumnName("Status");
ob.HasOne(o => o.DetailedOrder).WithOne()
.HasForeignKey<DetailedOrder>(o => o.Id);
ob.Navigation(o => o.DetailedOrder).IsRequired();
});
Conseil / Astuce
Pour plus de contexte, consultez l’exemple de projet complet .
Utilisation
L’enregistrement et l’interrogation d’entités à l’aide du fractionnement de table se font comme pour les autres entités :
using (var context = new TableSplittingContext())
{
await context.Database.EnsureDeletedAsync();
await context.Database.EnsureCreatedAsync();
context.Add(
new Order
{
Status = OrderStatus.Pending,
DetailedOrder = new DetailedOrder
{
Status = OrderStatus.Pending,
ShippingAddress = "221 B Baker St, London",
BillingAddress = "11 Wall Street, New York"
}
});
await context.SaveChangesAsync();
}
using (var context = new TableSplittingContext())
{
var pendingCount = await context.Orders.CountAsync(o => o.Status == OrderStatus.Pending);
Console.WriteLine($"Current number of pending orders: {pendingCount}");
}
using (var context = new TableSplittingContext())
{
var order = await context.DetailedOrders.FirstAsync(o => o.Status == OrderStatus.Pending);
Console.WriteLine($"First pending order will ship to: {order.ShippingAddress}");
}
Entité dépendante facultative
Si toutes les colonnes utilisées par une entité dépendante se trouvent NULL
dans la base de données, aucune instance ne sera créée lors de l’interrogation. Cela permet de modéliser une entité dépendante facultative, où la propriété de relation sur le principal serait null. Remarquez que cela peut également se produire si toutes les propriétés dépendantes sont facultatives et définies à null
, ce qui pourrait ne pas être attendu.
Toutefois, la vérification supplémentaire peut avoir un impact sur les performances des requêtes. En outre, si le type d’entité dépendante a à leur tour des dépendants, la détermination de savoir si une instance doit être créée n’est pas triviale. Pour éviter ces problèmes, le type d’entité dépendante peut être marqué comme requis, consultez Les dépendances un-à-un requises pour plus d’informations.
Jetons de concurrence
Si l’un des types d’entités partageant une table a un jeton d’accès concurrentiel, il doit également être inclus dans tous les autres types d’entités. Cela est nécessaire pour éviter une valeur obsolète de jeton de concurrence lorsqu'une seule des entités mappées à la même table est mise à jour.
Pour éviter d’exposer le jeton d’accès concurrentiel au code consommateur, il est possible de le créer comme une propriété d’ombre :
modelBuilder.Entity<Order>()
.Property<byte[]>("Version").IsRowVersion().HasColumnName("Version");
modelBuilder.Entity<DetailedOrder>()
.Property(o => o.Version).IsRowVersion().HasColumnName("Version");
Héritage
Il est recommandé de lire la page dédiée sur l’héritage avant de continuer avec cette section.
Les types dépendants utilisant le fractionnement de table peuvent avoir une hiérarchie d’héritage, mais il existe certaines limitations :
- Le type d’entité dépendant ne peut pas utiliser le mappage TPC, car les types dérivés ne peuvent pas être mappés à la même table.
- Le type d’entité dépendant peut utiliser le mappage TPT, mais seul le type d’entité racine peut utiliser le fractionnement de table.
- Si le type d’entité principal utilise TPC, seuls les types d’entités qui n’ont pas de descendants peuvent utiliser le fractionnement de table. Sinon, les colonnes dépendantes doivent être dupliquées sur les tables correspondant aux types dérivés, ce qui complique toutes les interactions.
Fractionnement d'entité
EF Core permet de mapper une entité à des lignes dans deux tables ou plus. Il s’agit du fractionnement d’entité.
Paramétrage
Par exemple, considérez une base de données avec trois tables qui contiennent des données client :
- Tableau
Customers
pour les informations sur le client - Tableau
PhoneNumbers
pour le numéro de téléphone du client - Tableau
Addresses
pour l’adresse du client
Voici les définitions de ces tables dans SQL Server :
CREATE TABLE [Customers] (
[Id] int NOT NULL IDENTITY,
[Name] nvarchar(max) NOT NULL,
CONSTRAINT [PK_Customers] PRIMARY KEY ([Id])
);
CREATE TABLE [PhoneNumbers] (
[CustomerId] int NOT NULL,
[PhoneNumber] nvarchar(max) NULL,
CONSTRAINT [PK_PhoneNumbers] PRIMARY KEY ([CustomerId]),
CONSTRAINT [FK_PhoneNumbers_Customers_CustomerId] FOREIGN KEY ([CustomerId]) REFERENCES [Customers] ([Id]) ON DELETE CASCADE
);
CREATE TABLE [Addresses] (
[CustomerId] int NOT NULL,
[Street] nvarchar(max) NOT NULL,
[City] nvarchar(max) NOT NULL,
[PostCode] nvarchar(max) NULL,
[Country] nvarchar(max) NOT NULL,
CONSTRAINT [PK_Addresses] PRIMARY KEY ([CustomerId]),
CONSTRAINT [FK_Addresses_Customers_CustomerId] FOREIGN KEY ([CustomerId]) REFERENCES [Customers] ([Id]) ON DELETE CASCADE
);
Chacune de ces tables est généralement mappée à leur propre type d’entité, avec des relations entre les types. Toutefois, si les trois tables sont toujours utilisées ensemble, il peut être plus pratique de les mapper à un seul type d’entité. Par exemple:
public class Customer
{
public Customer(string name, string street, string city, string? postCode, string country)
{
Name = name;
Street = street;
City = city;
PostCode = postCode;
Country = country;
}
public int Id { get; set; }
public string Name { get; set; }
public string? PhoneNumber { get; set; }
public string Street { get; set; }
public string City { get; set; }
public string? PostCode { get; set; }
public string Country { get; set; }
}
Ceci est réalisé dans EF7 en appelant SplitToTable
pour chaque division dans le type d’entité. Par exemple, le code suivant fractionne le type d’entité Customer
dans les tableaux Customers
, PhoneNumbers
, et Addresses
ci-dessus :
modelBuilder.Entity<Customer>(
entityBuilder =>
{
entityBuilder
.ToTable("Customers")
.SplitToTable(
"PhoneNumbers",
tableBuilder =>
{
tableBuilder.Property(customer => customer.Id).HasColumnName("CustomerId");
tableBuilder.Property(customer => customer.PhoneNumber);
})
.SplitToTable(
"Addresses",
tableBuilder =>
{
tableBuilder.Property(customer => customer.Id).HasColumnName("CustomerId");
tableBuilder.Property(customer => customer.Street);
tableBuilder.Property(customer => customer.City);
tableBuilder.Property(customer => customer.PostCode);
tableBuilder.Property(customer => customer.Country);
});
});
Notez également que, si nécessaire, différents noms de colonnes peuvent être spécifiés pour chacune des tables. Pour configurer le nom de la colonne de la table principale, consultez la configuration de facette spécifique à la table.
Configuration de la clé étrangère de liaison
Le FK qui lie les tables mappées cible les mêmes propriétés sur lesquelles elle est déclarée. Normalement, il ne serait pas créé dans la base de données, car il serait redondant. Toutefois, il existe une exception lorsque le type d’entité est mappé à plusieurs tables. Pour modifier ses facettes, vous pouvez utiliser l’API Fluent de configuration de relation :
modelBuilder.Entity<Customer>()
.HasOne<Customer>()
.WithOne()
.HasForeignKey<Customer>(a => a.Id)
.OnDelete(DeleteBehavior.Restrict);
Limites
- Le fractionnement d’entité ne peut pas être utilisé pour les types d’entités dans les hiérarchies.
- Pour toute ligne de la table principale, il doit y avoir une ligne dans chacune des tables fractionnées (les fragments ne sont pas facultatifs).
Configuration de facette spécifique à une table
Certains modèles de mappage entraînent le mappage de la même propriété CLR à une colonne dans chacune de plusieurs tables différentes. EF7 permet à ces colonnes d’avoir des noms différents. Par exemple, considérez une hiérarchie d’héritage simple :
public abstract class Animal
{
public int Id { get; set; }
public string Breed { get; set; } = null!;
}
public class Cat : Animal
{
public string? EducationalLevel { get; set; }
}
public class Dog : Animal
{
public string? FavoriteToy { get; set; }
}
Avec la stratégie mappage d’héritage TPT, ces types seront mappés sur trois tables. Toutefois, la colonne clé primaire de chaque table peut avoir un nom différent. Par exemple:
CREATE TABLE [Animals] (
[Id] int NOT NULL IDENTITY,
[Breed] nvarchar(max) NOT NULL,
CONSTRAINT [PK_Animals] PRIMARY KEY ([Id])
);
CREATE TABLE [Cats] (
[CatId] int NOT NULL,
[EducationalLevel] nvarchar(max) NULL,
CONSTRAINT [PK_Cats] PRIMARY KEY ([CatId]),
CONSTRAINT [FK_Cats_Animals_CatId] FOREIGN KEY ([CatId]) REFERENCES [Animals] ([Id]) ON DELETE CASCADE
);
CREATE TABLE [Dogs] (
[DogId] int NOT NULL,
[FavoriteToy] nvarchar(max) NULL,
CONSTRAINT [PK_Dogs] PRIMARY KEY ([DogId]),
CONSTRAINT [FK_Dogs_Animals_DogId] FOREIGN KEY ([DogId]) REFERENCES [Animals] ([Id]) ON DELETE CASCADE
);
EF7 permet de configurer ce mappage à l’aide d’un générateur de tables imbriqué :
modelBuilder.Entity<Animal>().ToTable("Animals");
modelBuilder.Entity<Cat>()
.ToTable(
"Cats",
tableBuilder => tableBuilder.Property(cat => cat.Id).HasColumnName("CatId"));
modelBuilder.Entity<Dog>()
.ToTable(
"Dogs",
tableBuilder => tableBuilder.Property(dog => dog.Id).HasColumnName("DogId"));
Avec le mappage d’héritage TPC, la Breed
propriété peut également être mappée à différents noms de colonnes dans différentes tables. Par exemple, tenez compte des tables TPC suivantes :
CREATE TABLE [Cats] (
[CatId] int NOT NULL DEFAULT (NEXT VALUE FOR [AnimalSequence]),
[CatBreed] nvarchar(max) NOT NULL,
[EducationalLevel] nvarchar(max) NULL,
CONSTRAINT [PK_Cats] PRIMARY KEY ([CatId])
);
CREATE TABLE [Dogs] (
[DogId] int NOT NULL DEFAULT (NEXT VALUE FOR [AnimalSequence]),
[DogBreed] nvarchar(max) NOT NULL,
[FavoriteToy] nvarchar(max) NULL,
CONSTRAINT [PK_Dogs] PRIMARY KEY ([DogId])
);
EF7 prend en charge ce mappage de table :
modelBuilder.Entity<Animal>().UseTpcMappingStrategy();
modelBuilder.Entity<Cat>()
.ToTable(
"Cats",
builder =>
{
builder.Property(cat => cat.Id).HasColumnName("CatId");
builder.Property(cat => cat.Breed).HasColumnName("CatBreed");
});
modelBuilder.Entity<Dog>()
.ToTable(
"Dogs",
builder =>
{
builder.Property(dog => dog.Id).HasColumnName("DogId");
builder.Property(dog => dog.Breed).HasColumnName("DogBreed");
});