.NET - Attaching an entity of type failed because another entity of the same type already has the same primary key value

asp.net c# entity-framework-6 linq

Question

I know there are several questions posed about this very same thing but none of which seems to help me. I'm trying to do a .RemoveRange() and every question I've been seeing has to do with edits and adds.

Here's the relevant bits of the method in which the exception is getting thrown:

public bool UpdateFileboundApplications(IList<IFileboundApplicationDm> fileboundApplications)
    {
        // get all mappings in the DB that match the incoming fileboundApplications
        var incomingFbAppsAlreadyExistingInDb =
            fileboundApplications.Where(app => app.Id == Db.inf_DMS_FBApplicationProjectMapping.SingleOrDefault(a => a.ApplicationId == app.Id)?.ApplicationId
                                               && app.FileboundProject != null).ToList();

        // in the case that application/project mappings include filebound applications with no project mapping,
        // pass the collection to a method which will handle removal of these records.
        var fbAppMappingsWithoutNulls = RemoveNullFileboundApplicationMappings(incomingFbAppsAlreadyExistingInDb, fileboundApplications);
        var fbAppMappingsAppIdsAndProjectIds = fbAppMappingsWithoutNulls.Select(x => new { appId = x.Id, projectId = x.FileboundProject.Id}).ToList();
        var dbRecords = Db.inf_DMS_FBApplicationProjectMapping.Select(y => new { appId = y.ApplicationId, projectId = y.ProjectID}).ToList();

        var fbApplicationDifferences =
            dbRecords.FindDifferences(fbAppMappingsAppIdsAndProjectIds,
            s => new Tuple<int, int>(s.appId, s.projectId),
            d => new Tuple<int, int>(d.appId, d.projectId));

        if (fbApplicationDifferences.ExistOnlyInSource.Any())
        {
            // items to remove from the table, as these apps are now assigned to a different project.
            var allAppsToRemove = fbApplicationDifferences.ExistOnlyInSource.Select(x => new inf_DMS_FBApplicationProjectMapping
                                                                                         {
                                                                                             ApplicationId = x.appId,
                                                                                             ProjectID = x.projectId,
                                                                                             MapId = Db.inf_DMS_FBApplicationProjectMapping.Single(m => m.ApplicationId == x.appId).MapId
                                                                                         }).ToList();

            Db.inf_DMS_FBApplicationProjectMapping.RemoveRange(allAppsToRemove);

        }

        Db.SaveChanges();
        return true;
    }

FWIW, I'll include the code for the RemoveNullFileboundApplicationMappings as well:

        private IEnumerable<IFileboundApplicationDm> RemoveNullFileboundApplicationMappings(IEnumerable<IFileboundApplicationDm> incomingFbAppsAlreadyExistingInDb,
                                                        IEnumerable<IFileboundApplicationDm> fileboundApplications)
    {
        // hold a collection of incoming fileboundApplication IDs for apps that have no associated fileboundProject
        var appIdsWithNoFbProject = fileboundApplications.Except(incomingFbAppsAlreadyExistingInDb)
                                                         .Select(app => app.Id);

        // get records in the table that now need to be removed
        var dbRecordsWithMatchingIds = Db.inf_DMS_FBApplicationProjectMapping.Where(mapping => appIdsWithNoFbProject.Contains(mapping.ApplicationId));

        if (dbRecordsWithMatchingIds.Any())
        {
            // remove records for apps that no will no longer have an associated Filebound project
            Db.inf_DMS_FBApplicationProjectMapping.RemoveRange(dbRecordsWithMatchingIds);
            Db.SaveChanges();
        }

        return fileboundApplications.Where(app => app.FileboundProject != null);
    }

Finally, here's the inf_DMS_FBApplicationProjectMapping class:

    public partial class inf_DMS_FBApplicationProjectMapping
{
    public int MapId { get; set; } // <-- this is the PK
    public int ApplicationId { get; set; } 
    public int ProjectID { get; set; }
    public Nullable<int> Modified_By { get; set; }
    public Nullable<System.DateTime> Modified_On { get; set; }

    public virtual glb_Applications glb_Applications { get; set; }
}

}

Exception reads as follows:

{"Attaching an entity of type 'xxxx' 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."}

I don't quite understand how I need to be using Db.inf_.....Add(), as I'm not intending to add records to the table; I need to be removing records.

I don't understand what this "attaching to context" is all about and what that really means.

I really appreciate any insight the community may have on this. It's been a struggle trying to find a way to solve this. Thanks!

1
1
2/24/2018 10:10:16 PM

Popular Answer

I guess the problem is in the new that you use to compose the list you pass as parameter to RemoveRange. As the entities in that list have not been queried directly from your DbSet they have never been attached to your local context and so EF gets confused.

You need to understand the concept of entities attached to the context. Entity Framework keeps track of the changes done to entities you are working with, in order to be able to decide what to do when you do SaveChanges: insert, update, delete. EF is only able to do that if the entities are attached to the context. That means they have a property State with the value Added, Deleted, Modified, Unchanged, etc.

In simple scenarios this is transparent to you, because entities get automatically attached when you do DbSet.Add(entity), DbSet.Find(entityId), or when you get an entity instance as a result of a query, like DbSet.Where(...), DbSet.FirstOrDefault(...), etc. That is why you probably never had to worry about attached entities before in your EF code.

In more complex scenarios like your current one, the entities you are trying to delete have not been instantiated from one of those operations, so they have not been automatically attached to your context. You have to do it explicitly, if you instantiate them with new.

So you should do something like this before the SaveChanges:

foreach(var item in allAppsToRemove)
{
    Db.Entry(item).State = EntityState.Deleted;
}

By using the method Entry the entities get attached to the context, and then you explicity set their state as Deleted, to have them deleted when SaveChanges is executed later.

Take a look at this page. Even if it deals mostly with Add and Update cases it contains information relevant to your problem with the Delete. Understanding the concept of entities attached to the local DbContext will help you a lot when programming with EF. There are some cases like this one where you will have trouble if you don't know how attached entities work (you will eventually get to some 'orphaned children' errors also).

Note: in Entity Framework Core (EF7) there is an AttachRange method that can be used before RemoveRange.

1
2/25/2018 12:21:25 AM


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow