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.
Cada DbContext instância controla as alterações feitas nas entidades. Essas entidades controladas, por sua vez, direcionam as alterações no banco de dados quando SaveChanges são chamadas.
Este documento apresenta uma visão geral do controle de alterações do Entity Framework Core (EF Core) e como ele se relaciona com consultas e atualizações.
Tip
Você pode executar e depurar todo o código neste documento baixando o código de exemplo do GitHub.
Como rastrear entidades
As instâncias de entidade são rastreadas quando:
- Resultante de uma consulta executada na base de dados
- Explicitamente anexado ao DbContext por
Add,Attach,Update, ou métodos semelhantes - Detetado como novas entidades ligadas a entidades rastreadas já existentes
As instâncias de entidade não são mais rastreadas quando:
- O DbContext é descartado
- O rastreador de alterações foi apagado
- As entidades são explicitamente separadas
DbContext foi projetado para representar uma unidade de trabalho de curta duração, conforme descrito em DbContext Initialization and Configuration. Isso significa que descartar o DbContext é a maneira normal de parar de rastrear entidades. Em outras palavras, o tempo de vida de um DbContext deve ser:
- Criar a instância DbContext
- Rastrear algumas entidades
- Fazer algumas alterações nas entidades
- Chame SaveChanges para atualizar o banco de dados
- Descarte a instância DbContext
Tip
Não é necessário limpar o rastreador de alterações ou desanexar explicitamente instâncias de entidade ao adotar essa abordagem. No entanto, se você precisar desanexar entidades, chamar ChangeTracker.Clear é mais eficiente do que separar entidades uma a uma.
Estados da entidade
Cada entidade está associada a um dado EntityState:
-
Detachedentidades não estão sendo rastreadas pelo DbContext. -
AddedAs entidades são novas e ainda não foram inseridas na base de dados. Isso significa que eles serão inseridos quando SaveChanges for chamado. -
UnchangedAs entidades não têm sido alteradas desde que foram consultadas na base de dados. Todas as entidades retornadas de consultas estão inicialmente nesse estado. -
ModifiedAs entidades foram alteradas desde que foram consultadas a partir da base de dados. Isso significa que eles serão atualizados quando o SaveChanges for invocado. -
Deletedexistem na base de dados, mas são marcadas para serem eliminadas quando o método SaveChanges é chamado.
O EF Core controla as alterações no nível da propriedade. Por exemplo, se apenas um único valor de propriedade for modificado, uma atualização do banco de dados alterará apenas esse valor. No entanto, as propriedades só podem ser marcadas como modificadas quando a própria entidade estiver no estado Modificado. (Ou, de uma perspetiva alternativa, o estado Modificado significa que pelo menos um valor de propriedade foi marcado como modificado.)
A tabela a seguir resume os diferentes estados:
| Estado da entidade | Rastreado por DbContext | Existe na base de dados | Propriedades modificadas | Ação em SaveChanges |
|---|---|---|---|---|
Detached |
No | - | - | - |
Added |
Yes | No | - | Insert |
Unchanged |
Yes | Yes | No | - |
Modified |
Yes | Yes | Yes | Update |
Deleted |
Yes | Yes | - | Delete |
Note
Este texto usa termos de banco de dados relacional para maior clareza. Os bancos de dados NoSQL normalmente suportam operações semelhantes, mas possivelmente com nomes diferentes. Consulte a documentação do provedor de banco de dados para obter mais informações.
Rastreamento de consultas
O controle de alterações do EF Core funciona melhor quando a mesma DbContext instância é usada para consultar entidades e atualizá-las chamando SaveChanges. Isso ocorre porque o EF Core rastreia automaticamente o estado das entidades consultadas e, em seguida, deteta quaisquer alterações feitas nessas entidades quando SaveChanges é chamado.
Essa abordagem tem várias vantagens em relação ao rastreamento explícito de instâncias de entidade:
- É simples. Os estados de entidade raramente precisam ser manipulados explicitamente - o EF Core cuida das mudanças de estado.
- As atualizações são limitadas apenas aos valores que realmente foram alterados.
- Os valores das propriedades de sombra são preservados e usados conforme necessário. Isso é especialmente relevante quando chaves estrangeiras são armazenadas no estado sombra.
- Os valores originais das propriedades são preservados automaticamente e usados para atualizações eficientes.
Consulta e atualização simples
Por exemplo, considere um modelo simples de blog/posts:
public class Blog
{
public int Id { get; set; }
public string Name { get; set; }
public IList<Post> Posts { get; } = new List<Post>();
}
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; }
}
Podemos usar este modelo para consultar blogs e posts e, em seguida, fazer algumas atualizações no banco de dados:
using var context = new BlogsContext();
var blog = await context.Blogs.Include(e => e.Posts).FirstAsync(e => e.Name == ".NET Blog");
blog.Name = ".NET Blog (Updated!)";
await foreach (var post in blog.Posts.AsQueryable().Where(e => !e.Title.Contains("5.0")).AsAsyncEnumerable())
{
post.Title = post.Title.Replace("5", "5.0");
}
await context.SaveChangesAsync();
Chamar SaveChanges resulta nas seguintes atualizações de banco de dados, usando SQLite como um banco de dados de exemplo:
-- Executed DbCommand (0ms) [Parameters=[@p1='1' (DbType = String), @p0='.NET Blog (Updated!)' (Size = 20)], CommandType='Text', CommandTimeout='30']
UPDATE "Blogs" SET "Name" = @p0
WHERE "Id" = @p1;
SELECT changes();
-- Executed DbCommand (0ms) [Parameters=[@p1='2' (DbType = String), @p0='Announcing F# 5.0' (Size = 17)], CommandType='Text', CommandTimeout='30']
UPDATE "Posts" SET "Title" = @p0
WHERE "Id" = @p1;
SELECT changes();
A vista de depuração do rastreio de alterações é uma excelente forma de visualizar quais entidades estão a ser monitorizadas e quais são os seus estados. Por exemplo, insira o seguinte código no exemplo acima antes de chamar SaveChanges:
context.ChangeTracker.DetectChanges();
Console.WriteLine(context.ChangeTracker.DebugView.LongView);
Gera a seguinte saída:
Blog {Id: 1} Modified
Id: 1 PK
Name: '.NET Blog (Updated!)' Modified Originally '.NET Blog'
Posts: [{Id: 1}, {Id: 2}, {Id: 3}]
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}
Post {Id: 2} Modified
Id: 2 PK
BlogId: 1 FK
Content: 'F# 5 is the latest version of F#, the functional programming...'
Title: 'Announcing F# 5.0' Modified Originally 'Announcing F# 5'
Blog: {Id: 1}
Observe especificamente
- A
Blog.Namepropriedade está marcada como modificada (Name: '.NET Blog (Updated!)' Modified Originally '.NET Blog'), o que faz com que o blog esteja no estadoModified. - A
Post.Titlepropriedade do post 2 está marcada como modificado (Title: 'Announcing F# 5.0' Modified Originally 'Announcing F# 5'), e isso resulta que este post está no estadoModified. - Os outros valores de propriedade do post 2 não foram alterados e, portanto, não são marcados como modificados. É por isso que esses valores não são incluídos na atualização do banco de dados.
- O outro post não foi modificado de forma alguma. É por isso que ele ainda está no
Unchangedestado e não está incluído na atualização do banco de dados.
Consultar, em seguida, inserir, atualizar e excluir
Atualizações como as do exemplo anterior podem ser combinadas com inserções e exclusões na mesma unidade de trabalho. Por exemplo:
using var context = new BlogsContext();
var blog = await context.Blogs.Include(e => e.Posts).FirstAsync(e => e.Name == ".NET Blog");
// Modify property values
blog.Name = ".NET Blog (Updated!)";
// Insert a new Post
blog.Posts.Add(
new Post
{
Title = "What’s next for System.Text.Json?", Content = ".NET 5.0 was released recently and has come with many..."
});
// Mark an existing Post as Deleted
var postToDelete = blog.Posts.Single(e => e.Title == "Announcing F# 5");
context.Remove(postToDelete);
context.ChangeTracker.DetectChanges();
Console.WriteLine(context.ChangeTracker.DebugView.LongView);
await context.SaveChangesAsync();
Neste exemplo:
- Um blog e posts relacionados são consultados a partir do banco de dados e rastreados
- A
Blog.Namepropriedade é alterada - Uma nova postagem é adicionada à coleção de postagens existentes para o blog
- Uma publicação existente é marcada para eliminação chamando DbContext.Remove
Examinar novamente a visualização de depuração do rastreador de alterações antes de chamar o SaveChanges mostra como o EF Core está rastreando essas alterações.
Blog {Id: 1} Modified
Id: 1 PK
Name: '.NET Blog (Updated!)' Modified Originally '.NET Blog'
Posts: [{Id: 1}, {Id: 2}, {Id: 3}, {Id: -2147482638}]
Post {Id: -2147482638} Added
Id: -2147482638 PK Temporary
BlogId: 1 FK
Content: '.NET 5.0 was released recently and has come with many...'
Title: 'What's next for System.Text.Json?'
Blog: {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}
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: {Id: 1}
Observe que:
- O blog está marcado como
Modified. Isso gerará uma atualização do banco de dados. - O post 2 está marcado como
Deleted. Isso gerará uma exclusão do banco de dados. - Uma nova postagem com uma ID temporária está associada ao blog 1 e é marcada como
Added. Isso gerará uma inserção de banco de dados.
Isso resulta nos seguintes comandos de banco de dados (usando SQLite) quando SaveChanges é chamado:
-- Executed DbCommand (0ms) [Parameters=[@p1='1' (DbType = String), @p0='.NET Blog (Updated!)' (Size = 20)], CommandType='Text', CommandTimeout='30']
UPDATE "Blogs" SET "Name" = @p0
WHERE "Id" = @p1;
SELECT changes();
-- Executed DbCommand (0ms) [Parameters=[@p0='2' (DbType = String)], CommandType='Text', CommandTimeout='30']
DELETE FROM "Posts"
WHERE "Id" = @p0;
SELECT changes();
-- Executed DbCommand (0ms) [Parameters=[@p0='1' (DbType = String), @p1='.NET 5.0 was released recently and has come with many...' (Size = 56), @p2='What's next for System.Text.Json?' (Size = 33)], CommandType='Text', CommandTimeout='30']
INSERT INTO "Posts" ("BlogId", "Content", "Title")
VALUES (@p0, @p1, @p2);
SELECT "Id"
FROM "Posts"
WHERE changes() = 1 AND "rowid" = last_insert_rowid();
Consulte Rastreio explícito de entidades para mais informações sobre a inserção e eliminação de entidades. Consulte Deteção de alterações e notificações para obter mais informações sobre como o EF Core deteta automaticamente alterações como esta.
Tip
Ligue ChangeTracker.HasChanges() para determinar se foram feitas alterações que farão com que SaveChanges faça atualizações no banco de dados. Se HasChanges retornar falso, então SaveChanges será um no-op.