Entity Framework duplicates search values โ€‹โ€‹when assigned to a nested list item

This solution includes three objects, Client , Competency and WeaponType .

  • A Client instance can have Competency instances with a number less than or greater than Competency .
  • A Competency instance can have one or more WeaponType instances inside the List<WeaponType> member. ( WeaponType is our search item)

Before updating DbContext, a new List object is assigned to the client. This is a complete updated list of competencies for the client, where old competencies can be deleted and new ones created.

The problem is that dbContext.SaveChanges () causes the creation of duplicate weapon entries.

Here is the code for my entities:

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

And at the same time, the code to save my updated client information and competence (which also reflects my attempts to overcome this problem:

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

I can confirm that the analysis and JSON mapping in the above code works as expected - the identifier for existing objects is in tact, and the new object identifier is set to 0.

What am I doing is causing this behavior? How to fix it?


UPDATE: As Gert recommended, I tried to implement a solution using GraphDiff (which seems to fit my requirements). However, I'm struggling to get it to work. Here is what I did (according to the Github question):

I have the following:

Client Client โ†’ Competency ListCertificate Certificates โ†’ CompetencyTypes List

I load the client object from the database and then assign new List values โ€‹โ€‹to the above list items.

Subsequently, I call the following code:

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

Here is the stacktrace for the exception that throws the UpdateGraph:

The CurrentValues โ€‹โ€‹member cannot be called on an object of type CompetencyCertificate because the entity does not exist in context. To add an object to the context call, add the Add or Attach DbSet method.

Description: An unhandled exception occurred during the execution of the current web request. View the stack trace for error information and where it originated in the code.

Exception Details: System.InvalidOperationException: Member "CurrentValues" cannot be called on an object of type "CompetencyCertificate" because the entity does not exist in context. To add an object to the context call, add the Add or Attach DbSet method.

Source Error:

Line 138: Line 139: // UPDATE GRAPH OF INDIVIDUALS Line 140: dbContext.UpdateGraph (client, map => map Line 141: .OwnedCollection (cc => cc.CompetencyCertificates, c => with line 142: .AssociatedCollection (kt = > kt.CompetencyTypes))

Source file: [Not important] Line: 140

Stack trace:

[InvalidOperationException: Member 'CurrentValues' cannot be called for a person of type "CompetencyCertificate" because the entity does not exist in context. To add an object to the context call, add or Bind the DbSet method.]
System.Data.Entity.Internal.InternalEntityEntry.ValidateNotDetachedAndInitializeRelatedEnd (String method) +102
System.Data.Entity.Internal.InternalEntityEntry.ValidateStateToGetValues โ€‹โ€‹(String method, EntityState invalidState) +55
System.Data.Entity.Internal.InternalEntityEntry.get_CurrentValues โ€‹โ€‹() +53 System.Data.Entity.Infrastructure.DbEntityEntry.get_CurrentValues โ€‹โ€‹() +44 RefactorThis.GraphDiff.DbContextExtensions.RecursiveGraphUpdatentb object , UpdateMember member) +942
RefactorThis.GraphDiff.DbContextExtensions.UpdateGraph (DbContext context, T object, 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
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
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 parameters) +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 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 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.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.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.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.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.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.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.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.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.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.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, Object tag) +59
System.Web.Mvc.Async.AsyncResultWrapper.End (IAsyncResult asyncResult, Object tag) +40
System.Web.Mvc.MvcHandler.EndProcessRequest (IAsyncResult asyncResult) +40 System.Web.Mvc.MvcHandler.System.Web.IHttpAsyncHandler.EndProcessRequest (IAsyncResult result) +38
System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute () +9514928 System.Web.HttpApplication.ExecuteStep (step IExecutionStep, Boolean & completed synchronously) +155

+2
c # asp.net-mvc orm entity-framework-6
source share
2 answers

Therefore, I managed to solve the problem and, as such, summarize it here for future reference.

From the code indicated in my initial question, I de-serialize from JSON to Enities, thereby basically creating a disconnected graph (because the graph was not loaded from the database, and therefore there were no traces on the entity.

Entity Framework 6 (and earlier) does not support work with disabled charts. (See https://entityframework.codeplex.com/workitem/864 )

As @Gert Arnold mentioned above, there is a GraphDiff plugin that supports it. (You can download it from https://github.com/refactorthis/GraphDiff ).

I highly recommend that you create the code from the source code and not use the Nuget package, since it was deprecated when I used it, and then ran into a battery of errors that were already fixed in the latest version.

Finally, keep in mind that GraphDiff does not yet support working with linked graphs / tracked objects, so you must call the .AsNoTracking () method when loading data for your disabled graph.

+1
source share

The problem is the line

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

All CompetencyCertificates at the beginning of the collection are unattached objects when they are deserialized. When you assign a deserialized collection of CompetencyCertificates , all CompetencyCertificate objects change from Detached to Added .

This change of state means that all Detached objects in the object graph are also marked as Added . So, at the moment, all WeaponType are Added and will be saved as new objects if you do nothing.

If you know for sure that all WeaponType objects WeaponType always be existing objects, I think the quickest solution would be to WeaponType through all the new CompetencyCertificate objects and mark their WeaponType as Unchanged .

This is probably what you are trying to do in AttachLookup , but it seems to me that a completely different context is involved here, so the dbContext change tracker is never involved in this.

0
source share

All Articles