#c# #.net #entity-framework #.net-core #entity-framework-core
#c# #.net #entity-framework #.net-ядро #entity-framework-core
Вопрос:
Как мне обновить таблицы с включениями в Entity Framework Core? Кажется, что следующее обновляет транзакцию клиента, но не идентификатор продукта.
И у клиента, и у продукта есть атрибуты, которые меняются, но CustomerID и ProductID остаются неизменными.
public void ModifyTransaction(IEnumerable<CustomerTransaction> customerTransactionList)
{
foreach (var modifyItem in customerTransactionList)
{
var existingItem = _dbContext.Set<CustomerTransaction>().Include(x => x.Product)
.FirstOrDefault(x => x.CustomerTransactionId == modifyItem.CustomerTransactionId );
if (existingItem == null)
{
_dbContext.Add(existingItem );
}
else
{
_dbContext.Entry(existingItem).State = EntityState.Modified;
}
}
_dbContext.SaveChanges();
}
использование Net Core 3.1
Комментарии:
1. Вы не внесли никаких изменений в сущность. Что необходимо обновить?
Ответ №1:
Этот код подвержен ошибкам, которые будут возникать в зависимости от сценария. При передаче классов сущностей между клиентом и сервером важно понимать, что объекты, передаваемые обратно на сервер, являются просто сериализованными копиями, а не отслеживаемыми объектами. Из-за способа работы сериализации, когда DbContext, извлекающий две записи транзакций, которые ссылаются на продукт с идентификатором: 14, будет ссылаться на один и тот же экземпляр объекта, эта же пара транзакций при десериализации будет иметь две отдельные ссылки на объекты, каждая с идентификатором продукта: 14.
Учитывая ваш пример, как минимум, вам нужно будет сделать что-то вроде:
foreach (var modifyItem in customerTransactionList)
{
var existingItem = _dbContext.CustomerTransactions
.Include(x => x.Product)
.SingleOrDefault(x => x.CustomerTransactionId == modifyItem.CustomerTransactionId );
var trackedProduct = _dbContext.Products.Local(x => x.ProductId == modifyItem.Product.ProductId).SingleOrDefault();
if (trackedProduct != null)
modifyItem.Product = trackedProduct;
else
_dbContext.Products.Attach(modifyItem.Product);
if (existingItem == null)
_dbContext.Add(modifyItem);
else
{
_dbContext.Entry(existingItem).CurrentValues.SetValues(modifyItem);
if(existingItem.Product.ProductId != modifyItem.Product.ProductId)
existingItem.Product = modifyItem.Product; // tracked reference.
}
}
_dbContext.SaveChanges();
}
Это означает проверку существующей транзакции, как вы делали. Однако мы также должны проверять наличие любых кэшированных копий связанных объектов (Product), которые может отслеживать DbContext. Если мы этого не сделаем и попытаемся добавить транзакцию, в которой есть продукт с идентификатором, соответствующим идентификатору, который уже отслеживается контекстом, мы получим либо нарушение PK, либо дублирующую запись продукта с новым созданным идентификатором продукта, в зависимости от того, как настроен PK вашего продукта. (т.е. DatabaseGenerated.Identity
столбец) Мы обновляем ссылку на продукт экземпляром локального кэша, если он найден, в противном случае мы сообщаем DbContext начать отслеживание экземпляра продукта. Это предполагает, что этот метод не может принимать новые продукты как часть этого вызова и что полученная запись продукта должна существовать юридически. Обработка новых продуктов и проверка переданного продукта потребовали бы дополнительных проверок условного кода и базы данных. Оттуда мы определяем, является ли Транзакция обновлением или вставкой. В случае обновления мы можем использовать CurrentValues.SetValues
для копирования значений (как указано выше) или Automapper, или вручную скопировать соответствующие значения. Предполагая, что транзакция может изменить продукт, мы также проверяем идентификатор продукта на соответствие измененному, и если он отличается, мы обновляем ссылку на продукт. modifyItem.Product
на этом этапе будет указываться на отслеживаемую ссылку DbContext.
Обновление объектов с помощью подобных методов может быть довольно сложным, поскольку вам приходится учитывать не только обнаружение новых и существующих записей, но и потенциальное обновление ссылок на объекты, которые DbContext уже отслеживает. Моя настоятельная рекомендация — использовать модели представления для явных операций добавления и обновления и обрабатывать операции как можно более атомарно. Т.е. Вместо того, чтобы передавать коллекцию транзакций, которые могут содержать обновления или вставки для обработки, делайте более детальные вызовы для каждого отдельного типа изменений. (Более простые, быстрые операции и меньше мест для возникновения ошибок)