问题
Got this error when trying to serialize a set of errors:
"ISerializable type 'System.Data.Entity.Infrastructure.DbUpdateConcurrencyException' does not have a valid constructor. To correctly implement ISerializable a constructor that takes SerializationInfo and StreamingContext parameters should be present."
The constructor is in fact present in the base classes, but it is a protected
member.
Someone asked to see 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"
}
Here is example code that throws 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
This gets even stranger, because as one answer points out, this is an exceptional class in that it does not directly implement a constructor with the expected signature. Instead, decompiling the class shows some kind of, quite literal, "justification" for NOT implementing the expected 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:
According to the Json.net documentation,
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.
Since you don't owe the DbUpdateConcurrencyException
class, A workaround could be to create a custom exception class which derive from the DbUpdateConcurrencyException
and Mark it with 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 a just a POC that I tried to make it work for JSON.Net. It makes no sense to create custom classes for all type which inherits ISerializable
and doesn't have required constructor. May be you can try creating Castle core DynamicProxy
generator to wrap the thrown exception which are ISerializable
and mark them with JsonObject
attribute on-the-fly before serializing them.
And you're right. Json.net is not able to find the protected constructor because inheritance goes like
DbUpdateConcurrencyException <- DbUpdateException <- DataException
And DataException class have the Protected constructor which json.net is looking for. Every exception class in .Net framework which is derived from SystemException
have this constructor as protected constructor but DbUpdateException
&& DbUpdateConcurrencyException
doesn't have it. So you can guess who to blame now (IMO EF).
Following are the classes I found which have the standard serializable constructor missing and would throw exception during deserialization.
- EntitySqlException
- PropertyConstraintException
- DbUpdateConcurrencyException
- DbUpdateException
- ToolingException
- DbEntityValidationException
- CommandLineException
I wrote this issue to EF team here.
回答2:
The issue appears to be that, for ISerializable objects, Json.NET does not support serialization of proxy types defined via the SerializationInfo.SetType(Type) method, nor deserialization of proxy objects that implement the IObjectReference interface that replaces the deserialized proxy with the corresponding "real" object. Specifically, changing the serialized type during serialization would need to be supported in JsonSerializerInternalWriter.SerializeISerializable() and JsonSerializerInternalReader.CreateISerializable() -- but it is not.
And as it turns out, exceptions subtypes such as DbUpdateConcurrencyException apparently no longer provide their own streaming constructor, but instead rely on this very proxy mechanism to serialize themselves, in particular setting the SerializationInfo.ObjectType to the internal type SafeSerializationManager -- which, as stated, Json.NET does not support, causing the problem you are seeing.
If your code is running in full trust, as a workaround you can add a custom JsonConverter that handles serializing and deserializing of serialization proxies:
public class SerializableConverter<T> : JsonConverter where T : ISerializable
{
public override bool CanConvert(Type objectType)
{
return typeof(T).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var wrapper = serializer.Deserialize<SerializableProxyWrapper>(reader);
var proxy = wrapper.SerializableValues;
if (proxy == null)
return null;
return proxy.GetRealObject(serializer.Context);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var serializable = (ISerializable)value;
var proxy = SerializableProxy.ToProxy(serializable, serializer.Context);
serializer.Serialize(writer, new SerializableProxyWrapper { SerializableValues = proxy });
}
}
sealed class SerializableProxyWrapper
{
[JsonProperty(TypeNameHandling = TypeNameHandling.All)]
public SerializableProxy SerializableValues { get; set; }
}
abstract class SerializableProxy : IObjectReference
{
public static SerializableProxy ToProxy(ISerializable value, StreamingContext context)
{
if (value == null)
return null;
SerializationInfo serializationInfo = new SerializationInfo(value.GetType(), new FormatterConverter());
value.GetObjectData(serializationInfo, context);
var objectType = serializationInfo.GetObjectType();
return (SerializableProxy)Activator.CreateInstance(typeof(SerializableProxy<>).MakeGenericType(new[] { objectType }), new object[] { serializationInfo, context });
}
#region IObjectReference Members
public abstract object GetRealObject(StreamingContext context);
#endregion
}
[Serializable]
sealed class SerializableProxy<T> : SerializableProxy, ISerializable, IObjectReference where T : ISerializable
{
SerializationInfo originalInfo;
StreamingContext context;
public SerializableProxy(SerializationInfo originalInfo, StreamingContext context)
{
this.originalInfo = originalInfo;
this.context = context;
}
#region ISerializable Members
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
foreach (SerializationEntry entry in originalInfo)
info.AddValue(entry.Name, entry.Value, entry.ObjectType);
}
#endregion
#region IObjectReference Members
public override object GetRealObject(StreamingContext context)
{
var realObject = Activator.CreateInstance(typeof(T), BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null,
new object[] { originalInfo, context }, CultureInfo.InvariantCulture);
return realObject.GetFinalRealObject(context);
}
#endregion
}
static partial class SerializationExtensions
{
public static object GetFinalRealObject(this object obj, StreamingContext context)
{
while (obj is IObjectReference)
{
var realObj = ((IObjectReference)obj).GetRealObject(context);
if (!(realObj is IObjectReference))
return realObj;
if (realObj == obj)
return realObj; // Avoid an infinite loop
obj = (IObjectReference)realObj;
}
return obj;
}
}
static partial class SerializationExtensions
{
public static Type GetObjectType(this SerializationInfo info)
{
if (info == null)
return null;
return info.ObjectType;
}
}
Then it should be usable as follows:
var settings = new JsonSerializerSettings
{
Converters = new [] { new SerializableConverter<Exception>() },
Formatting = Formatting.Indented,
};
var json = JsonConvert.SerializeObject(ex, settings);
Console.WriteLine(json);
However, if your code is not fully trusted, this workaround might fail, and support from Json.NET itself would be required.
Note - tested in some mockup scenarios but not with the actual exceptions you are using.
回答3:
You can do it with a contract resolver if you accept to use only base exception fields. Must be used with TypeNameHandling = TypeNameHandling.All;
public class ISafeSerializationInfoContractResolver : DefaultContractResolver
{
protected override JsonContract CreateContract(Type objectType)
{
JsonContract contract = base.CreateContract(objectType);
if (contract is JsonISerializableContract)
{
JsonISerializableContract serializable = contract as JsonISerializableContract;
if (serializable.ISerializableCreator == null && typeof(Exception).IsAssignableFrom(objectType))
{
serializable.ISerializableCreator = p =>
{
SerializationInfo info = (SerializationInfo)p[0];
StreamingContext context = (StreamingContext)p[1];
Exception exception = (Exception)Activator.CreateInstance(typeof(Exception), BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null,
new object[] { info, context }, CultureInfo.InvariantCulture);
object realException = Activator.CreateInstance(objectType, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, null, CultureInfo.InvariantCulture);
FieldInfo[] fields = typeof(Exception).GetFields(System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
foreach (FieldInfo field in fields)
{
field.SetValue(realException, field.GetValue(exception));
}
return realException;
};
}
}
return contract;
}
}
来源:https://stackoverflow.com/questions/35900889/is-this-a-bug-in-json-net-or-entity-framework-or-am-i-doing-something-wrong-whil