为什么这个实体框架崩溃有时发生在我的WPF应用程序



我管理一个公开可用的WPF应用程序,它使用实体框架6.1(模型优先的ObjectContext)和SQLite数据库。

我的应用程序的头号崩溃日志(根据HockeyApp)如下(与指针指向我的应用程序的代码标记****):

System.InvalidOperationException: The property 'Id' is part of the object's key information and cannot be modified. 
   at System.Data.Entity.Core.Objects.EntityEntry.VerifyEntityValueIsEditable(StateManagerTypeMetadata typeMetadata, Int32 ordinal, String memberName)
   at System.Data.Entity.Core.Objects.EntityEntry.GetAndValidateChangeMemberInfo(String entityMemberName, Object complexObject, String complexObjectMemberName, StateManagerTypeMetadata& typeMetadata, String& changingMemberName, Object& changingObject)
   at System.Data.Entity.Core.Objects.EntityEntry.EntityMemberChanging(String entityMemberName, Object complexObject, String complexObjectMemberName)
   at System.Data.Entity.Core.Objects.EntityEntry.EntityMemberChanging(String entityMemberName)
   at System.Data.Entity.Core.Objects.ObjectStateEntry.System.Data.Entity.Core.Objects.DataClasses.IEntityChangeTracker.EntityMemberChanging(String entityMemberName)
   at System.Data.Entity.Core.Objects.DataClasses.EntityObject.ReportPropertyChanging(String property)
   ****
   at MyNamespace.MyApp.MyEntity.set_Id(Int64 value) in C:MyPathMyAppEfGeneratedEntities.cs
   ****
   at lambda_method(Closure , Object , Object )
   at System.Data.Entity.Core.Objects.DelegateFactory.SetValue(EdmProperty property, Object target, Object value)
   at System.Data.Entity.Core.Objects.StateManagerMemberMetadata.SetValue(Object userObject, Object value)
   at System.Data.Entity.Core.Objects.Internal.LightweightEntityWrapper`1.SetCurrentValue(EntityEntry entry, StateManagerMemberMetadata member, Int32 ordinal, Object target, Object value)
   at System.Data.Entity.Core.Objects.EntityEntry.SetCurrentEntityValue(StateManagerTypeMetadata metadata, Int32 ordinal, Object userObject, Object newValue)
   at System.Data.Entity.Core.Objects.ObjectStateEntryDbUpdatableDataRecord.SetRecordValue(Int32 ordinal, Object value)
   at System.Data.Entity.Core.Mapping.Update.Internal.PropagatorResult.SetServerGenValue(Object value)
   at System.Data.Entity.Core.Mapping.Update.Internal.UpdateTranslator.BackPropagateServerGen(List`1 generatedValues)
   at System.Data.Entity.Core.Mapping.Update.Internal.UpdateTranslator.Update()
   at System.Data.Entity.Core.EntityClient.Internal.EntityAdapter.<Update>b__2(UpdateTranslator ut)
   at System.Data.Entity.Core.EntityClient.Internal.EntityAdapter.Update[T](T noChangesResult, Func`2 updateFunction)
   at System.Data.Entity.Core.EntityClient.Internal.EntityAdapter.Update()
   at System.Data.Entity.Core.Objects.ObjectContext.<SaveChangesToStore>b__d()
   at System.Data.Entity.Core.Objects.ObjectContext.ExecuteInTransaction[T](Func`1 func, IDbExecutionStrategy executionStrategy, Boolean startLocalTransaction, Boolean releaseConnectionOnSuccess)
   at System.Data.Entity.Core.Objects.ObjectContext.SaveChangesToStore(SaveOptions options, IDbExecutionStrategy executionStrategy, Boolean startLocalTransaction)
   at System.Data.Entity.Core.Objects.ObjectContext.<>c__DisplayClassb.<SaveChangesInternal>b__8()
   at System.Data.Entity.Infrastructure.DefaultExecutionStrategy.Execute[TResult](Func`1 operation)
   at System.Data.Entity.Core.Objects.ObjectContext.SaveChangesInternal(SaveOptions options, Boolean executeInExistingTransaction)
   at System.Data.Entity.Core.Objects.ObjectContext.SaveChanges()
   ****
   at MyNamespace.MyApp.MyContext.OnMyEvent() in C:MyPathMyAppMyContext.cs
   ****

我无法重现这个崩溃,所以我无法尝试修复它。我想把它整理一下。

我理解崩溃信息。但我的应用没有做任何应该导致这个的事情。MyEntity.Id属性是一个实体键(StoreGeneratedPattern = Identity),我从未手动设置Id值。我从不手动将实体标记为已修改。我的SQLite数据库没有任何触发器或任何其他可能与EF对数据库生成的标识值的解释混淆的东西。我真的不明白这个崩溃是怎么发生的,我自己也不能让它发生——插入新实体对我来说很好。

我当然不指望任何人读了这篇文章,想出解决方案,但我希望有人能引导我在正确的方向。

哪些不明显的原因会导致崩溃?

我肯定会被要求提供源代码,但它真的没有太多。在罪魁祸首代码中,我只是创建了一个MyEntity对象,设置了一些属性值,将其添加到上下文中,然后调用SaveChanges()。就是这样。正如我所说,它在我的测试中运行良好,99%的客户使用。但是我每天都会收到几份这样的崩溃报告,我很想结束它们。

---- UPDATE ----

我认为值得指出的是,堆栈跟踪中的MyEntity.set_Id(Int64 value)显然是实体框架的键/身份值检索的一部分(参见PropagatorResult.SetServerGenValue(Object value)周围的行,等)。这不是我的代码设置Id,它是EF。

查看实体框架源代码,当试图为不处于Added状态的实体设置实体键值时抛出此异常:

// Key fields are only editable if the entry is the `Added` state.
if (member.IsPartOfKey
  && State != EntityState.Added)
{
  throw new InvalidOperationException(Strings.ObjectStateEntry_CannotModifyKeyProperty(memberName));
}

那么问题可能是MyEntity在 SaveChanges()之前以某种方式被置于替代状态(例如Modified) (或在SaveChanges()期间但在身份值检索之前)?

这怎么可能呢?如前所述,我没有在任何地方手动设置状态

已解决。

堆栈跟踪底部的MyNamespace.MyApp.MyContext.OnMyEvent()行在预定时间执行。

事实证明,两个或多个执行可能同时被调度。在某些情况下(甚至在我的测试中,它是不一致的,通常工作),它会导致这个崩溃,大概是因为SQLite的EF生成的ID检索可能会被绊倒,当多个DB插入在几乎瞬间接近完成。

无论如何,解决方案是简单地将MyNamespace.MyApp.MyContext.OnMyEvent()中的代码包装在lock { ... }语句中,以防止并发执行。

最新更新