У меня есть этот класс, который я использую для операций с БД:
public class EntityService<TEntity> : IRepository<TEntity> where TEntity : BaseModel
{
ApplicationDbContext _context;
private DbSet<TEntity> _entities;
public EntityService()
{
_context = new ApplicationDbContext();
}
public virtual void Update(TEntity entity)
{
if (entity == null)
throw new ArgumentNullException(nameof(entity));
try
{
var dbEnt = _context.Set<TEntity>().Where(c => c.Id == entity.Id).First();
dbEnt = entity;
dbEnt.UpdatedBy = GetCurrentUser();
dbEnt.DateUpdated = DateTime.Now;
_context.SaveChanges();
}
catch (DbUpdateException exception)
{
throw new Exception(GetFullErrorTextAndRollbackEntityChanges(exception), exception);
}
//-----other methods for insert and get working fine----
}
В этом классе есть и другие методы для insert и get, которые работают нормально. Только этот метод обновления не обновляет сущность и не генерирует исключение.
ОБНОВИТЬ
Я столкнулся с аналогичной проблемой, но с противоположным функционированием здесь: Метод Add(), добавляющий повторяющиеся строки для связанных моделей в Code-First Entity Framework
Я думаю, что у этих двоих одна и та же причина отслеживания изменений. Но один добавляет другой не обновляет.
@ hassan.ef Это не сработало, когда я это сделал. Поэтому я подумал, что если я снова получу объект базы данных из базы данных, то _context будет иметь ссылку на базу данных. Потому что entity — это просто параметр метода.
Я думаю, что сначала вы должны обновить параметр dbEnt параметром entity следующим образом: например: edbEnt.Name = entity.Name и не использовать dbEnt = entity.
@hassan.ef Это распространенный метод обновления всех объектов. Я не могу явно назначить здесь отдельное свойство.
Перед сохранением вы можете попробовать вызвать context.Entry(entity).State=EntityState.Modified;.
@bolkay Я пробовал, но мне показывает эту ошибку Attaching an entity of type '<type>' failed because another entity of the same type already has the same primary key value.This can happen when using the 'Attach' method or setting the state of an entity to 'Unchanged' or 'Modified' if any entities in the graph have conflicting key values. `
@bolkay продолжение комментария выше This may be because some entities are new and have not yet received database-generated key values.In this case use the 'Add' method or the 'Added' entity state to track the graph and then set the state of non-new entities to 'Unchanged' or 'Modified' as appropriate.'
@bolkay Как использовать решение, предложенное ошибкой?
Это проблема отслеживания. Попробуйте что-то вроде: _context.Set<TEntity>().Add(dbEnt); Тогда : _context.Entry(dbEnt).State=EntityState.Modified; Тогда SaveChanges(); Вы можете прочитать о различных состояниях сущности.
@bolkay После этого появляется эта ошибка Saving or accepting changes failed because more than one entity of type '<type>' have the same primary key value. Ensure that explicitly set primary key values are unique. Ensure that database-generated primary keys are configured correctly in the database and in the Entity Framework model.
Ваш опубликованный код в порядке, за исключением dbEnt = entity;. Вместо этого скопируйте измененные поля в dbEnt.Prop1 = entity.Prop1; и т. д. Мы используем Automapper, чтобы сделать все поля за 1 шаг: Mapper.Map(entity, dbEnt);
Вы начали вознаграждение, потому что вопрос не получил достаточного внимания. Но что не ясно/недостаточно в моем ответе? Кажется, я точно описал причину неудачного обновления. И предложил рабочие альтернативы. Вам было бы намного дешевле ответить на мой ответ, если бы он был недостаточно ясен.
@GertArnold Я уже пробовал ваш ответ, как упоминал в предыдущих комментариях. Болкай предложил мне это. Я пробовал всеми способами прикрепить сущность к трекеру изменений, но это не работает. Даже я попытался снова инициализировать объект ApplicationContext в этом методе. Но я все еще пробую другие способы и ищу еще предложения, чтобы получить подсказку. Вот почему я начал щедрость. Я вернусь к вашему ответу, как только перепробую все, что смогу найти в Интернете.
Непонятно, какой точно вы пробовали, потому что это всего лишь фрагмент в комментарии. Но что бы это ни было, нет смысла использовать Add() для сущности, которую вы только что извлекли из базы данных и хотите только обновить. Он уже привязан к контексту! Вам нужно только обновить его значения и сохранить изменения. В этом суть моего второго предложения.





Линия...
var dbEnt = _context.Set<TEntity>().Where(c => c.Id == entity.Id).First();
... присоединяет объект сущности к контексту и возвращает ссылку на эту сущность.
Потом очередь...
dbEnt = entity;
... заменяет эту ссылку ссылкой на переменную entity, которая входит в метод. Это не отслеживаемый объект сущности. Вы фактически потеряли ссылку на отслеживаемый объект, и изменить ее больше невозможно.
Вы должны либо прикрепить entity к контексту и пометить его как измененный, либо получить dbEnt, как вы уже делаете, и изменить и сохранить объект это. Оба метода имеют свои плюсы и минусы, см. здесь.
После получения сущности из _context обновите все поля из новой детали и установите состояние сущности как измененное.
var dbEnt = _context.Set<TEntity>().Where(c => c.Id == entity.Id).First();
dbEnt.Name = entity.Name;
...
...
...
dbEnt.UpdatedBy = GetCurrentUser();
dbEnt.DateUpdated = DateTime.Now;
_context.Entry(dbEnt).State = EntityState.Modified;
_context.SaveChanges();
Ставить State = EntityState.Modified не обязательно, я бы даже сказал не рекомендуется.
Если вы нашли свою сущность по идентификатору
var dbEnt = _context.Set<TEntity>().Where(c => c.Id == entity.Id).First();
тогда зачем эта строка?
dbEnt = entity;
Удалите указанную выше строку, так как она удалит ссылку на отслеживаемый объект.
Спасибо всем. Я получил много подсказок из ваших ответов. Как @GertArnold и Ответ @Colonel Software намекнул мне, что я изменил свой код следующим образом, и это сработало:
//Assigning BaseModel properties
entity.CreatedBy = dbEnt.CreatedBy;
entity.UpdatedBy = GetCurrentUser();
entity.DateUpdated = DateTime.Now;
entity.DateCreated = dbEnt.DateCreated;
//Changing entity states
_context.Entry(dbEnt).State = EntityState.Detached;
_context.Entry(entity).State = EntityState.Modified;
_context.SaveChanges();
Почему бы просто не изменить и не сохранить dbEnt, как это происходит в ответе, на который вы ссылаетесь?
@GertArnold Пробовал это, но он показывает исключение повторяющихся значений первичного ключа. Я попытаюсь решить это позже, но пока это работает, и мне нужно скоро доставить проект, поэтому я должен пойти на это решение. Спасибо за вашу помощь, я многому научился из вашего ответа и +1 к нему.
Понижение за что? Это решает мою проблему. Может быть, это не лучшее решение, но это решение.
Тогда вы сделали больше, чем показываете. Как сказано в другом ответе (и моем), все, что вам нужно, это удалить dbEnt = entity;.
Понижение связано с тем, что ваш код едва соответствует бывает, работает. Использование AddOrUpdate здесь довольно странно.
@GertArnold Решение представляло собой смесь всех ответов. Было две проблемы. Одно упомянуло вами, что это произошло из-за того, что отслеживание было потеряно, а другое упоминание Colonel Software намекнуло, что мне нужно назначить все свойства базовой модели.
@GertArnold в любом случае я отмечаю твой ответ вместо своего. Пожалуйста, отзовите отрицательный голос, потому что это одно из решений.
Извините, мой отрицательный голос остается в силе, потому что это плохой метод обновления объекта. Вы действительно не должны использовать AddOrUpdate, пока не узнаете, что он делает (и делает нет). И когда вы понимаете, что больше не будете его использовать.
@GertArnold Хорошо, не могли бы вы объяснить мне или дать ссылку, где я могу найти его недостатки? И не могли бы вы изменить мой код и обновить свой ответ?
@GertArnold Каким-то образом я смог сделать это без AddOrUpdate. Пожалуйста, проверьте обновление моего ответа и отзовите отрицательный голос, если все в порядке.
Удалите dbEnt = entity; и измените свойства dbEnt!
если вы хотите обновить
entity, почему вы снова получаете его и ставитеentityвdbEnt?