EF Core странно кешируется внутри Scope

107
24 января 2021, 17:10

Поймал багу с Entity Framework Core 2.2 (Npgsql) и не могу понять, что происходит за кулисами, в чем и прошу помочь разобраться.

Рассмотрим для примера код:

using (var scope1 = Services.CreateScope())
{
    var db1 = scope1.ServiceProvider.GetRequiredService<DbContext>();
    var item1 = await db1.Items.FirstOrDefaultAsync();
    //Executed DbCommand: SELECT a."Id", a."Value" FROM "Items" AS a LIMIT 1
    item1.Value = "123";
    await db1.SaveChangesAsync();
    //Executed DbCommand: UPDATE "Items" SET "Value" = @p0 WHERE "Id" = @p1;
    // Представим, что этот using исполняется на другой машине
    using (var scope2 = Services.CreateScope())
    {
        var db2 = scope2.ServiceProvider.GetRequiredService<DbContext>();
        var item2 = await db2.Items.FirstOrDefaultAsync();
        //Executed DbCommand: SELECT a."Id", a."Value" FROM "Items" AS a LIMIT 1
        item2.Value = "qwe";
        await db2.SaveChangesAsync();
        //Executed DbCommand: UPDATE "Items" SET "Value" = @p0 WHERE "Id" = @p1;
    }
    item1 = await db1.Items.FirstOrDefaultAsync();
    //Executed DbCommand: SELECT a."Id", a."Value" FROM "Items" AS a LIMIT 1
    //Result: item1.Value = "123" - old value... =(
}

Проблема в том, что db1 не видит изменения, которые были внесены через db2. Т.е. в консоли отладки я вижу, что Executed: SELECT ... FROM "Items" AS a LIMIT 1, но полученное значение item1 содержит старое значение "123", вместо "qwe".

Кстати,

  • если использовать только один scope, то все нормально.
  • или, если в последней строчке добавить .AsNoTracking(), то все нормально.

Это очень похоже на то, что данные просто закешировались, но в консоли то выводится, что запросы к базе таки идут...

Вопрос в следующем:

В результате работы данного кода, в БД физически отправляется пять SQL запросов (в том числе, последний SELECT). Так вот, что это за кеширование такое, при котором SELECT запрос отправляется, но его результат игнорируется? В моем понимании кеширование - это когда мы "помним" данные, чтобы НЕ делать лишних запросов. А тут наоборот. Должно же быть этому объяснение...

Answer 1

EF реализует паттерн UnitOfWork: ваш DbContext содержит набор репозиториев, вы можете к ним обращаться, чтобы читать и писать данные.

При этом EF "помнит" в каком состоянии находится база данных. Вы можете изменять данные в оперативной памяти, но реальное сохранение в базу данных происходит только тогда, когда вы запустите .SaveChanges().

Это хранение состояния происходит на уровне экземпляра DbContext. Поэтому когда вы принудительно создали два разных экземпляра — то и изменения в них трекаются независимо.

Поэтому никакой ошибки Entity Framework в вашем коде нет, просто вы не в курсе, как это должно работать.

Если вы хотите отслеживание изменений — используйте один экземпляр. Приведённый вами пример является концептом для демонстрации, я думаю, что в вашем реальном коде нет никакой необходимости открывать второй scope, раз можно обойтись одним - либо не закрывая вовсе using, либо закрыть и тут же открыть новый. На практике такой пример как у вас стараются не допускать.

Вторая часть вашего вопроса о AsNoTracking. Дело в том, что указывая AsNoTracking вы (в качестве побочного эффекта, основное-то — не трекать изменения) заставляете взять не значение из "кеша", а прочитать из базы. (Не буду утверждать, что правильно понимаю механику, но мне кажется дело происходит примерно так: у вас есть как вы говорите "в кеше" одна запись, которая трекает изменения, когда вы просите создать нетреканную - она не берётся как копия уже имеющейся, а лезет в базу.)

В реальном коде я бы не стал полагаться на такую механику, потому что это нетривиально (может поломаться), да и предполагает, что программист хорошо понимает, что происходит под капотом. (иначе для того, кто читает этот код (а это можете быть и вы: через полгода уже не вспомните, что и зачем) будет казаться "магией").

READ ALSO
C# При обращении к процедуре ошибка PLS-00306

C# При обращении к процедуре ошибка PLS-00306

Собственно при таком раскладе происходит эта ошибка (смвложения) два варианта обращения к процедуре, с разным аргументом, необходимы по причине...

93
Как работает GroupBy

Как работает GroupBy

Всем доброго времени суток! Прошу помочь разобраться в том, как работает GroupBy и в чем разница приведенного ниже кода

129
Ошибка System.UnauthorizedAccessException" в mscorlib.dll в приложении в win 10

Ошибка System.UnauthorizedAccessException" в mscorlib.dll в приложении в win 10

Есть мое приложениекоторое работает, и вот в него добавлю механизм записи технической информации

119