Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Información general sobre las claves externas y las navegaciones
Las relaciones en un modelo de Entity Framework Core (EF Core) se representan mediante claves externas (FK). Un FK consta de una o varias propiedades en la entidad dependiente o secundaria de la relación. Esta entidad dependiente o secundaria está asociada a una entidad principal o primaria determinada cuando los valores de las propiedades de clave externa en el elemento dependiente o secundario coinciden con los valores de las propiedades de clave principal o alternativa (PK) en la entidad de seguridad o elemento primario.
Las claves externas son una buena manera de almacenar y manipular relaciones en la base de datos, pero no son muy fáciles de usar cuando se trabaja con varias entidades relacionadas en el código de la aplicación. Por lo tanto, la mayoría de los modelos de EF Core también superponen las "navegaciones" sobre la representación de FK. Las navegaciones forman referencias en C#/.NET entre instancias de entidad que reflejan las asociaciones encontradas por coincidencia de valores de clave externa con los valores de clave principal o clave alternativa.
Las navegaciones se pueden usar en ambos lados de la relación, solo en un lado, o no en absoluto, dejando solo la propiedad FK. La propiedad FK se puede ocultar haciendo que sea una propiedad sombra. Consulte Relaciones para obtener más información sobre el modelado de relaciones.
Sugerencia
En este documento se da por supuesto que se comprenden los estados de entidad y los conceptos básicos del seguimiento de cambios de EF Core. Consulte Change Tracking en EF Core para obtener más información sobre estos temas.
Sugerencia
Puede ejecutar y depurar todo el código de este documento descargando el código de ejemplo de GitHub.
Modelo de ejemplo
El modelo siguiente contiene cuatro tipos de entidad con relaciones entre ellos. Los comentarios del código indican qué propiedades son claves externas, claves primarias y propiedades de navegación.
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
}
Las tres relaciones de este modelo son:
- Cada blog puede tener muchas entradas (uno a varios):
-
Blog
es el principal o el principal (padre). -
Post
es el elemento dependiente o hijo. Contiene la propiedad FKPost.BlogId
, cuyo valor debe coincidir con el valor PKBlog.Id
del blog relacionado. -
Post.Blog
es una navegación de referencia desde una entrada al blog asociado.Post.Blog
es la navegación inversa paraBlog.Posts
. -
Blog.Posts
es una navegación para recoger todas las entradas relacionadas desde un blog.Blog.Posts
es la navegación inversa paraPost.Blog
.
-
- Cada blog puede tener un recurso (uno a uno):
-
Blog
es el principal o el principal (padre). -
BlogAssets
es el elemento dependiente o hijo. Contiene la propiedad FKBlogAssets.BlogId
, cuyo valor debe coincidir con el valor PKBlog.Id
del blog relacionado. -
BlogAssets.Blog
es una navegación de referencia desde los recursos al blog asociado.BlogAssets.Blog
es la navegación inversa paraBlog.Assets
. -
Blog.Assets
es una navegación de referencia desde el blog a los recursos asociados.Blog.Assets
es la navegación inversa paraBlogAssets.Blog
.
-
- Cada publicación puede tener muchas etiquetas y cada etiqueta puede tener muchas publicaciones (uno a uno).
- Las relaciones de varios a varios son un nivel adicional sobre dos relaciones de uno a varios. Las relaciones de muchos a muchos se abordarán más adelante en este documento.
-
Post.Tags
es una navegación de colección de una publicación a todas las etiquetas asociadas.Post.Tags
es la navegación inversa paraTag.Posts
. -
Tag.Posts
es una navegación de colección de una etiqueta a todas las publicaciones asociadas.Tag.Posts
es la navegación inversa paraPost.Tags
.
Consulte Relaciones para obtener más información sobre cómo modelar y configurar relaciones.
Ajuste de relaciones
EF Core mantiene las navegaciones alineadas con los valores de clave externa y viceversa. Es decir, si un valor de clave externa cambia de modo que ahora hace referencia a una entidad principal o primaria diferente, las navegaciones se actualizan para reflejar este cambio. Del mismo modo, si se cambia una navegación, los valores de clave externa de las entidades implicadas se actualizan para reflejar este cambio. Esto se denomina "corrección de relaciones".
Ajuste mediante consulta
La corrección se produce primero cuando se consultan entidades desde la base de datos. La base de datos solo tiene valores de clave externa, por lo que cuando EF Core crea una instancia de entidad a partir de la base de datos, usa los valores de clave externa para establecer las navegaciones de referencia y agregar entidades a las navegaciones de colección según corresponda. Por ejemplo, considere una consulta para blogs y sus publicaciones y recursos asociados:
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);
Para cada blog, EF Core creará primero una Blog
instancia. A continuación, a medida que cada entrada se carga desde la base de datos, su Post.Blog
navegación de referencia se establece para que apunte al blog asociado. Del mismo modo, la publicación se agrega a la navegación de la colección Blog.Posts
. Lo mismo sucede con BlogAssets
, excepto en este caso, ambas navegaciones son referencias. La navegación Blog.Assets
está configurada para apuntar a la instancia de recursos, y la navegación BlogAsserts.Blog
está configurada para apuntar a la instancia del blog.
Al examinar la vista de depuración de seguimiento de cambios después de esta consulta se muestran dos blogs, cada uno con un recurso y dos publicaciones a las que se realiza el seguimiento:
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 vista de depuración muestra los valores clave y las navegaciones. Las navegaciones se muestran mediante los valores de clave principal de las entidades relacionadas. Por ejemplo, Posts: [{Id: 1}, {Id: 2}]
en la salida anterior indica que la navegación de la colección Blog.Posts
contiene dos publicaciones relacionadas con las claves principales 1 y 2, respectivamente. Del mismo modo, para cada entrada asociada al primer blog, la Blog: {Id: 1}
línea indica que la Post.Blog
navegación hace referencia al blog con la clave principal 1.
Corrección de entidades rastreadas localmente
La corrección de relaciones también se produce entre las entidades devueltas por una consulta de seguimiento y las entidades que ya son rastreadas por el DbContext. Por ejemplo, considere la posibilidad de ejecutar tres consultas independientes para blogs, publicaciones y recursos:
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);
Al examinar de nuevo las vistas de depuración, después de la primera consulta solo se rastrean los dos blogs.
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: []
Las Blog.Assets
navegaciones de referencia son nulas y las Blog.Posts
navegaciones de colección están vacías porque actualmente no se realiza el seguimiento de entidades asociadas por el contexto.
Después de la segunda consulta, las Blogs.Assets
referencias de navegación se han corregido para que apunten a las nuevas instancias rastreadas BlogAsset
. Del mismo modo, las BlogAssets.Blog
navegaciones de referencia se establecen para que apunten a la instancia adecuada Blog
ya rastreada.
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}
Por último, después de la tercera consulta, las navegaciones de la colección Blog.Posts
ahora contienen todas las entradas relacionadas, y las referencias Post.Blog
apuntan a la instancia Blog
adecuada.
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: []
Este es el mismo estado final que se logró con la consulta única original, ya que EF Core ajustó las navegaciones mientras las entidades eran rastreadas, incluso cuando provenían de varias consultas diferentes.
Nota:
La corrección nunca hace que se devuelvan más datos de la base de datos. Solo conecta las entidades que ya son devueltas por la consulta o que ya son rastreadas por el DbContext. Consulte Identity Resolution in EF Core (Resolución de identidades en EF Core ) para obtener información sobre cómo controlar duplicados al serializar entidades.
Cambio de relaciones mediante las navegaciones
La manera más fácil de cambiar la relación entre dos entidades es manipular una navegación, al tiempo que deja EF Core para corregir la navegación inversa y los valores de FK adecuadamente. Esto se puede hacer mediante:
- Agregar o quitar una entidad de una navegación de colección.
- Cambiar una navegación de referencia para que apunte a una entidad diferente o establecerla en null.
Agregar o quitar de las navegaciones de recopilación
Por ejemplo, vamos a mover una de las entradas del blog de Visual Studio al blog de .NET. Esto requiere primero cargar los blogs y entradas y, a continuación, mover la entrada de la colección de navegación en un blog a la colección de navegación en el otro 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();
Sugerencia
Aquí se necesita una llamada a ChangeTracker.DetectChanges() porque acceder a la vista de depuración no provoca la detección automática de cambios.
Esta es la vista de depuración mostrada después de ejecutar el código de arriba.
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
navegación en el blog de .NET ahora tiene tres entradas (Posts: [{Id: 1}, {Id: 2}, {Id: 3}]
). Del mismo modo, la Blog.Posts
navegación en el blog de Visual Studio solo tiene una entrada (Posts: [{Id: 4}]
). Esto se espera, ya que el código cambió explícitamente estas colecciones.
Más interesantemente, aunque el código no cambió explícitamente la Post.Blog
navegación, se ha corregido para apuntar al blog de Visual Studio (Blog: {Id: 1}
). Además, el Post.BlogId
valor de clave externa se ha actualizado para que coincida con el valor de clave principal del blog de .NET. Este cambio en el valor de FK en se conserva en la base de datos cuando se llama a SaveChanges:
-- 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();
Cambio de las navegaciones de referencia
En el ejemplo anterior, una entrada se movió de un blog a otro manipulando la navegación de la colección de entradas en cada blog. Se puede lograr lo mismo cambiando la navegación de referencia Post.Blog
para que apunte al nuevo blog. Por ejemplo:
var post = vsBlog.Posts.Single(e => e.Title.StartsWith("Disassembly improvements"));
post.Blog = dotNetBlog;
La vista de depuración después de este cambio es exactamente la misma que en el ejemplo anterior. Esto se debe a que EF Core detectó el cambio de navegación de referencia y, a continuación, corrigió las navegaciones de colección y el valor de FK para que coincidan.
Cambio de relaciones mediante valores de clave externa
En la sección anterior, las relaciones se manipularon a través de las navegaciones, dejando que los valores de clave externa se actualicen automáticamente. Esta es la manera recomendada de manipular las relaciones en EF Core. Sin embargo, también es posible manipular los valores de FK directamente. Por ejemplo, podemos mover una entrada de un blog a otro cambiando el valor de Post.BlogId
clave externa:
var post = vsBlog.Posts.Single(e => e.Title.StartsWith("Disassembly improvements"));
post.BlogId = dotNetBlog.Id;
Observe cómo esto es muy similar a cambiar la navegación de referencia, como se muestra en el ejemplo anterior.
La vista de depuración después de este cambio es exactamente igual que en el caso de los dos ejemplos anteriores. Esto se debe a que EF Core detectó el cambio de valor de FK y, a continuación, corrigió las navegaciones de referencia y colección para que coincidan.
Sugerencia
No escriba código para manipular todas las navegaciones y los valores de FK cada vez que cambia una relación. Este código es más complicado y debe garantizar cambios coherentes en las claves externas y las navegaciones en cada caso. Si es posible, manipule solo una navegación o quizás ambas. Si es necesario, solo manipule los valores de FK. Evite manipular las navegaciones y los valores de FK.
Ajuste de entidades agregadas o eliminadas
Agregar a la navegación de una colección
EF Core realiza las siguientes acciones cuando detecta que se ha agregado una nueva entidad dependiente o secundaria a una navegación de recopilación:
- Si no se realiza el seguimiento de la entidad, se realiza el seguimiento. (Normalmente, la entidad estará en estado
Added
. Sin embargo, si el tipo de entidad está configurado para usar claves generadas y se establece el valor de clave principal, se realiza el seguimiento de la entidad en elUnchanged
estado). - Si la entidad está asociada a un principal o padre diferente, entonces esa relación se rompe.
- La entidad se asocia a la entidad principal o primaria que posee la navegación de la colección.
- Las navegaciones y los valores de clave externa se fijan para todas las entidades implicadas.
En función de esto, podemos ver que para mover una entrada de un blog a otro no es necesario quitarla de la navegación anterior de la colección antes de agregarla a la nueva. Por lo tanto, el código del ejemplo anterior se puede cambiar de:
var post = vsBlog.Posts.Single(e => e.Title.StartsWith("Disassembly improvements"));
vsBlog.Posts.Remove(post);
dotNetBlog.Posts.Add(post);
A:
var post = vsBlog.Posts.Single(e => e.Title.StartsWith("Disassembly improvements"));
dotNetBlog.Posts.Add(post);
EF Core ve que la entrada se ha agregado a un nuevo blog y la quita automáticamente de la colección en el primer blog.
Quitar de una navegación de colección
Al quitar una entidad dependiente o secundaria de la navegación de la colección de la entidad principal o primaria, ocasiona la ruptura de la relación con esa entidad principal o primaria. Lo que sucede a continuación depende de si la relación es opcional o necesaria.
Relaciones opcionales
De forma predeterminada, para las relaciones opcionales, el valor de clave externa se establece en NULL. Esto significa que el dependiente/secundario ya no está relacionado con ningún principal/elemento primario. Por ejemplo, vamos a cargar un blog y entradas y, a continuación, quitar una de las entradas de la navegación de la Blog.Posts
colección:
var post = dotNetBlog.Posts.Single(e => e.Title == "Announcing F# 5");
dotNetBlog.Posts.Remove(post);
Al examinar la vista de depuración de seguimiento de cambios después de este cambio, se observa que:
- La
Post.BlogId
FK se ha establecido en nulo (BlogId: <null> FK Modified Originally 1
) - La
Post.Blog
navegación de referencia se ha establecido en NULL (Blog: <null>
) - La publicación se ha quitado de la navegación de la colección
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: []
Observe que la publicación no está marcada como Deleted
. Se marca como Modified
para que el valor de FK de la base de datos se establezca en NULL cuando se llame a SaveChanges.
Relaciones necesarias
No se permite establecer el valor de FK en NULL (y normalmente no es posible) para las relaciones necesarias. Por lo tanto, la ruptura de una relación necesaria significa que la entidad dependiente o secundaria debe ser asignada a un nuevo principal/padre, o eliminada de la base de datos cuando se llama a SaveChanges, para evitar una violación de restricción referencial. Esto se conoce como "eliminar huérfanos" y es el comportamiento predeterminado en EF Core para las relaciones necesarias.
Por ejemplo, vamos a cambiar la relación entre blog y entradas que se van a requerir y, a continuación, ejecute el mismo código que en el ejemplo anterior:
var post = dotNetBlog.Posts.Single(e => e.Title == "Announcing F# 5");
dotNetBlog.Posts.Remove(post);
Al examinar la vista de depuración después de este cambio, se muestra que:
- La publicación se ha marcado como
Deleted
, de manera que se eliminará de la base de datos cuando se llame a SaveChanges. - La
Post.Blog
navegación de referencia se ha establecido en NULL (Blog: <null>
). - La publicación se ha eliminado de la navegación de colección
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: []
Observe que el Post.BlogId
permanece sin cambios, ya que para una relación necesaria no se puede establecer en nulo.
Al llamar a SaveChanges, se elimina la publicación huérfana:
-- Executed DbCommand (0ms) [Parameters=[@p0='2' (DbType = String)], CommandType='Text', CommandTimeout='30']
DELETE FROM "Posts"
WHERE "Id" = @p0;
SELECT changes();
Eliminación de tiempo huérfanos y re-parenting
De forma predeterminada, marcar huérfanos como Deleted
sucede en cuanto se detecta el cambio de relación. Sin embargo, este proceso se puede retrasar hasta que se llame realmente a SaveChanges. Esto puede ser útil para evitar convertir en huérfanos entidades que se han quitado de un principal/padre, pero serán reasociados con un nuevo principal/padre antes de llamar a SaveChanges.
ChangeTracker.DeleteOrphansTiming se usa para establecer este tiempo. Por ejemplo:
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();
Después de quitar la publicación de la primera colección, el objeto no está marcado como Deleted
en el ejemplo anterior. En su lugar, EF Core está realizando un seguimiento de que la relación se ha severado aunque se trata de una relación necesaria. (EF Core considera que el valor de FK es NULL aunque realmente no puede ser NULL porque el tipo no admite valores NULL. Esto se conoce como "conceptual 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: []
Al ejecutar SaveChanges ahora, se eliminaría la publicación huérfana. Sin embargo, si como en el ejemplo anterior, la publicación está asociada a un nuevo blog antes de llamar a SaveChanges, se vinculará adecuadamente con ese nuevo blog y ya no se considerará un huérfano.
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 llamado en este momento actualizará la publicación en la base de datos en lugar de eliminarla.
También es posible desactivar la eliminación automática de huérfanos. Esto producirá una excepción si se llama a SaveChanges mientras se realiza un seguimiento de un huérfano. Por ejemplo, este código:
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
Lanzará esta excepción:
System.InvalidOperationException: La asociación entre las entidades 'Blog' y 'Post' con el valor de clave '{BlogId: 1}' ha sido interrumpida, pero la relación está marcada como necesaria o se requiere implícitamente porque la clave externa no puede admitir valores NULL. Si se debe eliminar la entidad dependiente o secundaria cuando se ha severado una relación necesaria, configure la relación para usar eliminaciones en cascada.
La eliminación de huérfanos, así como la eliminación en cascada, se puede forzar en cualquier momento mediante una llamada a ChangeTracker.CascadeChanges(). Combinar esto con la configuración del tiempo de eliminación huérfano para garantizar que los huérfanos nunca se eliminen a Never
menos que EF Core se indique explícitamente que lo haga.
Cambio de una navegación de referencia
Cambiar la navegación de referencia en una relación de uno a varios tiene el mismo efecto que modificar la navegación de la colección en el otro lado de la relación. Establecer la navegación de referencia de dependiente o secundario en NULL equivale a quitar la entidad de la navegación de colección de la entidad principal o primaria. Todos los ajustes y cambios en la base de datos se producen como se describe en la sección anterior, incluida la posibilidad de convertir la entidad en huérfana si se requiere la relación.
Relaciones uno a uno opcionales
Para las relaciones uno a uno, el cambio de una navegación de referencia provoca que cualquier relación previa se rompa. En el caso de las relaciones opcionales, esto significa que el valor de FK en el elemento dependiente o secundario relacionado anteriormente está establecido en NULL. Por ejemplo:
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 vista de depuración antes de llamar a SaveChanges muestra que los nuevos recursos han reemplazado a los recursos existentes, que ahora se marcan como Modified
con un valor FK nulo 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>
Esto da como resultado una actualización y una inserción cuando se llama a SaveChanges:
-- 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();
Relaciones de uno a uno necesarias
Al ejecutar el mismo código que en el ejemplo anterior, pero esta vez con una relación uno a uno necesaria, se muestra que el BlogAssets
previamente asociado ahora está marcado como Deleted
, ya que se convierte en un huérfano cuando el nuevo BlogAssets
toma su lugar.
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>
A continuación, se produce una eliminación y una inserción cuando se llama a SaveChanges:
-- 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();
El momento en el que se marcan los huérfanos como eliminados se puede modificar de manera similar a como se indica para las navegaciones en colecciones y tiene los mismos efectos.
Eliminación de una entidad
Relaciones opcionales
Cuando una entidad se marca como Deleted
, por ejemplo, llamando a DbContext.Remove, las referencias a la entidad eliminada se quitan de las navegaciones de otras entidades. Para las relaciones opcionales, los valores de FK de las entidades dependientes se establecen en NULL.
Por ejemplo, vamos a marcar el blog de Visual Studio como 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();
Al examinar la vista de depuración de seguimiento de cambios antes de llamar a SaveChanges se muestra:
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: []
Tenga en lo siguiente:
- El blog está marcado como
Deleted
. - Los recursos relacionados con el blog eliminado tienen un valor FK nulo (
BlogId: <null> FK Modified Originally 2
) y una navegación de referencia nula (Blog: <null>
) - Cada entrada relacionada con el blog eliminado tiene un valor FK nulo (
BlogId: <null> FK Modified Originally 2
) y una navegación de referencia nula (Blog: <null>
)
Relaciones necesarias
El comportamiento de corrección de las relaciones necesarias es el mismo que para las relaciones opcionales, excepto que las entidades dependientes/hijo se marcan como Deleted
ya que no pueden existir sin una entidad principal/madre y deben quitarse de la base de datos cuando se llame a SaveChanges para evitar una excepción de restricción referencial. Esto se conoce como "eliminación en cascada" y es el comportamiento predeterminado en EF Core para las relaciones necesarias. Por ejemplo, ejecutar el mismo código que en el ejemplo anterior, pero con una relación necesaria da como resultado la siguiente vista de depuración antes de llamar a SaveChanges:
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: []
Como se esperaba, las personas dependientes/niños ahora están marcadas como Deleted
. Sin embargo, tenga en cuenta que las navegaciones de las entidades eliminadas no han cambiado. Esto puede parecer extraño, pero evita fragmentar completamente un gráfico eliminado de entidades borrando todas las navegaciones. Es decir, el blog, el recurso y las entradas siguen formando un gráfico de entidades incluso después de haber sido eliminados. Esto facilita mucho la eliminación de un gráfico de entidades de la que era el caso de EF6 en el que el grafo se ha fragmentado.
Sincronización de eliminación en cascada y reasignación de padre
De forma predeterminada, la eliminación en cascada se produce en cuanto la entidad principal se marca como Deleted
. Esto es lo mismo que para eliminar huérfanos, como se ha descrito anteriormente. Al igual que con la eliminación de huérfanos, este proceso se puede retrasar hasta que se llame a SaveChanges, o incluso deshabilitarse completamente, estableciendo ChangeTracker.CascadeDeleteTiming correctamente. Esto resulta útil de la misma manera que para eliminar huérfanos, incluidos los hijos o dependientes al reasignar su relación parental tras la eliminación de un principal/padre.
Las eliminaciones en cascada, así como la eliminación de huérfanos, se pueden forzar en cualquier momento llamando a ChangeTracker.CascadeChanges(). Combinar esto con la configuración del momento de eliminación en cascada a Never
garantizará que las eliminaciones en cascada nunca se produzcan a menos que se le indique explícitamente a EF Core que lo haga.
Sugerencia
La eliminación en cascada y la eliminación de huérfanos están estrechamente relacionadas. Ambos dan lugar a la eliminación de entidades dependientes o secundarias cuando se rompe la relación con su entidad principal o primaria requerida. Para la eliminación en cascada, esta separación se produce porque la entidad principal o padre se elimina. En el caso de los huérfanos, la entidad principal o primaria sigue existiendo, pero ya no está relacionada con las entidades dependientes o secundarias.
Relaciones de varios a varios
Las relaciones de varios a varios en EF Core se implementan mediante una entidad de unión. Cada lado de la relación de varios a varios está relacionada con esta entidad de combinación con una relación uno a varios. Esta entidad de combinación se puede definir y asignar explícitamente, o se puede crear implícitamente y ocultamente. En ambos casos, el comportamiento subyacente es el mismo. Examinaremos primero este comportamiento subyacente para comprender cómo funciona el seguimiento de las relaciones de muchos a muchos.
¿Cómo funcionan las relaciones de muchos a muchos?
Considere este modelo de EF Core que crea una relación de muchos a muchos entre entradas y etiquetas mediante un tipo de entidad de interconexión definido explícitamente.
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
}
Observe que el PostTag
tipo de entidad de combinación contiene dos propiedades de clave externa. En este modelo, para que una publicación esté relacionada con una etiqueta, debe haber una entidad de combinación PostTag donde el PostTag.PostId
valor de clave externa coincida con el Post.Id
valor de clave principal y donde el PostTag.TagId
valor de clave externa coincida con el valor de Tag.Id
clave principal. Por ejemplo:
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);
Al examinar la vista de depuración de seguimiento de cambios después de ejecutar este código, se observa que la publicación y la etiqueta están relacionadas mediante la nueva PostTag
entidad de unión.
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}]
Observe que se han corregido las navegaciones de la colección en Post
y Tag
, así como las navegaciones de referencia en PostTag
. Estas relaciones se pueden manipular mediante navegaciones en lugar de valores de FK, igual que en todos los ejemplos anteriores. Por ejemplo, el código anterior se puede modificar para agregar la relación estableciendo las navegaciones de referencia en la entidad de combinación:
context.Add(new PostTag { Post = post, Tag = tag });
Esto da como resultado exactamente el mismo cambio en los FK y las navegaciones que en el ejemplo anterior.
Omitir las navegaciones
La manipulación manual de la tabla de combinación puede resultar complicada. Las relaciones de varios a varios se pueden manipular directamente mediante navegaciones de colección especiales que "omiten" la entidad de combinación. Por ejemplo, se pueden añadir dos enlaces para saltar al modelo anterior: uno de Publicación a Etiquetas, y el otro de Etiqueta a Publicaciones.
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
}
Esta relación de varios a varios requiere la siguiente configuración para asegurarse de que las navegaciones de omisión y las navegaciones normales se usan para la misma relación de varios a varios:
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));
}
Consulte Relaciones para obtener más información sobre la asignación de relaciones de muchos a muchos.
Las navegaciones de salto parecen y se comportan como las navegaciones de colección normales. Sin embargo, la forma en que funcionan con valores de clave externa es diferente. Vamos a asociar una publicación con una etiqueta, pero esta vez mediante una navegación de omisión:
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);
Tenga en cuenta que este código no usa la entidad join. En su lugar, simplemente agrega una entidad a una colección de navegación de la misma manera que se haría si se tratase de una relación uno a varios. La vista de depuración resultante es básicamente la misma que antes:
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}]
Observe que se creó automáticamente una instancia de la PostTag
entidad de combinación con los valores de FK asignados a los valores PK de la etiqueta y la publicación que ahora están vinculado. Todas las navegaciones de referencia y colección normales se han corregido para que coincidan con estos valores de FK. Además, dado que este modelo contiene enlaces de navegación omitidos, estos también han sido corregidos. En concreto, aunque agregamos la etiqueta a la Post.Tags
navegación de omisión, la Tag.Posts
navegación inversa de omisión en el otro lado de esta relación también ha sido corregida para que contenga la publicación asociada.
Vale la pena considerar que las relaciones subyacentes de varios a varios todavía se pueden manipular directamente incluso cuando se han superpuesto las navegaciones de elusión. Por ejemplo, la etiqueta y Post podrían asociarse como hicimos antes de introducir las navegaciones de omisión:
context.Add(new PostTag { Post = post, Tag = tag });
O bien, mediante valores de FK:
context.Add(new PostTag { PostId = post.Id, TagId = tag.Id });
Esto seguirá provocando que las navegaciones de salto se corrijan correctamente, y dará como resultado la misma salida de vista de depuración que en el ejemplo anterior.
Omitir solo las navegaciones
En la sección anterior, se añadieron las navegaciones de salto además de definir completamente las dos relaciones subyacentes de uno a varios. Esto resulta útil para ilustrar lo que sucede con los valores de FK, pero a menudo no es necesario. En su lugar, la relación de varios a varios se puede definir mediante solo omitir las navegaciones. Esta es la forma en que se define la relación de muchos a muchos en el modelo en la parte superior de este documento. Con este modelo, podemos asociar de nuevo un Post y una Tag agregando una publicación a la Tag.Posts
navegación de salto (o, alternativamente, agregando una Tag a la Post.Tags
navegación de salto):
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);
Al examinar la vista de depuración después de realizar este cambio se revela que EF Core ha creado una instancia de Dictionary<string, object>
para representar la entidad de combinación. Esta entidad de combinación contiene las propiedades de clave externa PostsId
y TagsId
que se han configurado para coincidir con los valores de clave primaria de la publicación y la etiqueta que están asociadas.
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
Consulte Relaciones para obtener más información sobre las entidades de combinación implícitas y el uso de tipos de entidad Dictionary<string, object>
.
Importante
El tipo CLR que se usa para combinar tipos de entidad por convención puede cambiar en versiones futuras para mejorar el rendimiento. No dependa de que el tipo de combinación sea Dictionary<string, object>
a menos que se haya configurado explícitamente.
Unión de entidades con cargas
Hasta ahora, todos los ejemplos han usado un tipo de entidad de combinación (ya sea explícito o implícito) que contiene solo las dos propiedades de clave externa necesarias para la relación de muchos a muchos. La aplicación no debe establecer explícitamente ninguno de estos valores de FK al manipular las relaciones porque sus valores proceden de las propiedades de clave principal de las entidades relacionadas. Esto permite a EF Core crear instancias de la entidad de combinación sin que falten datos.
Cargas con valores generados
EF Core admite la adición de propiedades adicionales al tipo de entidad de combinación. Esto se conoce como dar a la entidad de combinación una "carga útil". Por ejemplo, vamos a agregar la propiedad TaggedOn
a la entidad de unión PostTag
.
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
}
Esta propiedad de carga útil no se asignará cuando EF Core cree una instancia de entidad de unión. La manera más común de tratar con esto es usar propiedades de carga con valores generados automáticamente. Por ejemplo, la TaggedOn
propiedad se puede configurar para usar una marca de tiempo generada por el almacén cuando se inserta cada nueva entidad:
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"));
}
Ahora, una publicación se puede etiquetar de la misma manera que antes:
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);
Al examinar la vista de depuración del rastreador de cambios después de llamar a SaveChanges, se muestra que la propiedad de payload se ha establecido correctamente.
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}]
Establecer explícitamente valores de carga
A continuación del ejemplo anterior, vamos a agregar una propiedad de carga que no usa un valor generado automáticamente:
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
}
Ahora se puede etiquetar una publicación de la misma manera que antes y la entidad de unión se seguirá creando automáticamente. A continuación, se puede acceder a esta entidad mediante uno de los mecanismos descritos en Acceso a entidades con seguimiento. Por ejemplo, el código siguiente usa DbSet<TEntity>.Find para acceder a la instancia de entidad de combinación:
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);
Una vez que se ha localizado la entidad de combinación, se puede manipular de la manera normal: en este ejemplo, para establecer la TaggedBy
propiedad de carga antes de llamar a SaveChanges.
Nota:
Tenga en cuenta que aquí se requiere una llamada a ChangeTracker.DetectChanges() para dar a EF Core la oportunidad de detectar el cambio en la propiedad de navegación y crear la instancia de entidad de combinación antes de que se use Find
. Consulte Detección de cambios y notificaciones para obtener más información.
Como alternativa, la entidad join se puede crear explícitamente para asociar una publicación a una etiqueta. Por ejemplo:
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);
Por último, otra manera de establecer los datos de carga es sobrescribiendo SaveChanges o usando el evento DbContext.SavingChanges para procesar entidades antes de actualizar la base de datos. Por ejemplo:
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);
}