Is this a bug in JSON.NET or Entity Framework or am I doing something wrong while trying to serialize a list of Exceptions with JSON.NET?

entity-framework-6 exception json.net serialization

Question

Got this error when trying to serialize a set of errors:

"'System.Data.Entity.Infrastructure' is an ISerializable type. The constructor for DbUpdateConcurrencyException is invalid. A constructor that accepts SerializationInfo and StreamingContext parameters must exist in order to properly implement ISerializable."

The base classes do contain a constructor, however it is aprotected member.

A request was made to view the JSON:

{
    "$type": "System.Data.Entity.Infrastructure.DbUpdateConcurrencyException, EntityFramework",
    "ClassName": "System.Data.Entity.Infrastructure.DbUpdateConcurrencyException",
    "Message": "Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=472540 for information on understanding and handling optimistic concurrency exceptions.",
    "Data": {
        "$type": "System.Collections.ListDictionaryInternal, mscorlib"
    },
    "InnerException": {
        "$type": "System.Data.Entity.Core.OptimisticConcurrencyException, EntityFramework",
        "ClassName": "System.Data.Entity.Core.OptimisticConcurrencyException",
        "Message": "Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=472540 for information on understanding and handling optimistic concurrency exceptions.",
        "Data": {
            "$type": "System.Collections.ListDictionaryInternal, mscorlib"
        },
        "InnerException": null,
        "HelpURL": null,
        "StackTraceString": "   at System.Data.Entity.Core.Mapping.Update.Internal.UpdateTranslator.ValidateRowsAffected(Int64 rowsAffected, UpdateCommand source)\r\n   at System.Data.Entity.Core.Mapping.Update.Internal.UpdateTranslator.Update()\r\n   at System.Data.Entity.Core.EntityClient.Internal.EntityAdapter.<Update>b__2(UpdateTranslator ut)\r\n   at System.Data.Entity.Core.EntityClient.Internal.EntityAdapter.Update[T](T noChangesResult, Func`2 updateFunction)\r\n   at System.Data.Entity.Core.EntityClient.Internal.EntityAdapter.Update()\r\n   at System.Data.Entity.Core.Objects.ObjectContext.<SaveChangesToStore>b__35()\r\n   at System.Data.Entity.Core.Objects.ObjectContext.ExecuteInTransaction[T](Func`1 func, IDbExecutionStrategy executionStrategy, Boolean startLocalTransaction, Boolean releaseConnectionOnSuccess)\r\n   at System.Data.Entity.Core.Objects.ObjectContext.SaveChangesToStore(SaveOptions options, IDbExecutionStrategy executionStrategy, Boolean startLocalTransaction)\r\n   at System.Data.Entity.Core.Objects.ObjectContext.<>c__DisplayClass2a.<SaveChangesInternal>b__27()\r\n   at System.Data.Entity.SqlServer.DefaultSqlExecutionStrategy.Execute[TResult](Func`1 operation)\r\n   at System.Data.Entity.Core.Objects.ObjectContext.SaveChangesInternal(SaveOptions options, Boolean executeInExistingTransaction)\r\n   at System.Data.Entity.Core.Objects.ObjectContext.SaveChanges(SaveOptions options)\r\n   at System.Data.Entity.Internal.InternalContext.SaveChanges()",
        "RemoteStackTraceString": null,
        "RemoteStackIndex": 0,
        "ExceptionMethod": "8\nValidateRowsAffected\nEntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089\nSystem.Data.Entity.Core.Mapping.Update.Internal.UpdateTranslator\nVoid ValidateRowsAffected(Int64, System.Data.Entity.Core.Mapping.Update.Internal.UpdateCommand)",
        "HResult": -2146233087,
        "Source": "EntityFramework",
        "WatsonBuckets": null
    },
    "HelpURL": null,
    "StackTraceString": "   at System.Data.Entity.Internal.InternalContext.SaveChanges()\r\n   at System.Data.Entity.Internal.LazyInternalContext.SaveChanges()\r\n   at System.Data.Entity.DbContext.SaveChanges()\r\n   at REDACTED FOR DISPLAY ON STACKOVERFLOW",
    "RemoteStackTraceString": null,
    "RemoteStackIndex": 0,
    "ExceptionMethod": "8\nSaveChanges\nEntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089\nSystem.Data.Entity.Internal.InternalContext\nInt32 SaveChanges()",
    "HResult": -2146233087,
    "Source": "EntityFramework",
    "WatsonBuckets": null,
    "SafeSerializationManager": {
        "$type": "System.Runtime.Serialization.SafeSerializationManager, mscorlib",
        "m_serializedStates": {
            "$type": "System.Collections.Generic.List`1[[System.Object, mscorlib]], mscorlib",
            "$values": [
                {
                    "$type": "System.Data.Entity.Infrastructure.DbUpdateException+DbUpdateExceptionState, EntityFramework",
                    "InvolvesIndependentAssociations": false
                }
            ]
        }
    },
    "CLR_SafeSerializationManager_RealType": "System.Data.Entity.Infrastructure.DbUpdateConcurrencyException, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
}

The following sample code raises the exception:

var serializationSettings = new JsonSerializerSettings() {
    DateFormatHandling = DateFormatHandling.IsoDateFormat,
    DateParseHandling = DateParseHandling.DateTime,
    DateTimeZoneHandling = DateTimeZoneHandling.RoundtripKind,
    DefaultValueHandling = DefaultValueHandling.Include,
    TypeNameHandling = TypeNameHandling.All,
    TypeNameAssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple,
    ObjectCreationHandling = ObjectCreationHandling.Replace, //Necessary for subclassing list types
    ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor
};

var json = JsonConvert.SerializeObject( new System.Data.Entity.Infrastructure.DbUpdateConcurrencyException( "hi" ), serializationSettings );
if (json == null)
    return null;
var err = JsonConvert.DeserializeObject<System.Data.Entity.Infrastructure.DbUpdateConcurrencyException>( json, serializationSettings ); //throws error

Even stranger, this class is unusual in that it does not explicitly implement a constructor with the anticipated signature, as one responder points out, which makes the situation even stranger. Instead, the class's decompilation reveals some sort of, blatantly obvious "justification" for not implementing the anticipated constructor.

/// <summary>
/// Exception thrown by <see cref="T:System.Data.Entity.DbContext" /> when the saving of changes to the database fails.
/// Note that state entries referenced by this exception are not serialized due to security and accesses to the
/// state entries after serialization will return null.
/// </summary>
[SuppressMessage("Microsoft.Design", "CA1032:ImplementStandardExceptionConstructors",
    Justification = "SerializeObjectState used instead")]
[Serializable]
public class DbUpdateException : DataException
{
    /// <summary>
    /// Holds exception state that will be serialized when the exception is serialized.
    /// </summary>
    [Serializable]
    private struct DbUpdateExceptionState : ISafeSerializationData
    {
1
2
3/10/2016 4:42:35 AM

Popular Answer

The Json.net documentation claims that

ISerializable

Types that implement ISerializable are serialized as JSON objects. When serializing, only the values returned from ISerializable.GetObjectData are used; members on the type are ignored. When deserializing, the constructor with a SerializationInfo and StreamingContext is called, passing the JSON object's values.

In situations where this behavior is not wanted, the JsonObjectAttribute can be placed on a .NET type that implements ISerializable to force it to be serialized as a normal JSON object.

as you don't owe theDbUpdateConcurrencyException class, An alternative would be to develop a unique exception class that derives from theDbUpdateConcurrencyException and add an attribute.JsonObject .

    [JsonObject]
    class CustomException : DbUpdateConcurrencyException
    {
        public CustomException(string message) : base(message) { }
    }

     // Serialize the new customException
     var json = JsonConvert.SerializeObject(
         new CustomException("hi"), serializationSettings);

     //shall not throw error now
     DbUpdateConcurrencyException err = 
          JsonConvert.DeserializeObject<DbUpdateConcurrencyException>(json, serializationSettings); 

This is merely a proof-of-concept that I tried to adapt to JSON.Net. Making custom classes for every type that inherits makes no sense.ISerializable and lacks the necessary constructor. Perhaps you could try making a Castle core.DynamicProxy generator to wrap the exceptions that are thrownISerializable and put a mark on themJsonObject attribute dynamically prior to serialization.

You're correct, too. Because inheritance follows this pattern, Json.net cannot locate the protected constructor.

DbUpdateConcurrencyException <- DbUpdateException <- DataException 

Additionally, the Protected constructor that json.net requires is present in the DataException class. Every.Net Framework exception class that is derived fromSystemException a protected constructor for this one, butDbUpdateException && DbUpdateConcurrencyException not possess it. Now you know who is to blame (IMO EF).

The classes I discovered that lacked the typical serializable constructor and would throw exceptions during deserialization are listed below.

  • EntitySqlException
  • PropertyConstraintException
  • DbUpdateConcurrencyException
  • DbUpdateException
  • ToolingException
  • DbEntityValidationException
  • CommandLineException

This message was sent to Team EF here.

2
3/9/2016 10:51:32 PM


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow