AsNoTrackingWithIdentityResolution в EF
При работе с Entity Framework Core понимание поведения отслеживания изменений критически важно как для производительности, так и для согласованности данных. Про AsNoTracking() знают все, а вот его мощная альтернатива AsNoTrackingWithIdentityResolution() не так популярна.
Что такое AsNoTracking()?
По умолчанию EF Core отслеживает все сущности, возвращаемые запросами в трекере изменений. Отслеживание обеспечивает:
- автоматическое обнаружение изменений в сущностях;
- операции обновления без явного присоединения сущностей;
- разрешение идентификаторов (гарантируя наличие только одного экземпляра каждой сущности в памяти).
Однако отслеживание сопряжено с накладными расходами. При выполнении операций только чтения, когда не требуется обновлять данные, AsNoTracking() повышает производительность, полностью отменяя отслеживание изменений:
var customers = await context.Customers
.AsNoTracking()
.ToListAsync();
Проблема - дублирование экземпляров сущностей. Если трекер изменений не будет выполнять разрешение идентификаторов, в памяти может оказаться несколько экземпляров одной и той же сущности, например:
var orders = await context.Orders
.AsNoTracking()
.Include(o => o.Customer)
.Where(o => o.OrderDate > DateTime.Now.AddDays(-30))
.ToListAsync();
Если несколько заказов принадлежат одному и тому же клиенту, вы получите отдельные экземпляры Customer для каждого заказа, даже если они представляют одну и ту же запись в БД. Это означает:
- увеличение потребления памяти,
- возможность путаницы при сравнении сущностей,
- потерю ссылочной целостности в графе объектов.
AsNoTrackingWithIdentityResolution()
AsNoTrackingWithIdentityResolution() решает эту проблему, объединяя преимущества двух подходов:
- отсутствие отслеживания изменений (выигрыш в производительности),
- разрешение идентификаторов (гарантия единственности экземпляров сущностей).
var orders = await context.Orders
.AsNoTrackingWithIdentityResolution()
.Include(o => o.Customer)
.Where(o => o.OrderDate > DateTime.Now.AddDays(-30))
.ToListAsync();
// Теперь все заказы от одного клиента
// будут ссылаться на один экземпляр Customer в памяти
При этом EF Core:
- выполняет запрос без присоединения сущностей к трекеру изменений;
- сохраняет временную карту идентификации во время материализации запроса;
- гарантирует, что сущности с одинаковым ключом ссылаются на один и тот же экземпляр;
- удаляет карту идентификации после завершения запроса.
Т.е. что вы получаете разрешение идентификации во время выполнения запроса без дополнительных затрат на отслеживание изменений.
Также можно установить AsNoTrackingWithIdentityResolution() как поведение трекера по умолчанию:
protected override void OnConfiguring(
DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseSqlServer(connectionString)
.UseQueryTrackingBehavior(
QueryTrackingBehavior.NoTrackingWithIdentityResolution);
}
Когда использовать
1. Отслеживание по умолчанию:
- необходимо обновлять, удалять или отслеживать изменения сущностей;
- небольшие наборы результатов, где затраты на отслеживание незначительны;
- необходимо автоматическое обнаружение изменений.
2. AsNoTracking:
- операции только чтения;
- нет свойств навигации или связанных сущностей;
- максимальная производительность критически важна;
- результаты не содержат дублирующихся сущностей.
3. AsNoTrackingWithIdentityResolution:
- операции только чтения с include/join;
- требуется ссылочная целостность в графе объектов;
- запросы, возвращающие одну сущность несколько раз;
- требуется сравнивать сущности по ссылке.
Источник: https://bartwullems.blogspot.com/2025/10/understanding-asnotrackingwithidentityr.html