【问题标题】:Entity Framework duplicates lookup values when assigned to nested list member实体框架在分配给嵌套列表成员时重复查找值
【发布时间】:2014-04-01 15:33:36
【问题描述】:

此解决方案涉及三个实体,ClientCompetencyWeaponType

  • Client 实例可以在 List<Competency> 成员中拥有零个或多个 Competency 实例。
  • Competency 实例可以在List<WeaponType> 成员中拥有一个或多个WeaponType 实例。 (WeaponType 是我们的查找成员)

在更新 DbContext 之前,会为客户端分配一个新的 List 对象。这代表了客户能力的完整更新列表,其中旧能力可能已被删除并创建新能力。

遇到的问题是 dbContext.SaveChanges() 会导致创建重复的 WeaponType 条目。

这是我的实体的代码:

public class Client : Person
    {
        public ICollection<CompetencyCertificate> CompetencyCertificates
        {
            get;
            set;
        }

    }

public class CompetencyCertificate
    {


        public Int64 Id { get; set; }

        [Required]
        public string CertificateNumber { get; set; }

        [Required]
        public List<WeaponType> CompetencyTypes { get; set; }    

    }



 public class WeaponType
    {
        public Int16 Id { get; set; }

        [Required]
        public string Name { get; set; }

    }

以及用于保存我更新的客户和能力信息的代码(这也反映了我克服这个问题的尝试:

 private void SaveClientProfile()
        {
            HttpRequestBase rb = this.Request;

            string sId = "";
            if (rb.Form["Id"] != null)
                sId = rb.Form["Id"];
            Int64 int64_id = 0;
            if (sId.Trim().Length > 0)
                int64_id = Int64.Parse(sId);
            Client client = loadOrCreateClient(int64_id);

            //Set the newly submitted form data for the client

            client.IDSocialSecurityPassNum = rb.Form["IDNumber"];
            client.EmailAddress = rb.Form["EmailAddress"];
            client.NickName = rb.Form["Name"];
            client.Surname = rb.Form["Surname"];

            //MAP AND TRANSLATE JSON COLLECTION OBJECTS TO ENTITY COLLECTIONS, UPDATE THE CONTEXT    

            Mapper.CreateMap<Client_Competency_ViewModel, CompetencyCertificate>();
            client.CompetencyCertificates = Mapper.Map<List<CompetencyCertificate>>(System.Web.Helpers.Json.Decode<System.Collections.Generic.List<Client_Competency_ViewModel>>(rb.Form["CompetencyCollection"]));    

            //PREVENT EF FROM DUPLICATING LOOKUP VALUES
            AttachLookup<WeaponType>(JCGunsDb.WeaponTypes.ToList<WeaponType>());


            //FNIALISE AND SAVE
            dbContext.UserId = User.Identity.GetUserName();
            dbContext.SaveChanges();
        }



private void AttachLookup<T>(ICollection<T> itemsToAttach) where T : class
        {
            foreach(T item in itemsToAttach)
            {
                JCGunsDb.Entry(item).State = EntityState.Unchanged;
            }
        }

我可以确认上述代码中的 JSON 解析和映射按预期工作 - 现有实体的 Id 完好无损,新实体 Id 设置为 0。

我在做什么导致这种行为?我该如何解决?


更新: 根据 Gert 的建议,我尝试使用 GraphDiff 实现一个解决方案(这似乎完全符合我的要求)。但是,我正在努力让它发挥作用。这是我所做的(根据提出的 Github 问题):

我有以下几点:

客户 客户 >> 列出能力证书 CompetencyCertificate >> 列出 CompetencyTypes

我从数据库中加载一个客户端对象,然后将新的 List 值分配给上面提到的 List 成员。

随后,我调用以下代码:

dbContext.UpdateGraph(client, map => map
                .OwnedCollection(cc => cc.CompetencyCertificates, with => with
                    .AssociatedCollection(kt => kt.CompetencyTypes))
                );

dbContext.SaveChanges();

以下是在 UpdateGraph 调用中引发的异常的堆栈跟踪:

无法为类型实体调用成员“CurrentValues” 'CompetencyCertificate',因为实体不存在于 语境。要将实体添加到上下文中,请调用 Add 或 Attach 方法 数据库集。

描述:执行过程中发生了未处理的异常 当前的网络请求。请查看堆栈跟踪以获取更多信息 有关错误的信息以及它在代码中的来源。

异常详细信息:System.InvalidOperationException:成员 不能为类型的实体调用“CurrentValues” 'CompetencyCertificate',因为实体不存在于 语境。要将实体添加到上下文中,请调用 Add 或 Attach 方法 数据库集。

来源错误:

第 138 行:第 139 行://更新分离实体的图表 第 140 行:dbContext.UpdateGraph(client, map => map Line 141:.OwnedCollection(cc => cc.CompetencyCertificates, 使用 => 使用第 142 行:.AssociatedCollection(kt => kt.CompetencyTypes))

源文件:[不重要]行:140

堆栈跟踪:

[InvalidOperationException:无法调用成员“CurrentValues” 对于“CompetencyCertificate”类型的实体,因为该实体确实 上下文中不存在。要将实体添加到上下文中,请调用 Add 或 DbSet 的 Attach 方法。]
System.Data.Entity.Internal.InternalEntityEntry.ValidateNotDetachedAndInitializeRelatedEnd(字符串 方法)+102
System.Data.Entity.Internal.InternalEntityEntry.ValidateStateToGetValues(字符串 方法,EntityState 无效状态)+55
System.Data.Entity.Internal.InternalEntityEntry.get_CurrentValues() +53 System.Data.Entity.Infrastructure.DbEntityEntry.get_CurrentValues() +44 RefactorThis.GraphDiff.DbContextExtensions.RecursiveGraphUpdate(DbContext 上下文、对象数据存储实体、对象更新实体、更新成员 会员)+942
RefactorThis.GraphDiff.DbContextExtensions.UpdateGraph(DbContext 上下文,T 实体,表达式1 mapping) +631
JCGunsOnline.Controllers.ClientController.SaveClientProfile() in c:\Users\Ben\Dropbox\Mighty IT\Active Projects\JCGunsOnline\JCGunsOnline\Views\Client\ClientController.cs:140 JCGunsOnline.Controllers.ClientController.SubmitStep1() in c:\Users\Ben\Dropbox\Mighty IT\Active Projects\JCGunsOnline\JCGunsOnline\Views\Client\ClientController.cs:60 lambda_method(Closure , ControllerBase , Object[] ) +101
System.Web.Mvc.ActionMethodDispatcher.Execute(ControllerBase controller, Object[] parameters) +59
System.Web.Mvc.ReflectedActionDescriptor.Execute(ControllerContext controllerContext, IDictionary
2 个参数)+435
System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary2 parameters) +60
System.Web.Mvc.Async.ActionInvocation.InvokeSynchronousActionMethod() +76 System.Web.Mvc.Async.AsyncControllerActionInvoker.<BeginInvokeSynchronousActionMethod>b__39(IAsyncResult asyncResult, ActionInvocation innerInvokeState) +36
System.Web.Mvc.Async.WrappedAsyncResult
2.CallEndDelegate(IAsyncResult asyncResult) +73
System.Web.Mvc.Async.WrappedAsyncResultBase1.End() +136
System.Web.Mvc.Async.AsyncResultWrapper.End(IAsyncResult asyncResult, Object tag) +102
System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeActionMethod(IAsyncResult asyncResult) +49
System.Web.Mvc.Async.AsyncInvocationWithFilters.<InvokeActionMethodFilterAsynchronouslyRecursive>b__3f() +117 System.Web.Mvc.Async.<>c__DisplayClass48.<InvokeActionMethodFilterAsynchronouslyRecursive>b__41() +323 System.Web.Mvc.Async.<>c__DisplayClass33.<BeginInvokeActionMethodWithFilters>b__32(IAsyncResult asyncResult) +44
System.Web.Mvc.Async.WrappedAsyncResult
1.CallEndDelegate(IAsyncResult asyncResult) +47
System.Web.Mvc.Async.WrappedAsyncResultBase1.End() +136
System.Web.Mvc.Async.AsyncResultWrapper.End(IAsyncResult asyncResult, Object tag) +102
System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeActionMethodWithFilters(IAsyncResult asyncResult) +50
System.Web.Mvc.Async.<>c__DisplayClass2b.<BeginInvokeAction>b__1c() +72 System.Web.Mvc.Async.<>c__DisplayClass21.<BeginInvokeAction>b__1e(IAsyncResult asyncResult) +185
System.Web.Mvc.Async.WrappedAsyncResult
1.CallEndDelegate(IAsyncResult asyncResult) +42
System.Web.Mvc.Async.WrappedAsyncResultBase1.End() +133
System.Web.Mvc.Async.AsyncResultWrapper.End(IAsyncResult asyncResult, Object tag) +56
System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeAction(IAsyncResult asyncResult) +40
System.Web.Mvc.Controller.<BeginExecuteCore>b__1d(IAsyncResult asyncResult, ExecuteCoreState innerState) +34
System.Web.Mvc.Async.WrappedAsyncVoid
1.CallEndDelegate(IAsyncResult asyncResult) +70
System.Web.Mvc.Async.WrappedAsyncResultBase1.End() +139
System.Web.Mvc.Async.AsyncResultWrapper.End(IAsyncResult asyncResult, Object tag) +59
System.Web.Mvc.Async.AsyncResultWrapper.End(IAsyncResult asyncResult, Object tag) +40
System.Web.Mvc.Controller.EndExecuteCore(IAsyncResult asyncResult) +44 System.Web.Mvc.Controller.<BeginExecute>b__15(IAsyncResult asyncResult, Controller controller) +39
System.Web.Mvc.Async.WrappedAsyncVoid
1.CallEndDelegate(IAsyncResult asyncResult) +62
System.Web.Mvc.Async.WrappedAsyncResultBase1.End() +139
System.Web.Mvc.Async.AsyncResultWrapper.End(IAsyncResult asyncResult, Object tag) +59
System.Web.Mvc.Async.AsyncResultWrapper.End(IAsyncResult asyncResult, Object tag) +40 System.Web.Mvc.Controller.EndExecute(IAsyncResult asyncResult) +39
System.Web.Mvc.Controller.System.Web.Mvc.Async.IAsyncController.EndExecute(IAsyncResult asyncResult) +39
System.Web.Mvc.MvcHandler.<BeginProcessRequest>b__5(IAsyncResult asyncResult, ProcessRequestState innerState) +39
System.Web.Mvc.Async.WrappedAsyncVoid
1.CallEndDelegate(IAsyncResult asyncResult) +70
System.Web.Mvc.Async.WrappedAsyncResultBase`1.End() +139
System.Web.Mvc.Async.AsyncResultWrapper.End(IAsyncResult asyncResult, 对象标签)+59
System.Web.Mvc.Async.AsyncResultWrapper.End(IAsyncResult asyncResult, 对象标签)+40
System.Web.Mvc.MvcHandler.EndProcessRequest(IAsyncResult asyncResult) +40 System.Web.Mvc.MvcHandler.System.Web.IHttpAsyncHandler.EndProcessRequest(IAsyncResult 结果)+38
System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +9514928 System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +155

【问题讨论】:

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


    【解决方案1】:

    问题出在一行

    client.CompetencyCertificates = Mapper.Map<....
    

    集合中的所有CompetencyCertificates 在反序列化时都作为未附加的对象开始。当您将反序列化集合分配给CompetencyCertificates 时,所有CompetencyCertificate 对象都会从Detached 更改为Added

    此状态更改会导致对象图中的所有 Detached 对象也被标记为Added。所以此时,所有WeaponTypes 都是Added,如果你不做任何事情,它们将被保存为新对象。

    如果您确定所有WeaponType 对象将始终是现有对象,我认为最快的解决方法是遍历所有新的CompetencyCertificate 对象并将它们的WeaponTypes 标记为Unchanged

    这可能是您在 AttachLookup 中尝试做的事情,但在我看来,那里涉及完全不同的上下文,因此 dbContext 的更改跟踪器从未参与其中。

    【讨论】:

    • 这符合我的推理。然而,这并不是人们对基于“智能”约定的 ORM 所期望的。 “智能”ORM 会根据它们的 PK 与现有对象的 PK 对应的事实来检测正在分配的一些对象是否存在,并且只会一致地更新数据库中的所述记录。新对象的主键为零,当然删除的对象不会出现在列表中。所以我特别在寻找一种解决方案,可以帮助我避免遍历每个集合来手动设置每个条目的状态。
    • 如果您所描述的实际上是 EF 的“预期”行为,我将在未来引导我的公司远离 EF,因为我们需要更长的时间来实施一项真正实现数据访问的技术通过增加额外的复杂层来增加难度。
    • 这很麻烦。我尝试编写一个方法来将实体上的列表属性与新返回的列表进行比较,并从实体列表中添加、更新或删除单个项目。事实证明,您不能通过引用传递属性值,因此您不能将其抽象为方法。
    • 我只是个信使 :) 我只能说 EF 正在发展。每个版本都让它变得更好。它的 LINQ 支持是无与伦比的,这就是吸引我的地方。其余的我试着忍受它尖尖的边缘。让你烦恼的事情显然还没有出现在团队的优先事项列表中。有一个库可以填补空白:GraphDiff。将来类似的东西不太可能成为 EF 的一部分。
    • 没关系 :) 我自己还没有使用 GraphDiff,但是阅读了一些关于它的积极用户体验。希望它能让你走得更远。 Here 是一位快乐的用户。
    【解决方案2】:

    所以我设法解决了这个问题,因此我在这里对其进行总结以供将来参考。

    从我原始问题中列出的代码中,我将 JSON 反序列化为实体,从而基本上创建了一个断开连接的图(因为该图没有从数据库中加载,因此实体上没有发生跟踪。

    Entity Framework 6(及更早版本)不支持使用断开连接的图。 (见https://entityframework.codeplex.com/workitem/864

    正如上面提到的@Gert Arnold,有一个名为 GraphDiff 的插件组件确实支持它。 (可以从https://github.com/refactorthis/GraphDiff下载)。

    我强烈建议您从源代码构建代码并且不要使用 Nuget 包,因为当我使用它时它已经过时并且随后运行了一系列已经在最新版本中修复的错误版本。

    最后,请记住 GraphDiff 尚不支持使用连接图/跟踪实体,因此您必须在为断开连接图加载数据时调用 .AsNoTracking() 方法。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-04-01
      • 1970-01-01
      • 1970-01-01
      • 2018-03-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-05-13
      相关资源
      最近更新 更多