.NET - 附加类型的实体失败,因为同一类型的另一个实体已具有相同的主键值

时间:2018-02-24 22:08:32

标签: c# asp.net linq entity-framework-6

我知道有几个问题提出了同样的事情,但似乎没有一个对我有所帮助。我正在尝试做一个.RemoveRange(),我看到的每个问题都与编辑和添加有关。

以下是抛出异常的方法的相关位:

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,我还将包含RemoveNullFileboundApplicationMappings的代码:

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

最后,这是inf_DMS_FBApplicationProjectMapping类:

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

}

异常内容如下:

  

{“附加类型为'xxxx'的实体失败,因为同一类型的另一个实体已经具有相同的主键值。当使用'Attach'方法或将实体的状态设置为'Unchanged'时,可能会发生这种情况。如果图中的任何实体具有冲突的键值,则为“已修改”。这可能是因为某些实体是新的并且尚未接收数据库生成的键值。   在这种情况下,使用“添加”方法或“已添加”实体状态来跟踪图表,然后根据需要将非新实体的状态设置为“未更改”或“已修改”。“}

我不太明白我需要如何使用Db.inf _..... Add(),因为我不打算在表中添加记录;我需要删除记录。

我不明白这种“附加到背景”是什么以及它的真正含义是什么。

我非常感谢社区对此有任何见解。试图找到解决这个问题的方法一直很困难。谢谢!

2 个答案:

答案 0 :(得分:1)

我想问题出现在new,您用来将作为参数传递的列表组成RemoveRange。由于该列表中的实体尚未直接从您的DbSet查询,因此它们从未被附加到您的本地环境中,因此EF会感到困惑。

您需要了解附加到上下文的实体的概念。实体框架会跟踪对您正在使用的实体所做的更改,以便能够在执行SaveChanges时决定要执行的操作:插入,更新,删除。如果实体附加到上下文,EF只能执行此操作。这意味着他们的属性State的值为AddedDeletedModifiedUnchanged等。

在简单的场景中,这对您来说是透明的,因为当您执行DbSet.Add(entity)DbSet.Find(entityId)或者作为查询结果获得实体实例时,实体会自动附加,例如{{1 }},DbSet.Where(...)等。这就是为什么你可能永远不必担心EF代码中的附加实体。

在更复杂的场景(如当前场景)中,您尝试删除的实体尚未从其中一个操作中实例化,因此它们尚未自动附加到您的上下文中。如果使用DbSet.FirstOrDefault(...)实例化它们,则必须明确地执行此操作。

所以你应该在new

之前做这样的事情
SaveChanges

通过使用方法foreach(var item in allAppsToRemove) { Db.Entry(item).State = EntityState.Deleted; } ,实体将附加到上下文,然后您明确地将其状态设置为Entry,以便在以后执行Deleted时删除它们。

看看this page。即使它主要处理添加和更新案例,它也包含与删除问题相关的信息。在使用EF进行编程时,了解附加到本地SaveChanges的实体的概念将对您有所帮助。在某些情况下,如果您不知道附加实体是如何工作的,那么您将遇到麻烦(您最终也会遇到一些'孤儿'错误)。

注意:在Entity Framework Core(EF7)中,可以在DbContext之前使用AttachRange方法。

答案 1 :(得分:0)

Diana's的帮助下,我能够解决这个问题。

问题是我手动翻转实体状态并调用.RemoveRange()。我只需要翻转实体状态。这是解决问题的相关部分:

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

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

                //Db.inf_DMS_FBApplicationProjectMapping.RemoveRange(allAppsToRemove); <-- these items are already "flagged for deletion" with .State property change a few lines above. 
            }