此解决方案涉及三个实体Client
,Competency
和WeaponType
。
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,Expression1 mapping) +631
2参数)+435
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
System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(ControllerContext controllerContext,ActionDescriptor actionDescriptor,IDictionary2 parameters) +60
2.CallEndDelegate(IAsyncResult) asyncResult)+73
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
System.Web.Mvc.Async.WrappedAsyncResultBase1.End() +136
1.CallEndDelegate(IAsyncResult的 asyncResult)+47
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
System.Web.Mvc.Async.WrappedAsyncResultBase1.End() +136
1.CallEndDelegate(IAsyncResult的 asyncResult)+42
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
System.Web.Mvc.Async.WrappedAsyncResultBase1.End() +133
1.CallEndDelegate(IAsyncResult的 asyncResult)+70
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
System.Web.Mvc.Async.WrappedAsyncResultBase1.End() +139
1.CallEndDelegate(IAsyncResult的 asyncResult)+62
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
System.Web.Mvc.Async.WrappedAsyncResultBase1.End() +139
1.CallEndDelegate(IAsyncResult的 asyncResult)+70
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
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
答案 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
的更改跟踪器永远不会参与其中。