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. Isso é abordado em Controle de alterações no EF Core, e este documento pressupõe que os estados da entidade e os conceitos básicos do controle de alterações do Entity Framework Core (EF Core) sejam compreendidos.
O controle de alterações de propriedade e relacionamento requer que o DbContext seja capaz de detetar essas alterações. Este documento aborda como essa deteção acontece, bem como como usar notificações de propriedade ou proxies de controle de alterações para forçar a deteção imediata de alterações.
Sugestão
Você pode executar e depurar todo o código neste documento baixando o código de exemplo do GitHub.
Rastreio de alterações de snapshot
Por padrão, o EF Core cria um instantâneo dos valores de propriedade de cada entidade quando ela é monitorada pela primeira vez por uma instância de DbContext. Os valores armazenados neste instantâneo são então comparados com os valores atuais da entidade para determinar quais valores de propriedade foram alterados.
Essa deteção de alterações acontece quando SaveChanges é chamado para garantir que todos os valores alterados sejam detetados antes de enviar atualizações para o banco de dados. No entanto, a deteção de alterações também acontece em outros momentos para garantir que o aplicativo esteja trabalhando com informações de rastreamento de data up-to. A deteção de alterações pode ser forçada a qualquer momento chamando ChangeTracker.DetectChanges().
Quando a deteção de alterações é necessária
A deteção de alterações é necessária quando uma propriedade ou navegação é alterada sem usar o EF Core para fazer essa alteração. Por exemplo, considere carregar blogs e postagens e, em seguida, fazer alterações nessas entidades:
using var context = new BlogsContext();
var blog = await context.Blogs.Include(e => e.Posts).FirstAsync(e => e.Name == ".NET Blog");
// Change a property value
blog.Name = ".NET Blog (Updated!)";
// Add a new entity to a navigation
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..."
});
Console.WriteLine(context.ChangeTracker.DebugView.LongView);
context.ChangeTracker.DetectChanges();
Console.WriteLine(context.ChangeTracker.DebugView.LongView);
Observar a visualização de depuração do controlador de alterações antes de chamar ChangeTracker.DetectChanges() mostra que as alterações feitas não foram detetadas e, portanto, não são refletidas nos estados da entidade e nos dados de propriedade modificados:
Blog {Id: 1} Unchanged
Id: 1 PK
Name: '.NET Blog (Updated!)' Originally '.NET Blog'
Posts: [{Id: 1}, {Id: 2}, <not found>]
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} 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}
Especificamente, o estado da entrada de blog ainda Unchangedé , e a nova postagem não aparece como uma entidade rastreada. (Os mais atentos notarão que as propriedades relatam os seus novos valores, mesmo que essas alterações ainda não tenham sido detectadas pelo EF Core. Isso ocorre porque o modo de depuração está lendo os valores atuais diretamente da instância da entidade.)
Compare isso com a vista de depuração depois de chamar DetectChanges.
Blog {Id: 1} Modified
Id: 1 PK
Name: '.NET Blog (Updated!)' Modified Originally '.NET Blog'
Posts: [{Id: 1}, {Id: 2}, {Id: -2147482643}]
Post {Id: -2147482643} Added
Id: -2147482643 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} 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}
Agora o blog está marcado corretamente como Modified e o novo post foi detetado e é rastreado como Added.
No início desta seção, afirmamos que a deteção de alterações é necessária quando não se está usando o EF Core para fazer a alteração. É o que está acontecendo no código acima. Ou seja, as alterações na propriedade e na navegação são feitas diretamente nas instâncias da entidade, e não usando nenhum método EF Core.
Compare isso com o código a seguir que modifica as entidades da mesma maneira, mas desta vez usando métodos EF Core:
using var context = new BlogsContext();
var blog = await context.Blogs.Include(e => e.Posts).FirstAsync(e => e.Name == ".NET Blog");
// Change a property value
context.Entry(blog).Property(e => e.Name).CurrentValue = ".NET Blog (Updated!)";
// Add a new entity to the DbContext
context.Add(
new Post
{
Blog = blog,
Title = "What’s next for System.Text.Json?",
Content = ".NET 5.0 was released recently and has come with many..."
});
Console.WriteLine(context.ChangeTracker.DebugView.LongView);
Nesse caso, a vista de depuração do rastreador de alterações mostra que todos os estados da entidade e modificações de propriedade são conhecidos, mesmo sem que a deteção de alterações tenha ocorrido. Isso ocorre porque PropertyEntry.CurrentValue é um método EF Core, o que significa que o EF Core sabe imediatamente sobre a alteração feita por esse método. Da mesma forma, a chamada DbContext.Add permite que o EF Core saiba imediatamente sobre a nova entidade e a rastreie adequadamente.
Sugestão
Não tente evitar a deteção de alterações usando exclusivamente métodos EF Core para modificar entidades. Fazê-lo é muitas vezes mais complicado e tem um desempenho menos bom do que fazer alterações nas entidades da forma normal. A intenção deste documento é informar quando a deteção de alterações é necessária e quando não é. A intenção não é incentivar a prevenção da deteção de alterações.
Métodos que detetam alterações automaticamente
DetectChanges() é chamado automaticamente por métodos em que isso é suscetível de afetar os resultados. Esses métodos são:
- DbContext.SaveChanges e DbContext.SaveChangesAsync, para garantir que todas as alterações sejam detetadas antes de atualizar o banco de dados.
- ChangeTracker.Entries() e ChangeTracker.Entries<TEntity>(), para garantir que os estados da entidade e as propriedades modificadas estejam up-todata.
- ChangeTracker.HasChanges(), para garantir que o resultado é exato.
- ChangeTracker.CascadeChanges(), para garantir estados de entidade corretos para entidades principais/mãe antes da ação em cascata.
- DbSet<TEntity>.Local, para garantir que o gráfico monitorizado está atualizado para up-to-data.
Há também alguns lugares onde a deteção de alterações acontece em apenas uma única instância de entidade, em vez de em todo o gráfico de entidades controladas. Estes locais são:
- Ao usar DbContext.Entryo , para garantir que o estado da entidade e as propriedades modificadas estejam up-to-date.
- Ao utilizar os métodos EntityEntry como
Property,Collection,ReferenceouMemberpara garantir que as modificações de propriedade, os valores atuais, etc., estejam atualizados até à data up-to. - Quando uma entidade dependente/filho vai ser apagada porque um relacionamento necessário foi rompido. Isso deteta quando uma entidade não deve ser excluída porque foi reparentada.
A deteção local de alterações para uma única entidade pode ser acionada explicitamente chamando EntityEntry.DetectChanges().
Observação
As alterações de deteção local podem perder algumas alterações que uma deteção completa encontraria. Isto acontece quando ações em cascata resultantes de alterações não detetadas noutras entidades têm impacto na entidade em questão. Em tais situações, a aplicação pode precisar forçar uma verificação completa de todas as entidades, chamando ChangeTracker.DetectChanges() explicitamente.
Desativando a deteção automática de alterações
O desempenho da deteção de alterações não é um gargalo para a maioria dos aplicativos. No entanto, a deteção de alterações pode se tornar um problema de desempenho para alguns aplicativos que rastreiam milhares de entidades. (O número exato dependerá de muitas coisas, como o número de propriedades na entidade.) Por esta razão, a deteção automática de alterações pode ser desativada usando ChangeTracker.AutoDetectChangesEnabledo . Por exemplo, considere o processamento de entidades de junção em um relacionamento muitos-para-muitos com cargas úteis:
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
foreach (var entityEntry in ChangeTracker.Entries<PostTag>()) // Detects changes automatically
{
if (entityEntry.State == EntityState.Added)
{
entityEntry.Entity.TaggedBy = "ajcvickers";
entityEntry.Entity.TaggedOn = DateTime.Now;
}
}
try
{
ChangeTracker.AutoDetectChangesEnabled = false;
return await base.SaveChangesAsync(cancellationToken); // Avoid automatically detecting changes again here
}
finally
{
ChangeTracker.AutoDetectChangesEnabled = true;
}
}
Como sabemos pela seção anterior, tanto ChangeTracker.Entries<TEntity>() quanto DbContext.SaveChanges automaticamente detetam alterações. No entanto, depois de chamar Entradas, o código não faz nenhuma alteração de entidade ou estado de propriedade. (A definição de valores de propriedade normais em entidades adicionadas não causa alterações de estado.) O código, portanto, desativa a deteção automática de alterações desnecessária ao chamar para o método SaveChanges base. O código também faz uso de um bloco try/finally para garantir que a configuração padrão seja restaurada mesmo se SaveChanges falhar.
Sugestão
Não assuma que seu código deve desativar a deteção automática de alterações para ter um bom desempenho. Isso só é necessário quando o perfil de um aplicativo que rastreia muitas entidades indica que o desempenho da deteção de alterações é um problema.
Deteção de alterações e conversões de valor
Para usar o rastreamento de alterações por instantâneo com um tipo de entidade, o EF Core deve ser capaz de:
- Faça um instantâneo de cada valor de propriedade quando a entidade for rastreada
- Compare esse valor com o valor atual da propriedade
- Gerar um código hash para o valor
Isso é tratado automaticamente pelo EF Core para tipos que podem ser mapeados diretamente para o banco de dados. No entanto, quando um conversor de valor é usado para mapear uma propriedade, esse conversor deve especificar como executar essas ações. Isso é conseguido com um comparador de valores e é descrito em detalhes na documentação de Comparadores de Valor .
Entidades notificadoras
O controle de alterações de instantâneo é recomendado para a maioria dos aplicativos. No entanto, os aplicativos que rastreiam muitas entidades e/ou fazem muitas alterações nessas entidades podem se beneficiar das entidades de implementação que notificam automaticamente o EF Core quando seus valores de propriedade e navegação são alterados. Estas são conhecidas como "entidades de notificação".
Implementação de entidades de notificação
As entidades de notificação usam as interfaces INotifyPropertyChanging e INotifyPropertyChanged, que fazem parte da biblioteca de classes base (.NET BCL). Essas interfaces definem eventos que devem ser acionados antes e depois de alterar um valor de propriedade. Por exemplo:
public class Blog : INotifyPropertyChanging, INotifyPropertyChanged
{
public event PropertyChangingEventHandler PropertyChanging;
public event PropertyChangedEventHandler PropertyChanged;
private int _id;
public int Id
{
get => _id;
set
{
PropertyChanging?.Invoke(this, new PropertyChangingEventArgs(nameof(Id)));
_id = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Id)));
}
}
private string _name;
public string Name
{
get => _name;
set
{
PropertyChanging?.Invoke(this, new PropertyChangingEventArgs(nameof(Name)));
_name = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name)));
}
}
public IList<Post> Posts { get; } = new ObservableCollection<Post>();
}
Além disso, qualquer navegação de coleta deve ser implementada INotifyCollectionChanged; no exemplo acima, isto é conseguido ao usar uma ObservableCollection<T> dos posts. O EF Core também vem com uma ObservableHashSet<T> implementação que realiza buscas mais eficientes à custa de uma ordenação estável.
A maior parte desse código de notificação normalmente é movida para uma classe base não mapeada. Por exemplo:
public class Blog : NotifyingEntity
{
private int _id;
public int Id
{
get => _id;
set => SetWithNotify(value, out _id);
}
private string _name;
public string Name
{
get => _name;
set => SetWithNotify(value, out _name);
}
public IList<Post> Posts { get; } = new ObservableCollection<Post>();
}
public abstract class NotifyingEntity : INotifyPropertyChanging, INotifyPropertyChanged
{
protected void SetWithNotify<T>(T value, out T field, [CallerMemberName] string propertyName = "")
{
NotifyChanging(propertyName);
field = value;
NotifyChanged(propertyName);
}
public event PropertyChangingEventHandler PropertyChanging;
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyChanged(string propertyName)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
private void NotifyChanging(string propertyName)
=> PropertyChanging?.Invoke(this, new PropertyChangingEventArgs(propertyName));
}
Configurando entidades de notificação
Não há como o EF Core validar se INotifyPropertyChanging ou INotifyPropertyChanged estão totalmente implementados para uso com o EF Core. Particularmente, alguns usos dessas interfaces ocorrem com notificações apenas em certas propriedades, em vez de em todas (incluindo as navegações), conforme exigido pelo EF Core. Por esse motivo, o EF Core não se conecta automaticamente a esses eventos.
Em vez disso, o EF Core deve ser configurado para usar essas entidades de notificação. Isso geralmente é feito para todos os tipos de entidade chamando ModelBuilder.HasChangeTrackingStrategy. Por exemplo:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasChangeTrackingStrategy(ChangeTrackingStrategy.ChangingAndChangedNotifications);
}
(A estratégia também pode ser definida de forma diferente para diferentes tipos de entidade usando EntityTypeBuilder.HasChangeTrackingStrategy, mas isso geralmente é contraproducente, pois DetectChanges ainda é necessário para os tipos que não são entidades de notificação.)
O controle completo de alterações de notificação requer que tanto INotifyPropertyChanging quanto INotifyPropertyChanged sejam implementados. Isso permite que os valores originais sejam salvos imediatamente antes de o valor da propriedade ser alterado, evitando a necessidade de o EF Core criar um instantâneo ao rastrear a entidade. Os tipos de entidade que implementam somente INotifyPropertyChanged também podem ser usados com o EF Core. Nesse caso, o EF ainda cria um instantâneo ao rastrear uma entidade para acompanhar os valores originais, mas depois usa as notificações para detectar alterações imediatamente, em vez de precisar que o método DetectChanges seja chamado.
Os diferentes ChangeTrackingStrategy valores estão resumidos na tabela a seguir.
| Estratégia de Rastreamento de Alterações | Interfaces necessárias | Precisa de DetectChanges | Valores originais de instantâneos |
|---|---|---|---|
| Instantâneo | Nenhum | Sim | Sim |
| Notificações alteradas | INotifyPropertyChanged | Não | Sim |
| Notificações de Alteração e Alteradas | INotifyPropertyChanged e INotifyPropertyChanging | Não | Não |
| NotificaçõesDeAlteraçãoEAlteradasComValoresOriginais | INotifyPropertyChanged e INotifyPropertyChanging | Não | Sim |
Usando entidades de notificação
As entidades de notificação comportam-se como quaisquer outras entidades, exceto que, ao fazer alterações nas instâncias da entidade, não é necessário realizar uma chamada para ChangeTracker.DetectChanges() para que essas alterações sejam detetadas. Por exemplo:
using var context = new BlogsContext();
var blog = await context.Blogs.Include(e => e.Posts).FirstAsync(e => e.Name == ".NET Blog");
// Change a property value
blog.Name = ".NET Blog (Updated!)";
// Add a new entity to a navigation
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..."
});
Console.WriteLine(context.ChangeTracker.DebugView.LongView);
Com entidades normais, a exibição de depuração do controlador de alterações mostrou que essas alterações não foram detetadas até que DetectChanges fosse chamado. Observar a visualização de depuração quando as entidades de notificação são usadas mostra que essas alterações foram detetadas imediatamente:
Blog {Id: 1} Modified
Id: 1 PK
Name: '.NET Blog (Updated!)' Modified
Posts: [{Id: 1}, {Id: 2}, {Id: -2147482643}]
Post {Id: -2147482643} Added
Id: -2147482643 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} 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}
Proxies de controle de alterações
O EF Core pode gerar dinamicamente tipos de proxy que implementam INotifyPropertyChanging e INotifyPropertyChanged. Isso requer a instalação do pacote Microsoft.EntityFrameworkCore.Proxies NuGet, e a ativação de proxies de rastreamento de alterações com UseChangeTrackingProxies Por exemplo:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseChangeTrackingProxies();
A criação de um proxy dinâmico envolve a criação de um novo tipo .NET dinâmico (usando a implementação de proxies Castle.Core ), que herda do tipo de entidade e, em seguida, substitui todos os setters de propriedade. Os tipos de entidade para proxies devem, portanto, ser tipos que podem ser herdados e devem ter propriedades que podem ser substituídas. Além disso, as navegações de coleção criadas explicitamente devem implementar INotifyCollectionChanged Por exemplo:
public class Blog
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<Post> Posts { get; } = new ObservableCollection<Post>();
}
public class Post
{
public virtual int Id { get; set; }
public virtual string Title { get; set; }
public virtual string Content { get; set; }
public virtual int BlogId { get; set; }
public virtual Blog Blog { get; set; }
}
Uma desvantagem significativa dos proxies de controle de alterações é que o EF Core sempre deve rastrear instâncias do proxy, nunca instâncias do tipo de entidade subjacente. Isso acontece porque as instâncias do tipo de entidade subjacente não gerarão notificações, o que significa que as alterações feitas nessas entidades serão desconsideradas.
O EF Core cria instâncias de proxy automaticamente ao consultar o banco de dados, portanto, essa desvantagem geralmente se limita ao rastreamento de novas instâncias de entidade. Essas instâncias devem ser criadas usando os CreateProxy métodos de extensão, e não da maneira normal usando new. Isso significa que o código dos exemplos anteriores agora deve fazer uso de CreateProxy:
using var context = new BlogsContext();
var blog = await context.Blogs.Include(e => e.Posts).FirstAsync(e => e.Name == ".NET Blog");
// Change a property value
blog.Name = ".NET Blog (Updated!)";
// Add a new entity to a navigation
blog.Posts.Add(
context.CreateProxy<Post>(
p =>
{
p.Title = "What’s next for System.Text.Json?";
p.Content = ".NET 5.0 was released recently and has come with many...";
}));
Console.WriteLine(context.ChangeTracker.DebugView.LongView);
Eventos de rastreamento de alterações
O EF Core dispara o ChangeTracker.Tracked evento quando uma entidade é rastreada pela primeira vez. Alterações futuras no estado da entidade resultam em ChangeTracker.StateChanged eventos. Consulte Eventos do .NET no EF Core para obter mais informações.
Observação
O evento StateChanged não é disparado quando uma entidade começa a ser seguida, mesmo que o estado tenha mudado de Detached para um dos outros estados. Certifique-se de ouvir tanto o evento StateChanged quanto o Tracked para receber todas as notificações relevantes.