oguzhn
oguzhn

Reputation: 27

Why I get this exception "Attaching an entity of type 'Model' failed because another entity of the same type already has the same primary key value."

An exception was thrown when I was trying to update an entry in the database. First I get the entry from the db in order to retrieve record date of the entry. Then I migrate dbmodel to datamodel, then migrate it back. But it did not work.

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.

This is my 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; }
    }

This is my data model

[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; }

    }

This is the part of code that interacts with db context.

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

This is the business layer that talks to the db module.

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);
            }
        }

This is the part of code when I am migrating model to 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();
        }

Upvotes: 1

Views: 88

Answers (2)

Steve Py
Steve Py

Reputation: 34968

PiGi78 nailed the issue: You are reading and converting between data model and entity excessively.

The only time you should be converting Dm to Entity would be when creating a new entity. When updating an entity we can load the entity, copy the values across from the passed model and save the entity. (No need to convert things back and forth)

This code:

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);

... can be simplified considerably. I've renamed a few of the variables for clarity.

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.

rather than reading the "detached" existing record and converting it to a DM and converting the passed DM into a new entity, we load the existing record and copy the allowed values from our passed DM into it and save it.

Upvotes: 1

PiGi78
PiGi78

Reputation: 435

You have this error because you're trying to attach/add an object to a database context when the context already knows an entity with the same Id.

Let me do a simple example for explain it in a easier way.

We have those model and 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; }
}

Not a lot of properties: an Id (primary key) and a description.

Now, I do what you do in yor code:

        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);
        }

Looking at all steps:

  1. We read data from the database (SingleOrDefault). Entity Framework gives us an object (model) and saves in its memory the data of that object
  2. We convert the model into the DTO (variable called dto)
  3. We convert back the DTO into a model. But we create a new instance of the model (variable anotherModel)
  4. We try to add the new model into the Db context.

When we do the 4th step, the application throws an exception, since the DbContext already know a model with Id = 1 (variable model) and you can't add another one with the same id (anotherModel).

There are two options:

  1. When we read data, instead of db.MyModels.SingleOrDefault, we can use the sintax db.MyModels.AsNoTracking().SingleOrDefault... With the "AsNoTracking" we are saying to the db context to NOT save the model in its memory (tracking)
  2. When we back object from DTO to model, we have to change the original model (variable model) instead of create a new one (anotherModel). Doing that, the db context will see our changes and will replicate them in the database

Looking at your code, I suggest to use the first options. It will be easyer and, since it hasn't to take care of the tracking, the query will be faster.

I hope I helped

Upvotes: 3

Related Questions