Entity Framework Core Чтение базы из одного контекста, сохранение в другом контексте и очистка локального контекста
PS Для примера классы сильно упрощены. Версия EFCore 3.1
Есть схема:
class Configure
{
public int id { get; set; }
public string name { get; set; }
}
class Value
{
public int id { get; set; }
[ForeignKey(nameof(confid))]
public Configure? conf { get; set; }
[NotMapped]
public int? confid { get; set; }
public long value { get; set; }
}
Данные я получаю в одном контексте и на основании Configure запускаю генерацию Value в бесконечном цикле:
DBContext context1 = new DBContext();
List<Configure> confs = context1.Configures.ToList();
foreach (var conf in confs)
{
Thread th = new Thread(() => calculate(conf));
th.Start();
}
Когда нужно добавить новые данные в контекст и сохранить его - возникает ошибка:
void calculate(Configure conf)
{
while(flagisactive)
{
DBContext context2 = new DBContext();
Value v = new Value()
{
Configure = conf,
confid = conf.id,
value = DateTime.Now.Ticks
}
context2.Values.Add(v);
context2.SaveChanges(); //тут ошибка:
//Microsoft.EntityFrameworkCore.DbUpdateException: "An error occurred while updating the entries. See the inner exception for details."
//MySqlException: Duplicate entry '1' for key 'configures.PRIMARY'
}
}
Причина в том, что context2 не знает о том, что эти данные по Configure уже есть в БД и были получены в context1 и context2 пытается записать их как новые.
Есть 2 пути решения данной проблемы:
- Как раньше и делал - писать всё руками через SQL, но число таких таблиц как Value растёт, как и их сложность и постоянно меняется, значительно повышается вероятность ошибки.
- Писать прямо в context1 новые записи по Value - бесконечный рост потребляемой памяти, нужна очистка значений, т.к. приложение будет крутиться месяцами и никто не будет его перезапускать.
В итоге - эти известные мне варианты - не вариант, и нужно как-то сообщать новому контексту, что связанные данные уже есть в БД и нужно добавить только Value и не писать Configure. Я где-то читал что такое возможно, но не могу найти пример.
В качестве альтернативного варианта - передавать context1 в цикл, добавлять новый Value, и затем сразу удалять его после context1.SaveChanges(), но только локально, а не из базы, но есть загвоздка в том, что не везде можно его передать, поэтому т.к. Value может быть не привязан к Configure. Так-же не могу найти информацию о том как можно удалять из локального контекста конкретные записи, чтобы впоследствии EntityFramework их не стёр из БД.
PS Новые данные в такие таблицы будут добавлять редко, поэтому с точки зрения ресурсозатратности (чтобы заводить новый контекст) проблем нет.
В итоге вопрос в следующем: как можно реализовать передачу информации о том что данные уже есть в БД из одного контекста в другой? Или хотя-бы удалять записи только из локального контекста, не трогая сохранённые в БД!?
Ответы (1 шт):
Нашёл решение по ссылкам:
https://docs.microsoft.com/ru-ru/ef/core/saving/disconnected-entities
https://github.com/dotnet/EntityFramework.Docs/tree/main/samples/core/Saving/Disconnected/
https://www.entityframeworktutorial.net/efcore/changetracker-trackgraph-ef-core.aspx
Нужно было заменить:
context2.Values.Add(v);
на:
context2.ChangeTracker.TrackGraph(v, n =>
{
if (n.Entry.Entity == ae) n.Entry.State = Microsoft.EntityFrameworkCore.EntityState.Added;
else n.Entry.State = Microsoft.EntityFrameworkCore.EntityState.Unchanged;
});
После чего SaveChanges() сохраняет только текущий объект.
Если кому-нибудь известно о подводных камнях и способах устранения - прошу поделиться информацией в комментариях.