尝试序列化一组错误时出现此错误:
"ISerializable 类型'System.Data.Entity.Infrastructure.DbUpdateConcurrencyException'没有有效的构造函数。为了正确实现 ISerializable,应该存在一个采用 SerializationInfo 和 StreamingContext 参数的构造函数。
构造函数实际上存在于基类中,但它是protected
成员。
有人要求查看 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)rn at System.Data.Entity.Core.Mapping.Update.Internal.UpdateTranslator.Update()rn at System.Data.Entity.Core.EntityClient.Internal.EntityAdapter.<Update>b__2(UpdateTranslator ut)rn at System.Data.Entity.Core.EntityClient.Internal.EntityAdapter.Update[T](T noChangesResult, Func`2 updateFunction)rn at System.Data.Entity.Core.EntityClient.Internal.EntityAdapter.Update()rn at System.Data.Entity.Core.Objects.ObjectContext.<SaveChangesToStore>b__35()rn at System.Data.Entity.Core.Objects.ObjectContext.ExecuteInTransaction[T](Func`1 func, IDbExecutionStrategy executionStrategy, Boolean startLocalTransaction, Boolean releaseConnectionOnSuccess)rn at System.Data.Entity.Core.Objects.ObjectContext.SaveChangesToStore(SaveOptions options, IDbExecutionStrategy executionStrategy, Boolean startLocalTransaction)rn at System.Data.Entity.Core.Objects.ObjectContext.<>c__DisplayClass2a.<SaveChangesInternal>b__27()rn at System.Data.Entity.SqlServer.DefaultSqlExecutionStrategy.Execute[TResult](Func`1 operation)rn at System.Data.Entity.Core.Objects.ObjectContext.SaveChangesInternal(SaveOptions options, Boolean executeInExistingTransaction)rn at System.Data.Entity.Core.Objects.ObjectContext.SaveChanges(SaveOptions options)rn at System.Data.Entity.Internal.InternalContext.SaveChanges()",
"RemoteStackTraceString": null,
"RemoteStackIndex": 0,
"ExceptionMethod": "8nValidateRowsAffectednEntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089nSystem.Data.Entity.Core.Mapping.Update.Internal.UpdateTranslatornVoid 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()rn at System.Data.Entity.Internal.LazyInternalContext.SaveChanges()rn at System.Data.Entity.DbContext.SaveChanges()rn at REDACTED FOR DISPLAY ON STACKOVERFLOW",
"RemoteStackTraceString": null,
"RemoteStackIndex": 0,
"ExceptionMethod": "8nSaveChangesnEntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089nSystem.Data.Entity.Internal.InternalContextnInt32 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"
}
下面是引发异常的示例代码:
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
这变得更加奇怪,因为正如一个答案指出的那样,这是一个特殊的类,因为它不直接实现具有预期签名的构造函数。 相反,反编译类显示了某种非常字面的"理由",用于不实现预期的构造函数......
/// <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
{
根据 Json.net 文档,
可独立化
实现 ISerializable 的类型被序列化为 JSON 对象。序列化时,仅使用从 ISerializable.GetObjectData 返回的值;将忽略类型上的成员。反序列化时,将调用具有 SerializationInfo 和 StreamingContext 的构造函数,并传递 JSON 对象的值。
在不需要此行为的情况下,可以将 JsonObjectAttribute 放置在实现 ISerializable 的 .NET 类型上,以强制将其序列化为普通 JSON 对象。
由于您不欠DbUpdateConcurrencyException
类,解决方法可能是创建一个从DbUpdateConcurrencyException
派生的自定义异常类,并使用属性 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);
这只是一个 POC,我试图让它为 JSON.Net 工作。为继承ISerializable
并且没有必需构造函数的所有类型创建自定义类是没有意义的。也许您可以尝试创建 Castle 核心DynamicProxy
生成器来包装ISerializable
抛出的异常,并在序列化它们之前用JsonObject
属性即时标记它们。
你是对的。 Json.net 无法找到受保护的构造函数,因为继承就像
DbUpdateConcurrencyException <- DbUpdateException <- DataException
DataException 类具有 json.net 正在寻找的受保护构造函数。.Net 框架中派生自SystemException
的每个异常类都有这个构造函数作为受保护的构造函数,但DbUpdateException
&&DbUpdateConcurrencyException
没有它。所以你现在可以猜到该怪谁(IMO EF(。
以下是我发现的类,这些类缺少标准的可序列化构造函数,并且在反序列化期间会引发异常。
- EntitySqlException
- 属性约束异常
- DbUpdateConcurrencyException
- DbUpdateException
- 工具异常
- DbEntityValidationException
- 命令行异常
我在这里向 EF 团队写了这个问题。
问题似乎是,对于ISerializable
对象,Json.NET 不支持通过 SerializationInfo.SetType(Type)
方法定义的代理类型的序列化,也不支持实现将反序列化代理替换为相应的"真实"对象的 IObjectReference
接口的代理对象的反序列化。 具体来说,在序列化期间更改序列化类型需要在 JsonSerializerInternalWriter.SerializeISerializable()
和 JsonSerializerInternalReader.CreateISerializable()
中得到支持,但事实并非如此。
事实证明,异常子类型(如 DbUpdateConcurrencyException
(显然不再提供自己的流构造函数,而是依赖于这种代理机制来序列化自己,特别是将SerializationInfo.ObjectType
设置为内部类型 SafeSerializationManager
- 如前所述,Json.NET 不支持,从而导致您看到的问题。
如果代码在完全信任环境中运行,作为解决方法,您可以添加一个自定义JsonConverter
来处理序列化代理的序列化和反序列化:
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;
}
}
那么它应该可用,如下所示:
var settings = new JsonSerializerSettings
{
Converters = new [] { new SerializableConverter<Exception>() },
Formatting = Formatting.Indented,
};
var json = JsonConvert.SerializeObject(ex, settings);
Console.WriteLine(json);
但是,如果您的代码不完全受信任,则此解决方法可能会失败,并且需要 Json.NET 本身的支持。
注意 - 在某些模型场景中进行了测试,但未与您使用的实际例外情况一起测试。
如果您接受仅使用基本异常字段,则可以使用协定解析程序执行此操作。必须与 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;
}
}