Почему я получаю это исключение «Не удалось присоединить объект типа ‘Model’, потому что другой объект того же типа уже имеет то же значение первичного ключа».

#c# #asp.net-mvc #entity-framework

#c# #asp.net-mvc #entity-framework

Вопрос:

Когда я пытался обновить запись в базе данных, было выдано исключение. Сначала я получаю запись из базы данных, чтобы получить дату записи записи. Затем я переношу dbmodel в datamodel, а затем переношу его обратно. Но это не сработало.

 Attaching an entity of type 'ProgramLanguage' 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. 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.
  

Это моя dbmodel

 public partial class ProgramLanguage
    {
        public int ProgramLanguageId { get; set; }
        public int TestId { get; set; }
        public string ProgramLanguageName { get; set; }
        public string ProgramLanguageLevel { get; set; }
        public Nullable<System.DateTime> RecordDate { get; set; }
    
        public virtual Test Test { get; set; }
    }
  

Это моя модель данных

 [DataContract]
    [Serializable]
    public class ProgramLanguage
    {
        public ProgramLanguage()
        {
            Test = new Test();
        }

        [DataMember]
        public int ProgramLanguageId { get; set; }

        [DataMember]
        public int TestId { get; set; }

        [DataMember]
        public string ProgramLanguageName { get; set; }

        [DataMember]
        public string ProgramLanguageLevel { get; set; }

        [DataMember]
        public DateTime RecordDate { get; set; }

        [DataMember]

        public  Test Test { get; set; }

    }
  

Это часть кода, которая взаимодействует с контекстом БД.

 public bool UpdateProgramLanguage(ProgramLanguage programLanguage)
{
     _repository.Update(programLanguage);
     return true;
}
  

Это бизнес-уровень, который взаимодействует с модулем БД.

 public bool UpdateProgramLanguage(ProgramLanguageDm programLanguage)
        {
            using (var iUnitOfWork = new UnitOfWork<TourismEntities>())
            {
                var coreProgramLanguage = iUnitOfWork.CommonHandler.GetProgramLanguageById(programLanguage.ProgramLanguageId);
                if (coreProgramLanguage == null)
                {
                    return false;
                }
                var detachedCore = GeneralMigrate.ProgramLanguageDmToEntity(GeneralMigrate.ProgramLanguageEntityToDm(coreProgramLanguage));
                var newCore = GeneralMigrate.ProgramLanguageDmToEntity(programLanguage);

                newCore.RecordDate = detachedCore.RecordDate;
                newCore.TestId = detachedCore.TestId;

                return iUnitOfWork.CommonHandler.UpdateProgramLanguage(newCore);
            }
        }
  

Это часть кода, когда я переношу model в datamodel.

 public static ProgramLanguageDm ProgramLanguageEntityToDm(ProgramLanguage entity)
        {
            return entity != null ? new ProgramLanguageDm{
                TestId = entity.TestId,
                ProgramLanguageId = entity.ProgramLanguageId,
                ProgramLanguageLevel = entity.ProgramLanguageLevel,
                ProgramLanguageName = entity.ProgramLanguageName,
                RecordDate = entity.RecordDate ?? new DateTime(),
            }
            : new ProgramLanguageDm();
        }

        public static ProgramLanguage ProgramLanguageDmToEntity(ProgramLanguageDm dm)
        {
            return dm != null ? new ProgramLanguage {
                TestId = dm.TestId,
                ProgramLanguageId = dm.ProgramLanguageId,
                ProgramLanguageLevel = dm.ProgramLanguageLevel,
                ProgramLanguageName = dm.ProgramLanguageName
            }
            : new ProgramLanguage();
        }
  

Ответ №1:

У вас эта ошибка, потому что вы пытаетесь присоединить / добавить объект к контексту базы данных, когда контекст уже знает объект с тем же идентификатором.

Позвольте мне привести простой пример, чтобы объяснить это более простым способом.

У нас есть эти model и dto:

 public class MyModel
{

    [Key]
    public int Id { get; set; }


    [StringLength(30)]
    public string Description { get; set; }
}


public class MyDTO
{

    public int Id { get; set; }


    public string Description { get; set; }
}
  

Не так много свойств: идентификатор (первичный ключ) и описание.

Теперь я делаю то, что вы делаете в своем коде:

         using (var db = new MyDbContext())
        {

            // In the DB context, there is 1 object with id 1
            var model = await db.MyModels.SingleOrDefaultAsync(x => x.Id == 1);

            // Convert model into DTO
            var dto = new MyDTO { Id = model.Id, Description = model.Description };

            // Convert back to a new model (you do that in your code)
            var anotherModel = new MyModel { Id = dto.Id, Description = dto.Description };


            // Trying to add the model to the db context...
            // It will throw an exception since the db context already has an object with Id = 1 (model)
            // The exception is the same you posted
            db.MyModels.Attach(anotherModel);
        }
  

Просмотр всех шагов:

  1. Мы считываем данные из базы данных (SingleOrDefault). Entity Framework предоставляет нам объект (модель) и сохраняет в своей памяти данные этого объекта
  2. Мы преобразуем модель в DTO (переменная с именем dto)
  3. Мы преобразуем DTO обратно в модель. Но мы создаем новый экземпляр модели (переменная AnotherModel)
  4. Мы пытаемся добавить новую модель в контекст БД.

Когда мы выполняем 4-й шаг, приложение выдает исключение, поскольку DbContext уже знает модель с Id = 1 (переменная model), и вы не можете добавить другую с тем же id (AnotherModel).

Есть два варианта:

  1. Когда мы читаем данные вместо db.MyModels.По умолчанию SingleOrDefault мы можем использовать sintax db.MyModels.AsNoTracking() .SingleOrDefault… С помощью «AsNoTracking» мы говорим контексту БД, чтобы ОН НЕ сохранял модель в своей памяти (отслеживание)
  2. Когда мы возвращаем объект из DTO в model, мы должны изменить исходную модель (переменная модель) вместо создания новой (AnotherModel). При этом контекст БД увидит наши изменения и реплицирует их в базе данных

Глядя на ваш код, я предлагаю использовать первые варианты. Это будет проще, и, поскольку ему не нужно заботиться об отслеживании, запрос будет быстрее.

Надеюсь, я помог

Комментарии:

1. Второй вариант решил мою проблему. Однако я не мог понять первый вариант, и я не мог попытаться решить свою проблему с его помощью.

2. Для первого варианта просто измените значение чтения с помощью этого синтаксиса: db.MyModels. AsNoTracking() . SingleOrDefaultAsync(x => x.Id == 1). При этом контекст ничего не сохранит о модели, которую он читает (без отслеживания), поэтому, когда код поступает в последнюю строку (прикрепить), контекст не знает модель, и исключение не будет выдано.

Ответ №2:

PiGi78 решил проблему: вы чрезмерно читаете и преобразуете между моделью данных и объектом.

Единственный раз, когда вы должны преобразовать Dm в объект, это при создании нового объекта. При обновлении объекта мы можем загрузить объект, скопировать значения из переданной модели и сохранить объект. (Нет необходимости конвертировать вещи туда и обратно)

Этот код:

 var coreProgramLanguage = iUnitOfWork.CommonHandler.GetProgramLanguageById(programLanguage.ProgramLanguageId);
if (coreProgramLanguage == null)
{
    return false;
}
var detachedCore = GeneralMigrate.ProgramLanguageDmToEntity(GeneralMigrate.ProgramLanguageEntityToDm(coreProgramLanguage));
var newCore = GeneralMigrate.ProgramLanguageDmToEntity(programLanguage);

newCore.RecordDate = detachedCore.RecordDate;
newCore.TestId = detachedCore.TestId;

return iUnitOfWork.CommonHandler.UpdateProgramLanguage(newCore);
  

… может быть значительно упрощен. Я переименовал несколько переменных для ясности.

 var existingProgramLanguage = iUnitOfWork.CommonHandler.GetProgramLanguageById(programLanguage.ProgramLanguageId);
if (coreProgramLanguage == null)
    return false;

existingProgramLanguage.RecordDate = programLanguage.RecordDate;

return iUnitOfWork.CommonHandler.UpdateProgramLanguage(existingProgramLanguage);

// assuming UpdateProgramLanguage returns Boolean? if it returns entity then convert to data model.
  

вместо того, чтобы читать «отсоединенную» существующую запись и преобразовывать ее в DM и преобразовывать переданный DM в новый объект, мы загружаем существующую запись и копируем в нее разрешенные значения из нашего переданного DM и сохраняем его.