实体框架在分配给嵌套列表成员时复制查找值

时间:2014-02-28 14:52:58

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

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

  • Client个实例可以在Competency成员中拥有零个或多个List<Competency>个实例。
  • Competency个实例可以在WeaponType成员中拥有一个或多个List<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提出的问题):

我有以下内容:

客户端 客户&gt;&gt;列出能力证书 CompetencyCertificate&gt;&gt;列出能力类型

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

随后,我调用以下代码:

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

dbContext.SaveChanges();

以下是在UpdateGraph调用上抛出异常的堆栈跟踪:

  

会员&#39; CurrentValues&#39;不能为类型的实体调用   &#39; CompetencyCertificate&#39;因为实体不存在于   上下文。要将实体添加到上下文,请调用Add或Attach方法   DbSet。

     

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

     

异常详细信息:System.InvalidOperationException:Member   &#39; CurrentValues&#39;不能为类型的实体调用   &#39; CompetencyCertificate&#39;因为实体不存在于   上下文。要将实体添加到上下文,请调用Add或Attach方法   DbSet。

     

来源错误:

     

第138行:第139行://更新已分离实体的图形   第140行:dbContext.UpdateGraph(client,map =&gt; map Line   141:.OwnedCollection(cc =&gt; cc.CompetencyCertificates,   with =&gt;第142行:.AssociatedCollection(kt =&gt;   kt.CompetencyTypes))

     

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

     

堆栈追踪:

     

[InvalidOperationException:Member&#39; CurrentValues&#39;不能叫   对于类型&#39; CompetencyCertificate&#39;的实体因为实体的确如此   在上下文中不存在。要将实体添加到上下文,请调用Add   或附加DbSet的方法。]
  System.Data.Entity.Internal.InternalEntityEntry.ValidateNotDetachedAndInitializeRelatedEnd(字符串   方法)+102
  System.Data.Entity.Internal.InternalEntityEntry.ValidateStateToGetValues(字符串   方法,EntityState invalidState)+55
  System.Data.Entity.Internal.InternalEntityEntry.get_CurrentValues()   +53 System.Data.Entity.Infrastructure.DbEntityEntry.get_CurrentValues()   +44 RefactorThis.GraphDiff.DbContextExtensions.RecursiveGraphUpdate(DbContext   context,Object dataStoreEntity,Object updatingEntity,UpdateMember   会员)+942
  RefactorThis.GraphDiff.DbContextExtensions.UpdateGraph(的DbContext   context,T entity,Expression 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,IDictionary 2 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.WrappedAsyncResultBase 1.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.WrappedAsyncResultBase 1.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.WrappedAsyncResultBase 1.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.WrappedAsyncResultBase 1.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.WrappedAsyncResultBase 1.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&amp; completedSynchronously)+155

2 个答案:

答案 0 :(得分:1)

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

从我原来的问题中列出的代码中,我从JSON反序列化到Enities,从而基本上创建了一个断开连接的图形(因为图形没有从数据库加载,因此没有在实体上进行跟踪。

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

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

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

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

答案 1 :(得分:0)

问题出在

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

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

此状态更改会导致对象图中的所有 Detached个对象也标记为Added。所以在这一点上,所有WeaponType都是Added,并且如果你不对它做任何事情,它将被保存为新对象。

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

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