这是 JSON.NET 或实体框架中的错误,还是我在尝试使用 JSON.NET 序列化异常列表时做错了什么



尝试序列化一组错误时出现此错误:

"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;
    }
}

相关内容

  • 没有找到相关文章

最新更新