Remarque
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.
Vue d’ensemble des clés étrangères et des navigations
Les relations dans un modèle Entity Framework Core (EF Core) sont représentées à l’aide de clés étrangères (FK). Un FK se compose d’une ou plusieurs propriétés sur l’entité dépendante ou enfant dans la relation. Cette entité dépendante/enfant est associée à une entité principale/parente donnée lorsque les valeurs des propriétés de clé étrangère sur le dépendant/enfant correspondent aux valeurs des propriétés de clé principale ou primaire (PK) sur le principal/parent.
Les clés étrangères constituent un bon moyen de stocker et de manipuler des relations dans la base de données, mais elles ne sont pas très conviviales lors de l’utilisation de plusieurs entités associées dans le code d’application. Par conséquent, la plupart des modèles EF Core couchent également des « navigations » sur la représentation FK. Les navigations forment des références C#/.NET entre les instances d’entité qui reflètent les associations trouvées en correspondant aux valeurs de clé étrangère à des valeurs de clé primaire ou alternative.
Les navigations peuvent être utilisées des deux côtés de la relation, d’un côté uniquement, ou pas du tout, en laissant uniquement la propriété FK. La propriété FK peut être masquée en la rendant une propriété d’ombre. Pour plus d’informations sur la modélisation des relations, consultez Relations .
Conseil / Astuce
Ce document suppose que les états d’entité et les principes de base du suivi des modifications EF Core sont compris. Pour plus d’informations sur ces rubriques, consultez Change Tracking in EF Core .
Conseil / Astuce
Vous pouvez exécuter et déboguer dans tout le code de ce document en téléchargeant l’exemple de code à partir de GitHub.
Exemple de modèle
Le modèle suivant contient quatre types d’entités avec des relations entre eux. Les commentaires du code indiquent quelles propriétés sont des clés étrangères, des clés primaires et des propriétés de navigation.
public class Blog
{
public int Id { get; set; } // Primary key
public string Name { get; set; }
public IList<Post> Posts { get; } = new List<Post>(); // Collection navigation
public BlogAssets Assets { get; set; } // Reference navigation
}
public class BlogAssets
{
public int Id { get; set; } // Primary key
public byte[] Banner { get; set; }
public int? BlogId { get; set; } // Foreign key
public Blog Blog { get; set; } // Reference navigation
}
public class Post
{
public int Id { get; set; } // Primary key
public string Title { get; set; }
public string Content { get; set; }
public int? BlogId { get; set; } // Foreign key
public Blog Blog { get; set; } // Reference navigation
public IList<Tag> Tags { get; } = new List<Tag>(); // Skip collection navigation
}
public class Tag
{
public int Id { get; set; } // Primary key
public string Text { get; set; }
public IList<Post> Posts { get; } = new List<Post>(); // Skip collection navigation
}
Les trois relations de ce modèle sont les suivantes :
- Chaque blog peut avoir plusieurs billets (un-à-plusieurs) :
-
Blogest le principal/parent. -
Postest l'enfant/la personne à charge. Il contient la propriétéPost.BlogIdFK, dont la valeur doit correspondre à laBlog.Idvaleur PK du blog associé. -
Post.Blogest une navigation de référence d’un billet vers le blog associé.Post.Blogest la navigation inverse pourBlog.Posts. -
Blog.Postsest un élément de navigation d'un blog vers tous les articles associés.Blog.Postsest la navigation inverse pourPost.Blog.
-
- Chaque blog peut avoir un seul actif (un à un) :
-
Blogest le principal/parent. -
BlogAssetsest l'enfant/la personne à charge. Il contient la propriétéBlogAssets.BlogIdFK, dont la valeur doit correspondre à laBlog.Idvaleur PK du blog associé. -
BlogAssets.Blogest une navigation de référence des ressources vers le blog associé.BlogAssets.Blogest la navigation inverse pourBlog.Assets. -
Blog.Assetsest une navigation de référence du blog vers les ressources associées.Blog.Assetsest la navigation inverse pourBlogAssets.Blog.
-
- Chaque publication peut avoir plusieurs balises et chaque balise peut avoir plusieurs publications (plusieurs-à-plusieurs) :
- Les relations plusieurs-à-plusieurs constituent une couche supplémentaire par-dessus deux relations un-à-plusieurs. Les relations plusieurs-à-plusieurs sont abordées plus loin dans ce document.
-
Post.Tagsest un outil de navigation depuis un billet de blog vers toutes les balises associées.Post.Tagsest la navigation inverse pourTag.Posts. -
Tag.Postsest une navigation de collection d’une balise vers tous les billets associés.Tag.Postsest la navigation inverse pourPost.Tags.
Pour plus d’informations sur la façon de modéliser et de configurer des relations, consultez Relations .
Ajustement de relation
EF Core maintient les navigations en cohérence avec les valeurs de clés étrangères et vice versa. Autrement dit, si une valeur de clé étrangère change de sorte qu’elle fait maintenant référence à une entité principale/parent différente, les navigations sont mises à jour pour refléter cette modification. De même, si une navigation est modifiée, les valeurs de clé étrangère des entités impliquées sont mises à jour pour refléter cette modification. C’est ce qu’on appelle « ajustement de relation ».
Ajustement via requête
La correction se produit d’abord lorsque les entités sont interrogées à partir de la base de données. La base de données a uniquement des valeurs de clé étrangère. Par conséquent, lorsque EF Core crée une instance d’entité à partir de la base de données, elle utilise les valeurs de clé étrangère pour définir les navigations de référence et ajouter des entités aux navigations de collection selon les besoins. Par exemple, considérez une requête pour les blogs et ses publications et ressources associées :
using var context = new BlogsContext();
var blogs = await context.Blogs
.Include(e => e.Posts)
.Include(e => e.Assets)
.ToListAsync();
Console.WriteLine(context.ChangeTracker.DebugView.LongView);
Pour chaque blog, EF Core crée d’abord une Blog instance. Ensuite, chaque billet étant chargé à partir de la base de données, sa Post.Blog navigation de référence est définie pour pointer vers le blog associé. De même, le billet est ajouté à la navigation de la collection Blog.Posts. La même chose se produit avec BlogAssets, sauf dans ce cas les deux navigations sont des références. La Blog.Assets navigation est définie pour pointer vers l’instance de ressources, et la BlogAsserts.Blog navigation est définie sur l’instance de blog.
En examinant la vue de débogage du suivi des modifications après cette requête, on voit deux blogs, chacun avec une seule ressource et deux publications en cours de suivi.
Blog {Id: 1} Unchanged
Id: 1 PK
Name: '.NET Blog'
Assets: {Id: 1}
Posts: [{Id: 1}, {Id: 2}]
Blog {Id: 2} Unchanged
Id: 2 PK
Name: 'Visual Studio Blog'
Assets: {Id: 2}
Posts: [{Id: 3}, {Id: 4}]
BlogAssets {Id: 1} Unchanged
Id: 1 PK
Banner: <null>
BlogId: 1 FK
Blog: {Id: 1}
BlogAssets {Id: 2} Unchanged
Id: 2 PK
Banner: <null>
BlogId: 2 FK
Blog: {Id: 2}
Post {Id: 1} Unchanged
Id: 1 PK
BlogId: 1 FK
Content: 'Announcing the release of EF Core 5.0, a full featured cross...'
Title: 'Announcing the Release of EF Core 5.0'
Blog: {Id: 1}
Tags: []
Post {Id: 2} Unchanged
Id: 2 PK
BlogId: 1 FK
Content: 'F# 5 is the latest version of F#, the functional programming...'
Title: 'Announcing F# 5'
Blog: {Id: 1}
Tags: []
Post {Id: 3} Unchanged
Id: 3 PK
BlogId: 2 FK
Content: 'If you are focused on squeezing out the last bits of perform...'
Title: 'Disassembly improvements for optimized managed debugging'
Blog: {Id: 2}
Tags: []
Post {Id: 4} Unchanged
Id: 4 PK
BlogId: 2 FK
Content: 'Examine when database queries were executed and measure how ...'
Title: 'Database Profiling with Visual Studio'
Blog: {Id: 2}
Tags: []
La vue débogage affiche à la fois les valeurs de clé et les navigations. Les navigations sont affichées à l’aide des valeurs de clé primaire des entités associées. Par exemple, dans l'exemple de sortie ci-dessus, Posts: [{Id: 1}, {Id: 2}]Blog.Posts navigation de collection contient deux publications associées avec les clés primaires 1 et 2 respectivement. De même, pour chaque billet associé au premier blog, la Blog: {Id: 1} ligne indique que la Post.Blog navigation fait référence au blog avec la clé primaire 1.
Correction des entités suivies localement
La correction de relation se produit également entre les entités provenant d'une requête de suivi et les entités déjà suivies par le DbContext. Par exemple, envisagez d’exécuter trois requêtes distinctes pour les blogs, les billets et les ressources :
using var context = new BlogsContext();
var blogs = await context.Blogs.ToListAsync();
Console.WriteLine(context.ChangeTracker.DebugView.LongView);
var assets = await context.Assets.ToListAsync();
Console.WriteLine(context.ChangeTracker.DebugView.LongView);
var posts = await context.Posts.ToListAsync();
Console.WriteLine(context.ChangeTracker.DebugView.LongView);
En examinant à nouveau les vues de débogage, après la première requête, seuls les deux blogs sont suivis :
Blog {Id: 1} Unchanged
Id: 1 PK
Name: '.NET Blog'
Assets: <null>
Posts: []
Blog {Id: 2} Unchanged
Id: 2 PK
Name: 'Visual Studio Blog'
Assets: <null>
Posts: []
Les Blog.Assets navigations de référence sont null et les Blog.Posts navigations de collection sont vides, car aucune entité associée n’est actuellement suivie par le contexte.
Après la deuxième requête, les navigations de référence Blogs.Assets ont été corrigées afin de pointer vers les instances BlogAsset nouvellement suivies. De même, les BlogAssets.Blog navigations de référence sont définies pour pointer vers l’instance Blog appropriée déjà suivie.
Blog {Id: 1} Unchanged
Id: 1 PK
Name: '.NET Blog'
Assets: {Id: 1}
Posts: []
Blog {Id: 2} Unchanged
Id: 2 PK
Name: 'Visual Studio Blog'
Assets: {Id: 2}
Posts: []
BlogAssets {Id: 1} Unchanged
Id: 1 PK
Banner: <null>
BlogId: 1 FK
Blog: {Id: 1}
BlogAssets {Id: 2} Unchanged
Id: 2 PK
Banner: <null>
BlogId: 2 FK
Blog: {Id: 2}
Enfin, après la troisième requête, les Blog.Posts navigations de collection contiennent désormais toutes les publications associées, et les Post.Blog références pointent vers l’instance appropriée Blog :
Blog {Id: 1} Unchanged
Id: 1 PK
Name: '.NET Blog'
Assets: {Id: 1}
Posts: [{Id: 1}, {Id: 2}]
Blog {Id: 2} Unchanged
Id: 2 PK
Name: 'Visual Studio Blog'
Assets: {Id: 2}
Posts: [{Id: 3}, {Id: 4}]
BlogAssets {Id: 1} Unchanged
Id: 1 PK
Banner: <null>
BlogId: 1 FK
Blog: {Id: 1}
BlogAssets {Id: 2} Unchanged
Id: 2 PK
Banner: <null>
BlogId: 2 FK
Blog: {Id: 2}
Post {Id: 1} Unchanged
Id: 1 PK
BlogId: 1 FK
Content: 'Announcing the release of EF Core 5.0, a full featured cross...'
Title: 'Announcing the Release of EF Core 5.0'
Blog: {Id: 1}
Tags: []
Post {Id: 2} Unchanged
Id: 2 PK
BlogId: 1 FK
Content: 'F# 5 is the latest version of F#, the functional programming...'
Title: 'Announcing F# 5'
Blog: {Id: 1}
Tags: []
Post {Id: 3} Unchanged
Id: 3 PK
BlogId: 2 FK
Content: 'If you are focused on squeezing out the last bits of perform...'
Title: 'Disassembly improvements for optimized managed debugging'
Blog: {Id: 2}
Tags: []
Post {Id: 4} Unchanged
Id: 4 PK
BlogId: 2 FK
Content: 'Examine when database queries were executed and measure how ...'
Title: 'Database Profiling with Visual Studio'
Blog: {Id: 2}
Tags: []
Il s’agit du même état final que celui obtenu avec la requête unique d'origine, car EF Core a corrigé les navigations au fur et à mesure que les entités étaient suivies, même lorsque les résultats provenaient de plusieurs requêtes différentes.
Remarque
La correction n’entraîne jamais le retour de données supplémentaires à partir de la base de données. Il connecte uniquement les entités déjà retournées par la requête ou déjà suivies par dbContext. Consultez La résolution d’identité dans EF Core pour plus d’informations sur la gestion des doublons lors de la sérialisation des entités.
Modification des relations à l’aide de navigations
Le moyen le plus simple de modifier la relation entre deux entités consiste à manipuler une navigation, tout en laissant EF Core corriger les valeurs de navigation inverse et FK de manière appropriée. Pour ce faire, vous pouvez :
- Ajout ou suppression d’une entité d’une navigation de collection.
- Modification d’une navigation de référence pour pointer vers une autre entité ou la définir sur Null.
Ajout ou suppression d'éléments de navigation dans les collections
Par exemple, nous allons déplacer l’un des billets du blog Visual Studio vers le blog .NET. Cela nécessite d’abord charger les blogs et les billets, puis déplacer le billet de la collection de navigation sur un blog vers la collection de navigation sur l’autre blog :
using var context = new BlogsContext();
var dotNetBlog = await context.Blogs.Include(e => e.Posts).SingleAsync(e => e.Name == ".NET Blog");
var vsBlog = await context.Blogs.Include(e => e.Posts).SingleAsync(e => e.Name == "Visual Studio Blog");
Console.WriteLine(context.ChangeTracker.DebugView.LongView);
var post = vsBlog.Posts.Single(e => e.Title.StartsWith("Disassembly improvements"));
vsBlog.Posts.Remove(post);
dotNetBlog.Posts.Add(post);
context.ChangeTracker.DetectChanges();
Console.WriteLine(context.ChangeTracker.DebugView.LongView);
await context.SaveChangesAsync();
Conseil / Astuce
Un appel à ChangeTracker.DetectChanges() est nécessaire ici, car l'accès à la vue de débogage n'entraîne pas la détection automatique des modifications.
Il s'agit de l'affichage de débogage généré après avoir exécuté le code ci-dessus :
Blog {Id: 1} Unchanged
Id: 1 PK
Name: '.NET Blog'
Assets: <null>
Posts: [{Id: 1}, {Id: 2}, {Id: 3}]
Blog {Id: 2} Unchanged
Id: 2 PK
Name: 'Visual Studio Blog'
Assets: <null>
Posts: [{Id: 4}]
Post {Id: 1} Unchanged
Id: 1 PK
BlogId: 1 FK
Content: 'Announcing the release of EF Core 5.0, a full featured cross...'
Title: 'Announcing the Release of EF Core 5.0'
Blog: {Id: 1}
Tags: []
Post {Id: 2} Unchanged
Id: 2 PK
BlogId: 1 FK
Content: 'F# 5 is the latest version of F#, the functional programming...'
Title: 'Announcing F# 5'
Blog: {Id: 1}
Tags: []
Post {Id: 3} Modified
Id: 3 PK
BlogId: 1 FK Modified Originally 2
Content: 'If you are focused on squeezing out the last bits of perform...'
Title: 'Disassembly improvements for optimized managed debugging'
Blog: {Id: 1}
Tags: []
Post {Id: 4} Unchanged
Id: 4 PK
BlogId: 2 FK
Content: 'Examine when database queries were executed and measure how ...'
Title: 'Database Profiling with Visual Studio'
Blog: {Id: 2}
Tags: []
La Blog.Posts navigation sur le blog .NET comporte désormais trois billets (Posts: [{Id: 1}, {Id: 2}, {Id: 3}]). De même, la Blog.Posts navigation sur le blog Visual Studio n’a qu’un seul billet (Posts: [{Id: 4}]). Cela doit être attendu, car le code a explicitement modifié ces collections.
Plus intéressant encore, même si le code n’a pas explicitement modifié la Post.Blog navigation, celle-ci a été corrigée pour pointer vers le blog de Visual Studio (Blog: {Id: 1}). En outre, la Post.BlogId valeur de clé étrangère a été mise à jour pour correspondre à la valeur de clé primaire du blog .NET. Cette modification de la valeur du FK est ensuite conservée dans la base de données lorsque SaveChanges est appelé :
-- Executed DbCommand (0ms) [Parameters=[@p1='3' (DbType = String), @p0='1' (Nullable = true) (DbType = String)], CommandType='Text', CommandTimeout='30']
UPDATE "Posts" SET "BlogId" = @p0
WHERE "Id" = @p1;
SELECT changes();
Modification des navigations de référence
Dans l’exemple précédent, un billet a été déplacé d’un blog à un autre en manipulant la navigation de collection de billets sur chaque blog. La même chose peut être obtenue en modifiant plutôt la Post.Blog navigation de référence pour pointer vers le nouveau blog. Par exemple:
var post = vsBlog.Posts.Single(e => e.Title.StartsWith("Disassembly improvements"));
post.Blog = dotNetBlog;
La vue de débogage après cette modification est exactement la même que dans l’exemple précédent. Cela est dû au fait qu’EF Core a détecté le changement de navigation de référence, puis corrigé les navigations de collection et la valeur FK à mettre en correspondance.
Modification des relations en utilisant des valeurs de clé étrangère
Dans la section précédente, les relations ont été manipulées par des navigations laissant les valeurs de clé étrangère être mises à jour automatiquement. Il s’agit de la méthode recommandée pour manipuler les relations dans EF Core. Toutefois, il est également possible de manipuler directement les valeurs FK. Par exemple, nous pouvons déplacer un billet d’un blog vers un autre en modifiant la Post.BlogId valeur de clé étrangère :
var post = vsBlog.Posts.Single(e => e.Title.StartsWith("Disassembly improvements"));
post.BlogId = dotNetBlog.Id;
Notez comment cela est très similaire à la modification de la navigation de référence, comme illustré dans l’exemple précédent.
La vue de débogage après cette modification est exactement la même que celle des deux exemples précédents. Cela est dû au fait que EF Core a détecté la modification de la valeur FK, puis corrigé les navigations de référence et de collection à mettre en correspondance.
Conseil / Astuce
N’écrivez pas de code pour manipuler toutes les navigations et valeurs FK chaque fois qu’une relation change. Ce code est plus compliqué et doit garantir des modifications cohérentes des clés étrangères et des navigations dans tous les cas. Si possible, il suffit de manipuler une seule navigation, ou peut-être les deux navigations. Si nécessaire, il vous suffit de manipuler les valeurs FK. Évitez de manipuler à la fois les navigations et les valeurs FK.
Correctif pour les entités ajoutées ou supprimées
Ajout à la navigation d'une collection
EF Core effectue les actions suivantes lorsqu’il détecte qu’une nouvelle entité dépendante/enfant a été ajoutée à une navigation de collection :
- Si l’entité n’est pas suivie, alors elle est suivie. (L’entité est généralement dans l’état
Added. Toutefois, si le type d’entité est configuré pour utiliser des clés générées et que la valeur de clé primaire est définie, l’entité est suivie dans l’étatUnchanged.) - Si l’entité est associée à un autre principal/parent, cette relation est rompue.
- L'entité s'associe au principal/parent qui possède la navigation de la collection.
- Les navigations et les valeurs de clé étrangère sont ajustées pour toutes les entités impliquées.
En fonction de cela, nous pouvons voir que pour déplacer un billet d’un blog vers un autre, nous n’avons pas réellement besoin de le supprimer de l’ancienne navigation de collection avant de l’ajouter au nouveau. Par conséquent, le code de l’exemple ci-dessus peut être modifié à partir de :
var post = vsBlog.Posts.Single(e => e.Title.StartsWith("Disassembly improvements"));
vsBlog.Posts.Remove(post);
dotNetBlog.Posts.Add(post);
À :
var post = vsBlog.Posts.Single(e => e.Title.StartsWith("Disassembly improvements"));
dotNetBlog.Posts.Add(post);
EF Core voit que le billet a été ajouté à un nouveau blog et le supprime automatiquement de la collection sur le premier blog.
Suppression du menu de navigation de collection
La suppression d’une entité dépendante/enfant de la navigation de collection du principal/parent entraîne la séparation de la relation avec ce principal/parent. Ce qui se passe ensuite dépend si la relation est facultative ou obligatoire.
Relations facultatives
Par défaut, pour les relations facultatives, la valeur de clé étrangère est définie sur Null. Cela signifie que le dépendant/enfant n’est plus associé à aucun principal/parent. Par exemple, nous allons charger un blog et des billets, puis supprimer l’un des billets de la navigation de collection Blog.Posts :
var post = dotNetBlog.Posts.Single(e => e.Title == "Announcing F# 5");
dotNetBlog.Posts.Remove(post);
Après cette modification, la vue de débogage du suivi des modifications montre que :
- Le
Post.BlogIdFK a été défini sur Null (BlogId: <null> FK Modified Originally 1) - La
Post.Blognavigation de référence a été définie sur Null (Blog: <null>) - Le billet a été supprimé de la navigation de la collection
Blog.Posts(Posts: [{Id: 1}])
Blog {Id: 1} Unchanged
Id: 1 PK
Name: '.NET Blog'
Assets: <null>
Posts: [{Id: 1}]
Post {Id: 1} Unchanged
Id: 1 PK
BlogId: 1 FK
Content: 'Announcing the release of EF Core 5.0, a full featured cross...'
Title: 'Announcing the Release of EF Core 5.0'
Blog: {Id: 1}
Tags: []
Post {Id: 2} Modified
Id: 2 PK
BlogId: <null> FK Modified Originally 1
Content: 'F# 5 is the latest version of F#, the functional programming...'
Title: 'Announcing F# 5'
Blog: <null>
Tags: []
Notez que le billet n’est pas marqué comme Deleted. Elle est marquée comme Modified étant de sorte que la valeur FK dans la base de données soit définie sur Null lorsque SaveChanges est appelé.
Relations requises
La définition de la valeur FK sur Null n’est pas autorisée (et n’est généralement pas possible) pour les relations requises. Par conséquent, la séparation d’une relation requise signifie que l’entité dépendante/enfant doit être re-parentée vers un nouveau principal/parent, ou supprimée de la base de données lorsque SaveChanges est appelé pour éviter une violation de contrainte référentielle. Il s’agit de « suppression d’orphelins » et est le comportement par défaut dans EF Core pour les relations requises.
Par exemple, nous allons modifier la relation entre le blog et les billets pour qu’elles soient requises, puis exécuter le même code que dans l’exemple précédent :
var post = dotNetBlog.Posts.Single(e => e.Title == "Announcing F# 5");
dotNetBlog.Posts.Remove(post);
Après avoir effectué cette modification, la vue de débogage indique que :
- Le billet a été marqué comme
Deletedtel qu’il sera supprimé de la base de données lorsque SaveChanges est appelé. - La
Post.Blognavigation de référence a été définie sur Null (Blog: <null>). - Le billet a été supprimé de la navigation de la collection
Blog.Posts(Posts: [{Id: 1}]).
Blog {Id: 1} Unchanged
Id: 1 PK
Name: '.NET Blog'
Assets: <null>
Posts: [{Id: 1}]
Post {Id: 1} Unchanged
Id: 1 PK
BlogId: 1 FK
Content: 'Announcing the release of EF Core 5.0, a full featured cross...'
Title: 'Announcing the Release of EF Core 5.0'
Blog: {Id: 1}
Tags: []
Post {Id: 2} Deleted
Id: 2 PK
BlogId: 1 FK
Content: 'F# 5 is the latest version of F#, the functional programming...'
Title: 'Announcing F# 5'
Blog: <null>
Tags: []
Notez que le Post.BlogId reste inchangé, car pour une relation requise, il ne peut pas être défini sur Null.
L’appel de SaveChanges entraîne la suppression du billet orphelin :
-- Executed DbCommand (0ms) [Parameters=[@p0='2' (DbType = String)], CommandType='Text', CommandTimeout='30']
DELETE FROM "Posts"
WHERE "Id" = @p0;
SELECT changes();
Supprimer le minutage et le re-parentage des orphelins
Par défaut, le marquage des orphelins se Deleted produit dès que la modification de la relation est détectée. Toutefois, ce processus peut être retardé jusqu’à ce que SaveChanges soit réellement appelé. Cela peut être utile pour éviter de créer des entités orphelines qui ont été supprimées d'un principal/parent, mais seront rattachées à un nouveau principal/parent avant l'appel de SaveChanges.
ChangeTracker.DeleteOrphansTiming est utilisé pour définir ce minutage. Par exemple:
context.ChangeTracker.DeleteOrphansTiming = CascadeTiming.OnSaveChanges;
var post = vsBlog.Posts.Single(e => e.Title.StartsWith("Disassembly improvements"));
vsBlog.Posts.Remove(post);
context.ChangeTracker.DetectChanges();
Console.WriteLine(context.ChangeTracker.DebugView.LongView);
dotNetBlog.Posts.Add(post);
context.ChangeTracker.DetectChanges();
Console.WriteLine(context.ChangeTracker.DebugView.LongView);
await context.SaveChangesAsync();
Après avoir supprimé le billet de la première collection, l’objet n’est pas marqué comme Deleted dans l’exemple précédent. Au lieu de cela, EF Core suit que la relation est rompue même s’il s’agit d’une relation requise. (La valeur FK est considérée comme null par EF Core, même si elle ne peut pas vraiment être null, car le type n’est pas nullable. Il s’agit d’un « null conceptuel ».
Post {Id: 3} Modified
Id: 3 PK
BlogId: <null> FK Modified Originally 2
Content: 'If you are focused on squeezing out the last bits of perform...'
Title: 'Disassembly improvements for optimized managed debugging'
Blog: <null>
Tags: []
Appeler la fonction SaveChanges à ce moment entraînerait la suppression du billet orphelin. Toutefois, si comme dans l’exemple ci-dessus, le billet est associé à un nouveau blog avant que SaveChanges soit appelé, il sera corrigé de manière appropriée à ce nouveau blog et n’est plus considéré comme orphelin :
Post {Id: 3} Modified
Id: 3 PK
BlogId: 1 FK Modified Originally 2
Content: 'If you are focused on squeezing out the last bits of perform...'
Title: 'Disassembly improvements for optimized managed debugging'
Blog: {Id: 1}
Tags: []
SaveChanges appelé à ce stade met à jour le billet dans la base de données plutôt que de le supprimer.
Il est également possible de désactiver la suppression automatique des orphelins. Cela entraîne une exception si SaveChanges est appelé alors qu’un orphelin est suivi. Par exemple, ce code :
var dotNetBlog = await context.Blogs.Include(e => e.Posts).SingleAsync(e => e.Name == ".NET Blog");
context.ChangeTracker.DeleteOrphansTiming = CascadeTiming.Never;
var post = dotNetBlog.Posts.Single(e => e.Title == "Announcing F# 5");
dotNetBlog.Posts.Remove(post);
await context.SaveChangesAsync(); // Throws
Lève cette exception :
System.InvalidOperationException : L’association entre les entités « Blog » et « Post » avec la valeur de clé « {BlogId : 1} » a été interrompue, mais la relation est marquée comme obligatoire ou est implicitement requise, car la clé étrangère n’est pas nullable. Si l’entité dépendante/enfant doit être supprimée lorsqu’une relation requise est rompue, configurez la relation pour utiliser des suppressions en cascade.
La suppression des orphelins, ainsi que les suppressions en cascade, peut être forcée à tout moment en appelant ChangeTracker.CascadeChanges(). En combinant cela avec la définition du minutage de suppression des orphelins à Never, cela garantit que les orphelins ne sont jamais supprimés, sauf demande explicite de EF Core.
Modification d’une navigation de référence
La modification de la navigation de référence d’une relation un-à-plusieurs a le même effet que la modification de la navigation de collection à l’autre extrémité de la relation. Configurer la navigation de référence du dépendant/enfant sur null équivaut à supprimer l’entité de la collection de navigation du principal/parent. Toutes les modifications de correctif et de base de données se produisent comme décrit dans la section précédente, notamment en rendant l’entité orpheline si la relation est requise.
Relations facultatives un-à-un
Pour les relations un-à-un, la modification d’une navigation de référence entraîne une rupture de toute relation précédente. Pour les relations facultatives, cela signifie que la valeur FK sur la dépendance/enfant précédemment associée est définie sur Null. Par exemple:
using var context = new BlogsContext();
var dotNetBlog = await context.Blogs.Include(e => e.Assets).SingleAsync(e => e.Name == ".NET Blog");
dotNetBlog.Assets = new BlogAssets();
context.ChangeTracker.DetectChanges();
Console.WriteLine(context.ChangeTracker.DebugView.LongView);
await context.SaveChangesAsync();
La vue de débogage avant d’appeler SaveChanges indique que les nouvelles ressources ont remplacé les ressources existantes, qui sont désormais marquées comme Modified, avec une valeur de FK null BlogAssets.BlogId.
Blog {Id: 1} Unchanged
Id: 1 PK
Name: '.NET Blog'
Assets: {Id: -2147482629}
Posts: []
BlogAssets {Id: -2147482629} Added
Id: -2147482629 PK Temporary
Banner: <null>
BlogId: 1 FK
Blog: {Id: 1}
BlogAssets {Id: 1} Modified
Id: 1 PK
Banner: <null>
BlogId: <null> FK Modified Originally 1
Blog: <null>
Cela entraîne une mise à jour et une insertion lorsque SaveChanges est appelé :
-- Executed DbCommand (0ms) [Parameters=[@p1='1' (DbType = String), @p0=NULL], CommandType='Text', CommandTimeout='30']
UPDATE "Assets" SET "BlogId" = @p0
WHERE "Id" = @p1;
SELECT changes();
-- Executed DbCommand (0ms) [Parameters=[@p2=NULL, @p3='1' (Nullable = true) (DbType = String)], CommandType='Text', CommandTimeout='30']
INSERT INTO "Assets" ("Banner", "BlogId")
VALUES (@p2, @p3);
SELECT "Id"
FROM "Assets"
WHERE changes() = 1 AND "rowid" = last_insert_rowid();
Relations un-à-un requises
L’exécution du même code que dans l’exemple précédent, mais cette fois avec une relation un-à-un requise, montre que l’ancien associé BlogAssets est désormais marqué comme Deleted, car il devient orphelin lorsque le nouveau BlogAssets prend sa place :
Blog {Id: 1} Unchanged
Id: 1 PK
Name: '.NET Blog'
Assets: {Id: -2147482639}
Posts: []
BlogAssets {Id: -2147482639} Added
Id: -2147482639 PK Temporary
Banner: <null>
BlogId: 1 FK
Blog: {Id: 1}
BlogAssets {Id: 1} Deleted
Id: 1 PK
Banner: <null>
BlogId: 1 FK
Blog: <null>
Cela entraîne ensuite une suppression et une insertion lorsque SaveChanges est appelé :
-- Executed DbCommand (0ms) [Parameters=[@p0='1' (DbType = String)], CommandType='Text', CommandTimeout='30']
DELETE FROM "Assets"
WHERE "Id" = @p0;
SELECT changes();
-- Executed DbCommand (0ms) [Parameters=[@p1=NULL, @p2='1' (DbType = String)], CommandType='Text', CommandTimeout='30']
INSERT INTO "Assets" ("Banner", "BlogId")
VALUES (@p1, @p2);
SELECT "Id"
FROM "Assets"
WHERE changes() = 1 AND "rowid" = last_insert_rowid();
Le moment où les orphelins sont marqués comme supprimés peut être modifié de la même manière que pour les navigations dans les collections et a les mêmes effets.
Suppression d’une entité
Relations facultatives
Lorsqu’une entité est marquée comme Deleted, par exemple en appelant DbContext.Remove, les références à l’entité supprimée sont supprimées des navigations d’autres entités. Pour les relations facultatives, les valeurs FK dans les entités dépendantes sont définies sur Null.
Par exemple, nous allons marquer le blog Visual Studio comme Deleted.
using var context = new BlogsContext();
var vsBlog = await context.Blogs
.Include(e => e.Posts)
.Include(e => e.Assets)
.SingleAsync(e => e.Name == "Visual Studio Blog");
context.Remove(vsBlog);
Console.WriteLine(context.ChangeTracker.DebugView.LongView);
await context.SaveChangesAsync();
Avant d’appeler SaveChanges, la vue de débogage du suivi des modifications montre :
Blog {Id: 2} Deleted
Id: 2 PK
Name: 'Visual Studio Blog'
Assets: {Id: 2}
Posts: [{Id: 3}, {Id: 4}]
BlogAssets {Id: 2} Modified
Id: 2 PK
Banner: <null>
BlogId: <null> FK Modified Originally 2
Blog: <null>
Post {Id: 3} Modified
Id: 3 PK
BlogId: <null> FK Modified Originally 2
Content: 'If you are focused on squeezing out the last bits of perform...'
Title: 'Disassembly improvements for optimized managed debugging'
Blog: <null>
Tags: []
Post {Id: 4} Modified
Id: 4 PK
BlogId: <null> FK Modified Originally 2
Content: 'Examine when database queries were executed and measure how ...'
Title: 'Database Profiling with Visual Studio'
Blog: <null>
Tags: []
Notez que :
- Le blog est marqué comme
Deleted. - Les ressources associées au blog supprimé ont une valeur FK null (
BlogId: <null> FK Modified Originally 2) et une navigation de référence Null (Blog: <null>) - Chaque billet lié au blog supprimé a une valeur FK null (
BlogId: <null> FK Modified Originally 2) et une navigation de référence Null (Blog: <null>)
Relations requises
Le comportement de correction pour les relations requises est le même que pour les relations facultatives, sauf que les entités dépendantes/enfants sont marquées comme Deleted étant donné qu’elles ne peuvent pas exister sans principal/parent et doivent être supprimées de la base de données lorsque SaveChanges est appelé pour éviter une exception de contrainte référentielle. Il s’agit de « suppression en cascade » et est le comportement par défaut dans EF Core pour les relations requises. Par exemple, l’exécution du même code que dans l’exemple précédent, mais avec une relation requise entraîne l’affichage de débogage suivant avant que SaveChanges soit appelé :
Blog {Id: 2} Deleted
Id: 2 PK
Name: 'Visual Studio Blog'
Assets: {Id: 2}
Posts: [{Id: 3}, {Id: 4}]
BlogAssets {Id: 2} Deleted
Id: 2 PK
Banner: <null>
BlogId: 2 FK
Blog: {Id: 2}
Post {Id: 3} Deleted
Id: 3 PK
BlogId: 2 FK
Content: 'If you are focused on squeezing out the last bits of perform...'
Title: 'Disassembly improvements for optimized managed debugging'
Blog: {Id: 2}
Tags: []
Post {Id: 4} Deleted
Id: 4 PK
BlogId: 2 FK
Content: 'Examine when database queries were executed and measure how ...'
Title: 'Database Profiling with Visual Studio'
Blog: {Id: 2}
Tags: []
Comme prévu, les personnes dépendantes/enfants sont désormais marquées comme Deleted. Toutefois, notez que les navigations sur les entités supprimées n’ont pas changé. Cela peut sembler étrange, mais cela évite de déchiqueter complètement un graphe d’entités supprimé en effaçant toutes les navigations. Autrement dit, le blog, la ressource et les billets forment toujours un graphique d’entités même après avoir été supprimés. Cela facilite grandement la suppression d’un graphique d’entités que dans EF6 où le graphique a été déchiqueté.
Minutage de suppression en cascade et re-parentage
Par défaut, la suppression en cascade se produit dès que le parent/principal est marqué comme Deleted. Il s’agit de la même chose que pour la suppression d’orphelins, comme décrit précédemment. Comme pour la suppression des orphelins, ce processus peut être retardé jusqu’à ce que SaveChanges soit appelé, ou même désactivé entièrement, en définissant ChangeTracker.CascadeDeleteTiming correctement. Cela est utile de la même façon que pour supprimer des orphelins, y compris pour les enfants/personnes dépendantes après la suppression d’un principal/parent.
Les suppressions en cascade, ainsi que la suppression d’orphelins, peuvent être forcées à tout moment en appelant ChangeTracker.CascadeChanges(). La combinaison de ce paramètre avec la définition du minutage Never de suppression en cascade garantit que les suppressions en cascade ne se produisent jamais, sauf si EF Core est explicitement invité à le faire.
Conseil / Astuce
La suppression en cascade et la suppression d’orphelins sont étroitement liées. Les deux entraînent la suppression d’entités dépendantes/enfants lorsque la relation avec leur principal/parent requis est rompue. Pour la suppression en cascade, cette rupture se produit parce que le principal/parent est lui-même supprimé. Pour les orphelins, l’entité principal/parent existe toujours, mais n’est plus liée aux entités dépendantes/enfants.
Relations plusieurs à plusieurs
Les relations plusieurs-à-plusieurs dans EF Core sont implémentées à l’aide d’une entité de jointure. Chaque côté de la relation plusieurs-à-plusieurs est lié à cette entité de jointure avec une relation un-à-plusieurs. Cette entité de jointure peut être explicitement définie et mappée, ou elle peut être créée implicitement et masquée. Dans les deux cas, le comportement sous-jacent est le même. Nous allons d’abord examiner ce comportement sous-jacent pour comprendre comment le suivi des relations plusieurs-à-plusieurs fonctionne.
Comment fonctionnent les relations plusieurs à plusieurs
Considérez ce modèle EF Core qui crée une relation plusieurs-à-plusieurs entre les publications et les balises à l’aide d’un type d’entité de jointure explicitement défini :
public class Post
{
public int Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public int? BlogId { get; set; }
public Blog Blog { get; set; }
public IList<PostTag> PostTags { get; } = new List<PostTag>(); // Collection navigation
}
public class Tag
{
public int Id { get; set; }
public string Text { get; set; }
public IList<PostTag> PostTags { get; } = new List<PostTag>(); // Collection navigation
}
public class PostTag
{
public int PostId { get; set; } // First part of composite PK; FK to Post
public int TagId { get; set; } // Second part of composite PK; FK to Tag
public Post Post { get; set; } // Reference navigation
public Tag Tag { get; set; } // Reference navigation
}
Notez que le type d’entité PostTag de jointure contient deux propriétés de clé étrangère. Dans ce modèle, pour qu’un billet soit lié à une balise, il doit y avoir une entité de jointure PostTag où la PostTag.PostId valeur de clé étrangère correspond à la Post.Id valeur de clé primaire et où la PostTag.TagId valeur de clé étrangère correspond à la Tag.Id valeur de clé primaire. Par exemple:
using var context = new BlogsContext();
var post = await context.Posts.SingleAsync(e => e.Id == 3);
var tag = await context.Tags.SingleAsync(e => e.Id == 1);
context.Add(new PostTag { PostId = post.Id, TagId = tag.Id });
Console.WriteLine(context.ChangeTracker.DebugView.LongView);
En examinant la vue de débogage du suivi des modifications après avoir exécuté ce code, le billet et la balise sont liés par la nouvelle PostTag entité de jointure :
Post {Id: 3} Unchanged
Id: 3 PK
BlogId: 2 FK
Content: 'If you are focused on squeezing out the last bits of perform...'
Title: 'Disassembly improvements for optimized managed debugging'
Blog: <null>
PostTags: [{PostId: 3, TagId: 1}]
PostTag {PostId: 3, TagId: 1} Added
PostId: 3 PK FK
TagId: 1 PK FK
Post: {Id: 3}
Tag: {Id: 1}
Tag {Id: 1} Unchanged
Id: 1 PK
Text: '.NET'
PostTags: [{PostId: 3, TagId: 1}]
Notez que les navigations de collection sur Post et Tag ont été réparées, tout comme les navigations de référence sur PostTag. Ces relations peuvent être manipulées par des navigations au lieu de valeurs FK, comme dans tous les exemples précédents. Par exemple, le code ci-dessus peut être modifié pour ajouter la relation en définissant les navigations de référence sur l’entité de jointure :
context.Add(new PostTag { Post = post, Tag = tag });
Cela entraîne exactement la même modification des clés et des navigations que dans l’exemple précédent.
Ignorer les navigations
La manipulation manuelle de la table de jointure peut être fastidieuse. Les relations plusieurs-à-plusieurs peuvent être manipulées directement à l’aide de navigations de collection spéciales qui « ignorent » l’entité de jointure. Par exemple, deux navigations de saut peuvent être ajoutées au modèle ci-dessus : l'une allant d'un Post à des Tags, et l'autre allant d'un Tag à des Posts.
public class Post
{
public int Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public int? BlogId { get; set; }
public Blog Blog { get; set; }
public IList<Tag> Tags { get; } = new List<Tag>(); // Skip collection navigation
public IList<PostTag> PostTags { get; } = new List<PostTag>(); // Collection navigation
}
public class Tag
{
public int Id { get; set; }
public string Text { get; set; }
public IList<Post> Posts { get; } = new List<Post>(); // Skip collection navigation
public IList<PostTag> PostTags { get; } = new List<PostTag>(); // Collection navigation
}
public class PostTag
{
public int PostId { get; set; } // First part of composite PK; FK to Post
public int TagId { get; set; } // Second part of composite PK; FK to Tag
public Post Post { get; set; } // Reference navigation
public Tag Tag { get; set; } // Reference navigation
}
Cette relation plusieurs-à-plusieurs nécessite la configuration suivante pour vous assurer que les navigations skip et les navigations normales sont toutes utilisées pour la même relation plusieurs-à-plusieurs :
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Post>()
.HasMany(p => p.Tags)
.WithMany(p => p.Posts)
.UsingEntity<PostTag>(
j => j.HasOne(t => t.Tag).WithMany(p => p.PostTags),
j => j.HasOne(t => t.Post).WithMany(p => p.PostTags));
}
Consultez Relations pour plus d’informations sur le mappage des relations plusieurs à plusieurs.
Les navigations de saut ressemblent et fonctionnent comme des navigations de collection normales. Toutefois, la façon dont elles fonctionnent avec des valeurs de clé étrangère est différente. Nous allons associer un billet à une balise, mais cette fois à l’aide d’un saut de navigation :
using var context = new BlogsContext();
var post = await context.Posts.SingleAsync(e => e.Id == 3);
var tag = await context.Tags.SingleAsync(e => e.Id == 1);
post.Tags.Add(tag);
context.ChangeTracker.DetectChanges();
Console.WriteLine(context.ChangeTracker.DebugView.LongView);
Notez que ce code n’utilise pas l’entité de jointure. Il ajoute plutôt une entité à une collection de navigation de la même façon que si cela était une relation un-à-plusieurs. La vue de débogage résultante est essentiellement la même qu’avant :
Post {Id: 3} Unchanged
Id: 3 PK
BlogId: 2 FK
Content: 'If you are focused on squeezing out the last bits of perform...'
Title: 'Disassembly improvements for optimized managed debugging'
Blog: <null>
PostTags: [{PostId: 3, TagId: 1}]
Tags: [{Id: 1}]
PostTag {PostId: 3, TagId: 1} Added
PostId: 3 PK FK
TagId: 1 PK FK
Post: {Id: 3}
Tag: {Id: 1}
Tag {Id: 1} Unchanged
Id: 1 PK
Text: '.NET'
PostTags: [{PostId: 3, TagId: 1}]
Posts: [{Id: 3}]
Notez qu'une instance de l'entité de jointure PostTag a été créée automatiquement avec des valeurs de FK définies sur les valeurs de PK de la balise et du post qui sont désormais associés. Toutes les navigations de référence et de collection normales ont été corrigées pour correspondre à ces valeurs FK. De plus, étant donné que ce modèle contient des liens de navigation pour ignorer, ceux-ci ont également été corrigés. Précisément, même si nous avons ajouté la balise à la Post.Tags navigation de saut, la Tag.Posts navigation de saut inversée de l’autre côté de cette relation a également été corrigée pour contenir l'article associé.
Il convient de noter que les relations de plusieurs à plusieurs sous-jacentes peuvent toujours être manipulées directement, même lorsque les navigations de saut ont été insérées. Par exemple, la balise et la publication peuvent être associées comme nous l’avons fait avant d’introduire des fonctions de contournement de navigation :
context.Add(new PostTag { Post = post, Tag = tag });
Ou à l’aide de valeurs FK :
context.Add(new PostTag { PostId = post.Id, TagId = tag.Id });
Cela entraîne toujours la correction correcte des navigations skip, ce qui entraîne la même sortie de vue de débogage que dans l’exemple précédent.
Ignorer les navigations uniquement
Dans la section précédente, nous avons ajouté ignorer les navigations en plus de définir entièrement les deux relations un-à-plusieurs sous-jacentes. Cela est utile pour illustrer ce qui arrive aux valeurs FK, mais est souvent inutile. Au lieu de cela, la relation plusieurs-à-plusieurs peut être définie uniquement à l'aide de navigations de contournement. Il s’agit de la façon dont la relation plusieurs-à-plusieurs est définie dans le modèle en haut de ce document. À l’aide de ce modèle, nous pouvons à nouveau associer un billet et une balise en ajoutant un billet à la Tag.Posts navigation ignorer (ou, alternativement, en ajoutant une balise à la Post.Tags navigation ignorer) :
using var context = new BlogsContext();
var post = await context.Posts.SingleAsync(e => e.Id == 3);
var tag = await context.Tags.SingleAsync(e => e.Id == 1);
post.Tags.Add(tag);
context.ChangeTracker.DetectChanges();
Console.WriteLine(context.ChangeTracker.DebugView.LongView);
En regardant dans la vue de débogage après avoir apporté cette modification, on constate que EF Core a créé une instance de Dictionary<string, object> pour représenter l'entité de jointure. Cette entité de jointure contient les propriétés de clé étrangère PostsId et TagsId qui ont été définies pour correspondre aux valeurs de clé primaire de l'article et de la balise associée.
Post {Id: 3} Unchanged
Id: 3 PK
BlogId: 2 FK
Content: 'If you are focused on squeezing out the last bits of perform...'
Title: 'Disassembly improvements for optimized managed debugging'
Blog: <null>
Tags: [{Id: 1}]
Tag {Id: 1} Unchanged
Id: 1 PK
Text: '.NET'
Posts: [{Id: 3}]
PostTag (Dictionary<string, object>) {PostsId: 3, TagsId: 1} Added
PostsId: 3 PK FK
TagsId: 1 PK FK
Pour plus d’informations sur les entités de jointure implicite et l’utilisation des types d’entités, consultez Dictionary<string, object>.
Important
Le type CLR utilisé pour les types d’entités de jointure par convention peut changer dans les futures versions afin d’améliorer les performances. Ne dépendez pas du type Dictionary<string, object> de jointure, sauf s’il a été configuré explicitement.
Joindre des entités avec des charges utiles
Jusqu’à présent, tous les exemples ont utilisé un type d’entité de jointure (explicite ou implicite) qui contient uniquement les deux propriétés de clé étrangère nécessaires pour la relation plusieurs-à-plusieurs. Aucune de ces valeurs FK ne doit être explicitement définie par l’application lors de la manipulation des relations, car leurs valeurs proviennent des propriétés de clé primaire des entités associées. Cela permet à EF Core de créer des instances de l’entité de jointure sans données manquantes.
Charges utiles avec des valeurs générées
EF Core prend en charge l’ajout de propriétés supplémentaires au type d’entité de jointure. Il s’agit de donner à l’entité de jointure une « charge utile ». Par exemple, nous allons ajouter TaggedOn une propriété à l’entité PostTag de jointure :
public class PostTag
{
public int PostId { get; set; } // First part of composite PK; FK to Post
public int TagId { get; set; } // Second part of composite PK; FK to Tag
public DateTime TaggedOn { get; set; } // Payload
}
Cette propriété de charge utile ne sera pas définie lorsque EF Core crée une instance d’entité de jointure. La façon la plus courante de traiter cela consiste à utiliser des propriétés de charge utile avec des valeurs générées automatiquement. Par exemple, la TaggedOn propriété peut être configurée pour utiliser un horodatage généré par le magasin lorsque chaque nouvelle entité est insérée :
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Post>()
.HasMany(p => p.Tags)
.WithMany(p => p.Posts)
.UsingEntity<PostTag>(
j => j.HasOne<Tag>().WithMany(),
j => j.HasOne<Post>().WithMany(),
j => j.Property(e => e.TaggedOn).HasDefaultValueSql("CURRENT_TIMESTAMP"));
}
Un billet peut maintenant être étiqueté de la même façon que précédemment :
using var context = new BlogsContext();
var post = await context.Posts.SingleAsync(e => e.Id == 3);
var tag = await context.Tags.SingleAsync(e => e.Id == 1);
post.Tags.Add(tag);
await context.SaveChangesAsync();
Console.WriteLine(context.ChangeTracker.DebugView.LongView);
En examinant la vue de débogage du suivi des changements après avoir appelé SaveChanges, la propriété de charge utile a été correctement définie :
Post {Id: 3} Unchanged
Id: 3 PK
BlogId: 2 FK
Content: 'If you are focused on squeezing out the last bits of perform...'
Title: 'Disassembly improvements for optimized managed debugging'
Blog: <null>
Tags: [{Id: 1}]
PostTag {PostId: 3, TagId: 1} Unchanged
PostId: 3 PK FK
TagId: 1 PK FK
TaggedOn: '12/29/2020 8:13:21 PM'
Tag {Id: 1} Unchanged
Id: 1 PK
Text: '.NET'
Posts: [{Id: 3}]
Définition explicite des valeurs de charge utile
À partir de l’exemple précédent, nous allons ajouter une propriété de charge utile qui n’utilise pas de valeur générée automatiquement :
public class PostTag
{
public int PostId { get; set; } // First part of composite PK; FK to Post
public int TagId { get; set; } // Second part of composite PK; FK to Tag
public DateTime TaggedOn { get; set; } // Auto-generated payload property
public string TaggedBy { get; set; } // Not-generated payload property
}
Un billet peut maintenant être étiqueté de la même façon que précédemment, et l’entité de jointure sera toujours créée automatiquement. Cette entité est ensuite accessible à l’aide de l’un des mécanismes décrits dans Accès aux entités suivies. Par exemple, le code ci-dessous utilise DbSet<TEntity>.Find pour accéder à l’instance d’entité de jointure :
using var context = new BlogsContext();
var post = await context.Posts.SingleAsync(e => e.Id == 3);
var tag = await context.Tags.SingleAsync(e => e.Id == 1);
post.Tags.Add(tag);
context.ChangeTracker.DetectChanges();
var joinEntity = await context.Set<PostTag>().FindAsync(post.Id, tag.Id);
joinEntity.TaggedBy = "ajcvickers";
await context.SaveChangesAsync();
Console.WriteLine(context.ChangeTracker.DebugView.LongView);
Une fois que l’entité de jointure a été localisée, elle peut être manipulée de manière normale dans cet exemple pour définir la TaggedBy propriété de charge utile avant d’appeler SaveChanges.
Remarque
Notez qu’un appel à ChangeTracker.DetectChanges() est requis ici pour permettre à EF Core de détecter la modification de la propriété de navigation et de créer l’instance d’entité de jointure avant l’utilisation de Find. Pour plus d’informations, consultez Détection des modifications et notifications .
Une autre possibilité est de concevoir explicitement l’entité de jointure pour associer un billet à une balise. Par exemple:
using var context = new BlogsContext();
var post = context.Posts.SingleAsync(e => e.Id == 3);
var tag = context.Tags.SingleAsync(e => e.Id == 1);
context.Add(
new PostTag { PostId = post.Id, TagId = tag.Id, TaggedBy = "ajcvickers" });
await context.SaveChangesAsync();
Console.WriteLine(context.ChangeTracker.DebugView.LongView);
Enfin, une autre façon de définir des données de charge utile consiste à remplacer SaveChanges ou à utiliser l’événement DbContext.SavingChanges pour traiter les entités avant de mettre à jour la base de données. Par exemple:
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
foreach (var entityEntry in ChangeTracker.Entries<PostTag>())
{
if (entityEntry.State == EntityState.Added)
{
entityEntry.Entity.TaggedBy = "ajcvickers";
}
}
return await base.SaveChangesAsync(cancellationToken);
}