0

I manage a publicly available WPF app which uses Entity Framework 6.1 (model-first ObjectContext) and a SQLite database.

The number one crash log (according to HockeyApp) for my app is the following (with pointers to my app's code marked ****):

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:\MyPath\MyApp\EfGeneratedEntities.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:\MyPath\MyApp\MyContext.cs
   ****

I have not been able to reproduce this crash, and so I have not been able to attempt to fix it. I would love to get it sorted.

I understand the crash message. But my app is not doing anything that should cause this. The MyEntity.Id property is an entity key (with StoreGeneratedPattern = Identity) and I never manually set the Id value. I never manually mark an entity as modified. My SQLite DB does not have any triggers or anything else that could mess with EF's interpretation of DB-generated identity values. I really can't see how this crash could happen, and I can't make it happen myself - inserts of new entities work just fine for me.

I of course don't expect anyone to read this post and come up with the solution, but I'm hoping somebody can lead me in the right direction.

What non-obvious reasons could cause this crash to happen?

I'm sure I'll be asked for source code, but there's really not much to it. In the culprit code I simply create a MyEntity object, set some property values, add it to context, and call SaveChanges(). That's it. As I said, it works just fine in my testing and for 99% of customer usage. But I get several crash reports like this every day and I'd love to put an end to them.

---- UPDATE ----

I think it's worth pointing out that the MyEntity.set_Id(Int64 value) in the stack trace is clearly part of Entity Framework's key/identity value retrieval (see lines around PropagatorResult.SetServerGenValue(Object value), etc.). This is not my code setting the Id, it's EF.

Looking the Entity Framework source code, this exception is thrown when trying to set an entity key value for an entity not in the Added state:

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

So could the issue be that MyEntity is somehow being put into an alternate state (e.g. Modified) before SaveChanges() (or during SaveChanges() but before identity value retrieval)?

How could that happen? As I mentioned, I am not manually setting the state anywhere.

Ross
  • 4,460
  • 2
  • 32
  • 59
  • If you never ever set Id in your code (also with reflection), you have to search in other libraries you use. For example, do you have a viewmodel and how do you transfer data between entities and viewmodel? Or do you use databinding on entities? (BTW, I understand the frustration!!!) – bubi Sep 17 '16 at 08:24
  • do you use manual transaction scope? and what is the type of this id column in the database? – DevilSuichiro Sep 17 '16 at 08:29
  • If you never set the `Id` on your property then perhaps making the setter `private` would lead to some other exception that would narrow it down, or even lead to a compile-time error? Also, there is some lambda method mentioned in your stack trace, so I'd suggest to take a closer look at those. Possible culprit could also be some 3rd party tool like`AutoMapper` or something of the sort. – Grx70 Sep 18 '16 at 07:38
  • Thanks guys. @DevilSuichiro nope it's not Manual TS. `Id` is `Int64`; @bubi I added more info to the question about the `Id` setter; @Grx70 Making `Id` `private` breaks the EF as it can no longer set the `Id` value after an `INSERT`. The lambda is part of the EF code (which I have stepped through the source of - it's a bit of internal reflection magic used for populating DB-computed property values. I've updated question with more info if it helps you see anything! – Ross Sep 18 '16 at 08:13
  • the error message points out that the id value did change between retrieval and saving. this would mean 1.somewhere in your code you passed object references inadequately, 2. some 3rd party tool is messing up your id values or 3. you have broken/faulty EF/EF db provider libraries – DevilSuichiro Sep 18 '16 at 10:48
  • Issue solved (see answer). Thanks for comments! – Ross Sep 19 '16 at 08:49

1 Answers1

0

Solved.

The MyNamespace.MyApp.MyContext.OnMyEvent() line at the bottom of my stack trace gets executed at a scheduled time.

It turns out that it would be possible for two or more executions to be scheduled at the same time. In some cases (even in my testing it was inconsistent and usually worked anyway), it would cause this crash, presumably because SQLite's EF generated ID retrieval could be tripped up when multiple DB inserts are done in near instantaneous proximity.

Anyway, the solution was to simply wrap the code in MyNamespace.MyApp.MyContext.OnMyEvent() in a lock { ... } statement to prevent concurrent execution.

Ross
  • 4,460
  • 2
  • 32
  • 59