Nota
O acesso a esta página requer autorização. Podes tentar iniciar sessão ou mudar de diretório.
O acesso a esta página requer autorização. Podes tentar mudar de diretório.
Visão geral de teclas estrangeiras e navegações
As relações em um modelo Entity Framework Core (EF Core) são representadas usando chaves estrangeiras (FKs). Um FK consiste em uma ou mais propriedades da entidade dependente ou filha na relação. Essa entidade dependente/filho é associada a uma determinada entidade principal/pai quando os valores das propriedades de chave estrangeira no dependente/filho correspondem aos valores das propriedades de chave primária (PK) ou alternativa no principal/pai.
As chaves estrangeiras são uma boa maneira de armazenar e manipular relacionamentos no banco de dados, mas não são muito amigáveis ao trabalhar com várias entidades relacionadas no código do aplicativo. Portanto, a maioria dos modelos EF Core também sobrepõe "navegações" à representação FK. As navegações formam referências C#/.NET entre instâncias de entidade que refletem as associações encontradas pela correspondência de valores de chave estrangeira com valores de chave primária ou alternativa.
As navegações podem ser usadas em ambos os lados da relação, em um lado apenas, ou não de todo, deixando apenas a propriedade FK. A propriedade FK pode ser ocultada, tornando-se uma propriedade oculta. Consulte Relações para obter mais informações sobre como modelar relações.
Sugestão
Este documento pressupõe que os estados da entidade e os conceitos básicos do controle de alterações do EF Core sejam compreendidos. Consulte Controle de alterações no EF Core para obter mais informações sobre esses tópicos.
Sugestão
Você pode executar e depurar todo o código neste documento baixando o código de exemplo do GitHub.
Modelo de exemplo
O modelo a seguir contém quatro tipos de entidade com relações entre eles. Os comentários no código indicam quais propriedades são chaves estrangeiras, chaves primárias e propriedades de navegação.
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
}
As três relações neste modelo são:
- Cada blog pode ter muitos posts (um-para-muitos):
-
Blogé o principal/pai. -
Posté o dependente/filho. Ele contém a propriedade FKPost.BlogId, cujo valor deve corresponder ao valor PKBlog.Iddo blog relacionado. -
Post.Blogé uma navegação de referência de um post para o blog associado.Post.Blogé a navegação inversa paraBlog.Posts. -
Blog.Postsé uma função de navegação que leva de um blog a todos os posts associados.Blog.Postsé a navegação inversa paraPost.Blog.
-
- Cada blog pode ter uns ativos (um-para-um):
-
Blogé o principal/pai. -
BlogAssetsé o dependente/filho. Ele contém a propriedade FKBlogAssets.BlogId, cujo valor deve corresponder ao valor PKBlog.Iddo blog relacionado. -
BlogAssets.Blogé uma navegação de referência dos ativos para o blog associado.BlogAssets.Blogé a navegação inversa paraBlog.Assets. -
Blog.Assetsé uma navegação de referência do blog para os ativos associados.Blog.Assetsé a navegação inversa paraBlogAssets.Blog.
-
- Cada post pode ter muitas tags e cada tag pode ter muitos posts (muitos-para-muitos):
- As relações muitos-para-muitos são uma camada adicional sobre duas relações um-para-muitos. As relações muitos-para-muitos são abordadas mais adiante neste documento.
-
Post.Tagsé uma navegação de coleção de uma postagem para todas as tags associadas.Post.Tagsé a navegação inversa paraTag.Posts. -
Tag.Postsé uma navegação de coleção desde uma tag para todas as publicações associadas.Tag.Postsé a navegação inversa paraPost.Tags.
Consulte Relações para obter mais informações sobre como modelar e configurar relações.
Reparação de relacionamento
O sistema EF Core garante que as navegações estejam em conformidade com os valores de chave estrangeira e vice-versa. Ou seja, se um valor de chave estrangeira for alterado de tal forma que agora se refira a uma entidade principal/pai diferente, as navegações serão atualizadas para refletir essa alteração. Da mesma forma, se uma navegação for alterada, os valores de chave estrangeira das entidades envolvidas serão atualizados para refletir essa alteração. Isso é chamado de "ajuste de relacionamento".
Correção por consulta
A correção ou ajuste ocorre primeiro quando as entidades são consultadas no banco de dados. O banco de dados tem apenas valores de chave estrangeira, portanto, quando o EF Core cria uma instância de entidade a partir do banco de dados, ele usa os valores de chave estrangeira para definir navegações de referência e adicionar entidades às navegações de coleção, conforme apropriado. Por exemplo, considere uma consulta para blogs e suas postagens e ativos associados:
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, o EF Core primeiro criará uma Blog instância. Em seguida, à medida que cada postagem é carregada do banco de dados, sua Post.Blog navegação de referência é definida para apontar para o blog associado. Da mesma forma, o post é adicionado à navegação da coleção Blog.Posts. A mesma coisa acontece com BlogAssets, exceto neste caso ambas as navegações são referências. A Blog.Assets navegação é definida para apontar para a instância de ativos e a BlogAsserts.Blog navegação é definida para apontar para a instância de blog.
Observar a visualização de depuração do rastreador de alterações após esta consulta mostra dois blogs, cada um com um ativo e duas publicações a serem rastreados:
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: []
A janela de depuração mostra tanto os valores das teclas quanto as navegações. As navegações são mostradas usando os valores de chave primária das entidades relacionadas. Por exemplo, Posts: [{Id: 1}, {Id: 2}] na saída acima indica que a navegação na coleção Blog.Posts contém dois artigos relacionados com chaves primárias 1 e 2, respectivamente. Da mesma forma, para cada post associado ao primeiro blog, a Blog: {Id: 1} linha indica que a Post.Blog navegação faz referência ao Blog com a chave primária 1.
Modificação para entidades rastreadas localmente
A correção de relacionamento também acontece entre entidades retornadas de uma consulta de rastreamento e entidades já rastreadas pelo DbContext. Por exemplo, considere executar três consultas separadas para blogs, postagens e ativos:
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);
Olhando novamente para as visualizações de depuração, após a primeira consulta, apenas os dois blogs são rastreados:
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: []
As Blog.Assets navegações de referência são nulas e as Blog.Posts navegações de coleção estão vazias porque nenhuma entidade associada está sendo rastreada pelo contexto.
Após a segunda consulta, as Blogs.Assets navegações de referência foram ajustadas para apontarem para as instâncias recentemente rastreadas BlogAsset . Da mesma forma, as BlogAssets.Blog navegações de referência são definidas para apontar para a instância apropriada já rastreada Blog .
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}
Finalmente, após a terceira consulta, as navegações da coleção Blog.Posts agora contêm todas as publicações relacionadas, e as referências Post.Blog apontam para a instância Blog apropriada.
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 é o mesmo estado final que foi alcançado com a consulta única original, uma vez que o EF Core fixou as navegações à medida que as entidades eram rastreadas, mesmo quando provenientes de várias consultas diferentes.
Observação
A correção nunca faz com que sejam retornados mais dados do banco de dados. Ele só conecta entidades que já são retornadas pela consulta ou já rastreadas pelo DbContext. Consulte Resolução de identidade no EF Core para obter informações sobre como lidar com duplicatas ao serializar entidades.
Alterando relacionamentos usando navegações
A maneira mais fácil de alterar a relação entre duas entidades é manipulando uma navegação, deixando o EF Core para corrigir a navegação inversa e os valores FK adequadamente. Isto pode ser feito através de:
- Adicionar ou remover uma entidade de uma navegação de coleção.
- Alterar uma navegação de referência para apontar para uma entidade diferente ou defini-la como nula.
Adicionar ou remover nas navegações de coleção
Por exemplo, vamos mover uma das postagens do blog do Visual Studio para o blog do .NET. Isso requer primeiro carregar os blogs e postagens e, em seguida, mover a postagem da coleção de navegação em um blog para a coleção de navegação no outro 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();
Sugestão
Uma chamada para ChangeTracker.DetectChanges() é necessária aqui porque acessar a visualização de depuração não causa deteção automática de alterações.
Esta é a visualização de depuração impressa após a execução do código acima.
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: []
A Blog.Posts navegação no .NET Blog agora tem três posts (Posts: [{Id: 1}, {Id: 2}, {Id: 3}]). Da mesma forma, a Blog.Posts navegação no blog do Visual Studio tem apenas uma postagem (Posts: [{Id: 4}]). Isso é de se esperar, já que o código alterou explicitamente essas coleções.
Mais interessante, embora o código não tenha alterado explicitamente a Post.Blog navegação, ele foi corrigido para apontar para o blog do Visual Studio (Blog: {Id: 1}). Além disso, o valor da Post.BlogId chave estrangeira foi atualizado para corresponder ao valor da chave primária do blog .NET. Essa alteração no valor FK é então mantida no banco de dados quando SaveChanges é chamado:
-- 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();
Alterar navegações de referência
No exemplo anterior, uma postagem era movida de um blog para outro manipulando a navegação da coleção de postagens em cada blog. Pode-se alcançar o mesmo objetivo alterando a navegação de referência do Post.Blog apontando para o novo blog. Por exemplo:
var post = vsBlog.Posts.Single(e => e.Title.StartsWith("Disassembly improvements"));
post.Blog = dotNetBlog;
A visualização de depuração após essa alteração é exatamente a mesma do exemplo anterior. Isso ocorre porque o EF Core detetou a alteração de navegação de referência e, em seguida, corrigiu as navegações de coleção e o valor FK para corresponder.
Alterando relacionamentos usando valores-chave estrangeiros
Na secção anterior, as relações eram manipuladas por navegações, deixando os valores de chave estrangeira serem atualizados automaticamente. Esta é a maneira recomendada de manipular relacionamentos no EF Core. No entanto, também é possível manipular valores FK diretamente. Por exemplo, podemos mover uma postagem de um blog para outro alterando o valor da Post.BlogId chave estrangeira:
var post = vsBlog.Posts.Single(e => e.Title.StartsWith("Disassembly improvements"));
post.BlogId = dotNetBlog.Id;
Observe como isso é muito semelhante a alterar a navegação de referência, como mostrado no exemplo anterior.
A visualização de depuração após essa alteração é novamente exatamente a mesma que foi o caso para os dois exemplos anteriores. Isso ocorre porque o EF Core detetou a alteração do valor FK e, em seguida, corrigiu as navegações de referência e de coleção para corresponder.
Sugestão
Não escreva código para manipular todas as navegações e valores FK cada vez que uma relação for alterada. Esse código é mais complicado e deve garantir alterações consistentes em chaves e navegações estrangeiras em todos os casos. Se possível, basta manipular uma única navegação, ou talvez ambas as navegações. Se necessário, basta manipular os valores FK. Evite manipular as navegações e os valores FK.
Ajuste para entidades adicionadas ou removidas.
Adicionar a uma navegação de coleção
O EF Core executa as seguintes ações quando deteta que uma nova entidade dependente/filho foi adicionada a uma navegação de coleção:
- Se a entidade não for rastreada, ela será rastreada. (A entidade normalmente estará no estado
Added. No entanto, se o tipo de entidade estiver configurado para usar chaves geradas e o valor da chave primária estiver definido, então a entidade será rastreada no estadoUnchanged.) - Se a entidade estiver associada a um principal/pai diferente, essa relação será cortada.
- A entidade torna-se associada à entidade principal/matriz que controla a navegação da coleção.
- As navegações e os valores das chaves estrangeiras são corrigidos para todas as entidades envolvidas.
Com base nisso, podemos ver que, para mover uma postagem de um blog para outro, não precisamos removê-la da navegação da coleção antiga antes de adicioná-la à nova. Assim, o código do exemplo acima pode ser alterado de:
var post = vsBlog.Posts.Single(e => e.Title.StartsWith("Disassembly improvements"));
vsBlog.Posts.Remove(post);
dotNetBlog.Posts.Add(post);
Para:
var post = vsBlog.Posts.Single(e => e.Title.StartsWith("Disassembly improvements"));
dotNetBlog.Posts.Add(post);
O EF Core vê que a postagem foi adicionada a um novo blog e a remove automaticamente da coleção no primeiro blog.
Remover de um navegador de coleções
A remoção de uma entidade dependente/filho da navegação de coleta do principal/pai causa o rompimento da relação com esse principal/pai. O que acontece a seguir depende se a relação é opcional ou obrigatória.
Relações opcionais
Por padrão, para relações opcionais, o valor da chave estrangeira é definido como nulo. Isto significa que o dependente/filho já não está associado a nenhum responsável/progenitor. Por exemplo, vamos carregar um blog e as suas postagens e depois remover uma das postagens da navegação da coleção Blog.Posts:
var post = dotNetBlog.Posts.Single(e => e.Title == "Announcing F# 5");
dotNetBlog.Posts.Remove(post);
Ao observar a visualização de depuração do controle de alterações após esta alteração, mostra que:
- O
Post.BlogIdFK foi definido como null (BlogId: <null> FK Modified Originally 1) - A
Post.Blognavegação de referência foi definida como null (Blog: <null>) - O post foi removido da navegação da
Blog.Postscoleção (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 a postagem não está marcada como Deleted. É marcado como Modified para que o valor de FK na base de dados seja definido como nulo quando SaveChanges for chamado.
Relações necessárias
Definir o valor FK como nulo não é permitido (e geralmente não é possível) para relações necessárias. Portanto, quebrar um relacionamento necessário significa que a entidade dependente/filho deve ser atribuída a uma nova entidade principal/pai ou removida do banco de dados durante a chamada de SaveChanges para evitar uma violação de restrição referencial. Isso é conhecido como "exclusão de órfãos" e é o comportamento padrão no EF Core para relacionamentos necessários.
Por exemplo, vamos alterar a relação entre blog e postagens para que seja obrigatória e, em seguida, executar o mesmo código como fizemos no exemplo anterior.
var post = dotNetBlog.Posts.Single(e => e.Title == "Announcing F# 5");
dotNetBlog.Posts.Remove(post);
Observar a vista de depuração após esta alteração mostra que:
- A publicação foi marcada como
Deletedde forma que será excluída da base de dados quando SaveChanges for executado. - A
Post.Blognavegação de referência foi definida como null (Blog: <null>). - A postagem foi removida da navegação da
Blog.Postscoleção (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 o Post.BlogId permanece inalterado, pois para uma relação necessária ele não pode ser definido como nulo.
Chamar SaveChanges resulta na exclusão da postagem órfã:
-- Executed DbCommand (0ms) [Parameters=[@p0='2' (DbType = String)], CommandType='Text', CommandTimeout='30']
DELETE FROM "Posts"
WHERE "Id" = @p0;
SELECT changes();
Excluir a temporização de órfãos e redefinição de parentalidade
Por padrão, a marcação dos órfãos como Deleted ocorre assim que a mudança de relacionamento é detetada. No entanto, esse processo pode ser adiado até que SaveChanges seja realmente chamado. Isso pode ser útil para evitar tornar órfãos de entidades que foram removidas de um principal/pai, mas serão reparentadas com um novo principal/pai antes de SaveChanges ser chamado.
ChangeTracker.DeleteOrphansTiming é utilizado para definir este tempo. Por exemplo:
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();
Depois de remover o post da primeira coleção, o objeto não é marcado como Deleted estava no exemplo anterior. Em vez disso, o EF Core está observando que a ligação foi rompida, mesmo que esta seja uma ligação obrigatória. (O valor FK é considerado nulo pelo EF Core, embora não possa ser realmente nulo porque o tipo não é anulável. Isso é conhecido como "nulo conceitual".)
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: []
Chamar SaveChanges neste momento resultaria na exclusão da postagem órfã. No entanto, se, como no exemplo acima, o post estiver associado a um novo blog antes de SaveChanges ser chamado, ele será corrigido adequadamente para esse novo blog e não será mais considerado órfão:
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 chamado neste momento atualizará a postagem no banco de dados em vez de excluí-la.
Também é possível desativar a exclusão automática de órfãos. Isso resultará em uma exceção se SaveChanges for chamado enquanto um órfão estiver sendo rastreado. Por exemplo, 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
Lançará esta exceção:
System.InvalidOperationException: A associação entre as entidades 'Blog' e 'Post' com o valor de chave '{BlogId: 1}' foi cortada, mas a relação é marcada como necessária ou é implicitamente necessária porque a chave estrangeira não é anulável. Se a entidade dependente/filho deve ser excluída quando um relacionamento necessário é cortado, configure o relacionamento para usar exclusões em cascata.
A exclusão de órfãos, bem como exclusões em cascata, podem ser forçadas a qualquer momento ligando para ChangeTracker.CascadeChanges(). Combinar isso com a configuração do momento de exclusão de órfãos para Never irá garantir que os órfãos nunca serão excluídos, a menos que o EF Core seja explicitamente instruído a fazê-lo.
Alterar uma navegação de referência
Alterar a navegação de referência de uma relação um-para-muitos tem o mesmo efeito que alterar a navegação de coleção na outra extremidade da relação. Definir a navegação de referência de dependente/filho como nula equivale a remover a entidade da navegação de coleção do principal/pai. Todas as correções e alterações no banco de dados acontecem conforme descrito na seção anterior, incluindo tornar a entidade órfã se o relacionamento for necessário.
Relações um-para-um opcionais
Para relações um-para-um, alterar uma navegação de referência faz com que qualquer relação anterior seja cortada. Para relações opcionais, isso significa que o valor FK do dependente/filho que estava anteriormente relacionado é definido como nulo. Por exemplo:
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();
A exibição de depuração antes de chamar SaveChanges mostra que os novos ativos substituíram os ativos existentes, que agora estão marcados como Modified com um 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>
Isso resulta em uma atualização e uma inserção quando SaveChanges é chamado:
-- 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();
Relações um-para-um necessárias
Executando o mesmo código do exemplo anterior, mas desta vez com uma relação necessária de um-para-um, mostra que o BlogAssets anteriormente associado agora está marcado como Deleted, porque fica órfão quando o novo BlogAssets ocupa o seu 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>
Isso resulta em uma exclusão e uma inserção quando SaveChanges é chamado:
-- 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();
O tempo de marcação de órfãos como excluídos pode ser alterado da mesma forma que mostrado para navegações de coleção e tem os mesmos efeitos.
Eliminar uma entidade
Relações opcionais
Quando uma entidade é marcada como Deleted, por exemplo, chamando DbContext.Remove, as referências à entidade excluída são removidas das navegações de outras entidades. Para relações opcionais, os valores FK em entidades dependentes são definidos como null.
Por exemplo, vamos marcar o blog do 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();
Observar a visualização de depuração do rastreador de alterações antes de chamar SaveChanges mostra:
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: []
Repare que:
- O blog está marcado como
Deleted. - Os ativos relacionados ao blog excluído tem um valor FK nulo (
BlogId: <null> FK Modified Originally 2) e uma navegação de referência nula (Blog: <null>) - Cada postagem relacionada ao blog excluído tem um valor FK nulo (
BlogId: <null> FK Modified Originally 2) e uma navegação de referência nula (Blog: <null>)
Relações necessárias
O comportamento de correção para relacionamentos necessários é o mesmo que para relacionamentos opcionais, exceto que as entidades dependentes/filhas são marcadas como Deleted, uma vez que não podem existir sem uma entidade principal/mãe e, portanto, devem ser removidas do banco de dados quando o SaveChanges for chamado para evitar uma exceção de restrição referencial. Isso é conhecido como "exclusão em cascata" e é o comportamento padrão no EF Core para relacionamentos necessários. Por exemplo, executar o mesmo código do exemplo anterior, mas com uma relação obrigatória, resulta na seguinte exibição de depuração antes de SaveChanges ser chamado:
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 esperado, os dependentes/filhos estão agora marcados como Deleted. No entanto, observe que as navegações nas entidades excluídas não foram alteradas. Isso pode parecer estranho, mas evita destruir completamente um gráfico excluído de entidades, limpando todas as navegações. Ou seja, o blog, o ativo e os posts ainda formam um gráfico de entidades mesmo depois de terem sido excluídos. Isso torna muito mais fácil cancelar a exclusão de um gráfico de entidades do que era o caso no EF6, onde o gráfico foi destruído.
Temporização de exclusão em cascata e reparentalidade
Por padrão, a exclusão em cascata acontece assim que o pai/principal é marcado como Deleted. Isso é o mesmo que para excluir órfãos, como descrito anteriormente. Tal como acontece com a exclusão de órfãos, esse processo pode ser adiado até que SaveChanges seja chamado, ou até mesmo desativado completamente, definindo ChangeTracker.CascadeDeleteTiming adequadamente. Isto é útil da mesma forma que para eliminar órfãos, incluindo para a reatribuição de paternidade/responsabilidade de filhos/dependentes após a eliminação de um responsável/progenitor.
As exclusões em cascata, bem como a exclusão de órfãos, podem ser forçadas a qualquer momento chamando ChangeTracker.CascadeChanges(). Combinar isto com a configuração do tempo de exclusão em cascata para Never irá garantir que as exclusões em cascata nunca ocorram, a menos que o EF Core seja instruído explicitamente a fazê-lo.
Sugestão
A exclusão em cascata e a exclusão de órfãos estão intimamente relacionadas. Ambos resultam na exclusão de entidades dependentes/filhas quando a relação com a sua entidade principal/pai necessária é cortada. Para a exclusão em cascata, esse corte acontece porque o principal/pai é excluído. Para órfãos, a entidade principal/mãe ainda existe, mas não está mais relacionada às entidades dependentes/filhos.
Relações de muitos-para-muitos
As relações muitos-para-muitos no EF Core são implementadas usando uma entidade de junção. Cada lado da relação de muitos-para-muitos está associado a esta entidade de junção por meio de uma relação de um-para-muitos. Essa entidade de junção pode ser explicitamente definida e mapeada, ou pode ser criada implicitamente e oculta. Em ambos os casos, o comportamento subjacente é o mesmo. Analisaremos esse comportamento subjacente primeiro para entender como funciona o rastreamento de relacionamentos muitos-para-muitos.
Como funcionam as relações de muitos para muitos
Considere este modelo EF Core que cria uma relação muitos-para-muitos entre postagens e tags usando um tipo de entidade de associação explicitamente definido:
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 o tipo de PostTag entidade de associação contém duas propriedades de chave estrangeira. Neste modelo, para que uma postagem seja relacionada a uma tag, deve haver uma entidade de junção PostTag onde o valor da PostTag.PostId chave estrangeira corresponde ao valor da Post.Id chave primária e onde o valor da PostTag.TagId chave estrangeira corresponde ao valor da Tag.Id chave primária. Por exemplo:
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);
Observar a visualização de depuração do controlador de alterações depois de executar esse código mostra que a postagem e a tag estão relacionadas pela nova PostTag entidade de associação:
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 as navegações da coleção em Post e Tag foram corrigidas, assim como as navegações de referência em PostTag. Essas relações podem ser manipuladas por navegações em vez de valores FK, assim como em todos os exemplos anteriores. Por exemplo, o código acima pode ser modificado para adicionar a relação definindo as navegações de referência na entidade de junção:
context.Add(new PostTag { Post = post, Tag = tag });
Isso resulta exatamente na mesma alteração para FKs e navegações como no exemplo anterior.
Saltar navegações
Manipular a tabela de junção manualmente pode ser complicado. As relações muitos-para-muitos podem ser manipuladas diretamente usando navegações de coleção especiais que "ignoram" a entidade de junção. Por exemplo, duas navegações de salto podem ser adicionadas ao modelo acima; uma de Posts para Tags, e a outra de Tags para 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
}
Esta relação muitos-para-muitos requer a seguinte configuração para garantir que as navegações de omissão e as navegações normais sejam todas usadas para a mesma relação muitos-para-muitos.
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 Relações para obter mais informações sobre como mapear relações muitos-para-muitos.
As navegações de salto parecem e se comportam como navegações normais de coleção. No entanto, a forma como trabalham com valores-chave estrangeiros é diferente. Vamos associar uma postagem a uma tag, mas desta vez usando uma navegação 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);
Observe que este código não utiliza a entidade de associação. Em vez disso, ele apenas adiciona uma entidade a uma coleção de navegação da mesma maneira que seria feito se essa fosse uma relação um-para-muitos. A visualização de depuração resultante é essencialmente a mesma de 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 uma instância da PostTag entidade de associação foi criada automaticamente com valores FK definidos para os valores PK da tag e post que agora estão associados. Todas as navegações normais de referência e coleção foram corrigidas para corresponder a esses valores FK. Além disso, como este modelo contém navegações de salto, estas também foram corrigidas. Especificamente, embora tenhamos adicionado a tag à Post.Tags navegação de salto, a Tag.Posts navegação de pulo inverso do outro lado dessa relação também foi corrigida para conter a postagem associada.
Vale a pena notar que as relações subjacentes muitos-para-muitos ainda podem ser manipuladas diretamente, mesmo quando as navegações de salto foram acrescidas. Por exemplo, a tag e a Post podem ser associadas como fazíamos antes de introduzir as navegações de salto:
context.Add(new PostTag { Post = post, Tag = tag });
Ou usando valores FK:
context.Add(new PostTag { PostId = post.Id, TagId = tag.Id });
Isto ainda resultará na correção adequada das navegações omitidas, resultando no mesmo resultado de visualização de depuração do exemplo anterior.
Apenas ignorar navegações
Na seção anterior, adicionamos navegações de salto, além de definir completamente as duas relações um-para-muitos subjacentes. Isso é útil para ilustrar o que acontece com os valores FK, mas muitas vezes é desnecessário. Em vez disso, a relação muitos-para-muitos pode ser definida usando apenas navegações ignoradas. É assim que a relação muitos-para-muitos é definida no modelo no topo deste documento. Usando este modelo, podemos associar novamente um Post e uma Tag adicionando um post à navegação de salto Tag.Posts (ou, alternativamente, adicionando uma tag à navegação de salto Post.Tags).
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);
Observar a visualização de depuração depois de fazer essa alteração revela que o EF Core criou uma instância de Dictionary<string, object> para representar a entidade de junção. Esta entidade de associação contém as propriedades de chave estrangeira PostsId e TagsId, que foram definidas para corresponder aos valores PK da publicação e da etiqueta associadas.
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 Relações para obter mais informações sobre entidades implícitas de união e o uso de tipos de entidade Dictionary<string, object>.
Importante
O tipo CLR utilizado por convenção para tipos de entidades associados pode mudar em versões futuras para melhorar o desempenho. Não dependa do tipo de associação que seja Dictionary<string, object>, a menos que isso tenha sido configurado explicitamente.
Junte-se a entidades com cargas úteis
Até agora, todos os exemplos usaram um tipo de entidade de junção (explícita ou implícita) que contém apenas as duas propriedades de chave estrangeira necessárias para a relação muitos-para-muitos. Nenhum desses valores FK precisa ser explicitamente definido pelo aplicativo ao manipular relacionamentos porque seus valores vêm das propriedades de chave primária das entidades relacionadas. Isso permite que o EF Core crie instâncias da entidade de associação sem dados ausentes.
Cargas úteis com valores gerados
O EF Core suporta a adição de propriedades adicionais ao tipo de entidade de associação. Isto é conhecido como dar à entidade de junção uma "carga útil". Por exemplo, vamos adicionar a TaggedOn propriedade à entidade de junção 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 propriedade do atributo da carga útil não será definida quando o EF Core cria uma instância de entidade de associação. A maneira mais comum de lidar com isso é usar propriedades de carga útil com valores gerados automaticamente. Por exemplo, a propriedade TaggedOn pode ser configurada para usar um carimbo de data/hora gerado pelo sistema quando cada nova entidade é inserida.
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"));
}
Uma publicação pode agora ser etiquetada da mesma forma 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);
Olhar para a vista de depuração do rastreador de alterações depois de chamar SaveChanges mostra que a propriedade de carga útil foi definida adequadamente:
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}]
Definição explícita de valores de carga útil
Seguindo o exemplo anterior, vamos adicionar uma propriedade payload que não usa um valor gerado automaticamente:
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
}
Uma publicação agora pode ser marcada da mesma forma que antes, e a entidade de associação ainda será criada automaticamente. Essa entidade pode ser acessada usando um dos mecanismos descritos em Acessando entidades rastreadas. Por exemplo, o código abaixo usa DbSet<TEntity>.Find para acessar a instância da entidade de associação:
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);
Uma vez que a entidade de junção tenha sido localizada, ela pode ser manipulada da maneira normal - neste exemplo, para definir a TaggedBy propriedade payload antes de chamar SaveChanges.
Observação
Observe que uma chamada para ChangeTracker.DetectChanges() é necessária aqui para dar ao EF Core a chance de detetar a alteração da propriedade de navegação e criar a instância da entidade de junção antes Find de ser usada. Consulte Deteção de alterações e notificações para obter mais informações.
Como alternativa, a entidade de associação pode ser criada explicitamente para associar uma postagem a uma tag. Por exemplo:
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);
Finalmente, outra maneira de definir dados de carga útil é substituindo SaveChanges ou usando o DbContext.SavingChanges evento para processar entidades antes de atualizar o banco de dados. Por exemplo:
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);
}