使用 Newtonsoft.Json 反序列化 SqlException 的 InvalidCastException

InvalidCastException deserializing a SqlException with Newtonsoft.Json

我在反序列化包含 SqlException 实例的 JSON 字符串时出错。

环境信息: - .NET 核心:2.2 版 - Newtonsoft.Json:版本 11.0.1

结果错误: System.InvalidCastException:“无法将类型 'Newtonsoft.Json.Linq.JValue' 的对象转换为类型 'System.Guid'。”

堆栈:

 at System.Data.SqlClient.SqlException..ctor(SerializationInfo si, StreamingContext sc)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateISerializable(JsonReader reader, JsonISerializableContract contract, JsonProperty member, String id)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize(JsonReader reader, Type objectType, Boolean checkAdditionalContent)
   at Newtonsoft.Json.JsonSerializer.DeserializeInternal(JsonReader reader, Type objectType)
   at Newtonsoft.Json.JsonConvert.DeserializeObject(String value, Type type, JsonSerializerSettings settings)
   at Newtonsoft.Json.JsonConvert.DeserializeObject[T](String value, JsonSerializerSettings settings)

这里是重现我的问题的测试:

[Fact(DisplayName = nameof(TestShitSingleSqlEx2))]
public async Task TestErrorSingleSqlEx2()
{
    string json = @"{
    ""$type"": ""System.Data.SqlClient.SqlException, System.Data.SqlClient"",
    ""ClassName"": ""System.Data.SqlClient.SqlException"",
    ""Message"": ""A network-related or instance-specific error occurred while establishing a connection to SQL Server. The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server is configured to allow remote connections. (provider: TCP Provider, error: 40 - Could not open a connection to SQL Server)"",
    ""Data"": {
                ""$type"": ""System.Collections.ListDictionaryInternal, System.Private.CoreLib"",
    ""HelpLink.ProdName"": ""Microsoft SQL Server"",
    ""HelpLink.EvtSrc"": ""MSSQLServer"",
    ""HelpLink.EvtID"": ""0"",
    ""HelpLink.BaseHelpUrl"": ""http://go.microsoft.com/fwlink"",
    ""HelpLink.LinkId"": ""20476"",
    ""SqlError 1"": ""System.Data.SqlClient.SqlError: A network-related or instance-specific error occurred while establishing a connection to SQL Server. The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server is configured to allow remote connections. (provider: TCP Provider, error: 40 - Could not open a connection to SQL Server)""
    },
    ""InnerException"": null,
    ""HelpURL"": null,
    ""StackTraceString"": ""   at System.Data.ProviderBase.DbConnectionPool.TryGetConnection(DbConnection owningObject, UInt32 waitForMultipleObjectsTimeout, Boolean allowCreate, Boolean onlyOneCheckConnection, DbConnectionOptions userOptions, DbConnectionInternal& connection)\n   at System.Data.ProviderBase.DbConnectionPool.TryGetConnection(DbConnection owningObject, TaskCompletionSource`1 retry, DbConnectionOptions userOptions, DbConnectionInternal& connection)\n   at System.Data.ProviderBase.DbConnectionFactory.TryGetConnection(DbConnection owningConnection, TaskCompletionSource`1 retry, DbConnectionOptions userOptions, DbConnectionInternal oldConnection, DbConnectionInternal& connection)\n   at System.Data.ProviderBase.DbConnectionInternal.TryOpenConnectionInternal(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource`1 retry, DbConnectionOptions userOptions)\n   at System.Data.ProviderBase.DbConnectionClosed.TryOpenConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource`1 retry, DbConnectionOptions userOptions)\n   at System.Data.SqlClient.SqlConnection.TryOpen(TaskCompletionSource`1 retry)\n   at System.Data.SqlClient.SqlConnection.OpenAsync(CancellationToken cancellationToken)\n--- End of stack trace from previous location where exception was thrown ---\n   at Microsoft.EntityFrameworkCore.Storage.RelationalConnection.OpenDbConnectionAsync(Boolean errorsExpected, CancellationToken cancellationToken)\n   at Microsoft.EntityFrameworkCore.Storage.RelationalConnection.OpenAsync(CancellationToken cancellationToken, Boolean errorsExpected)\n   at Microsoft.EntityFrameworkCore.Query.Internal.AsyncQueryingEnumerable`1.AsyncEnumerator.BufferlessMoveNext(DbContext _, Boolean buffer, CancellationToken cancellationToken)\n   at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func`4 operation, Func`4 verifySucceeded, CancellationToken cancellationToken)\n   at Microsoft.EntityFrameworkCore.Query.Internal.AsyncQueryingEnumerable`1.AsyncEnumerator.MoveNext(CancellationToken cancellationToken)\n   at System.Linq.AsyncEnumerable.SelectEnumerableAsyncIterator`2.MoveNextCore(CancellationToken cancellationToken) in D:\\a\\1\\s\\Ix.NET\\Source\\System.Interactive.Async\\Select.cs:line 106\n   at System.Linq.AsyncEnumerable.AsyncIterator`1.MoveNext(CancellationToken cancellationToken) in D:\\a\\1\\s\\Ix.NET\\Source\\System.Interactive.Async\\AsyncIterator.cs:line 98\n   at Microsoft.EntityFrameworkCore.Query.Internal.AsyncLinqOperatorProvider.ExceptionInterceptor`1.EnumeratorExceptionInterceptor.MoveNext(CancellationToken cancellationToken)\n   at System.Collections.Generic.AsyncEnumerableHelpers.ToArrayWithLength[T](IAsyncEnumerable`1 source, CancellationToken cancellationToken) in D:\\a\\1\\s\\Ix.NET\\Source\\System.Interactive.Async\\AsyncEnumerableHelpers.cs:line 48\n   at System.Collections.Generic.AsyncEnumerableHelpers.ToArray[T](IAsyncEnumerable`1 source, CancellationToken cancellationToken) in D:\\a\\1\\s\\Ix.NET\\Source\\System.Interactive.Async\\AsyncEnumerableHelpers.cs:line 16\n   at XXXXXX.Manufacturing.Sublot.EntityFramework.Repositories.SublotRepository.LoadDataModelAsync(IEnumerable`1 ids) in /src/Sublot/Sublot.EntityFramework/Repositories/SublotRepository.cs:line 28\n   at XXXXXX.Manufacturing.EntityFramework.Repository.DbMultiMapRepository`3.OnGetAsync(IEnumerable`1 ids)\n   at XXXXXX.Manufacturing.Repository.MultiRepository`1.GetAsync(String id)\n   at XXXXXX.Manufacturing.Sublot.Cqrs.Handlers.SublotHandler.Handle(MoveSublotCommand command) in /src/Sublot/Sublot.Cqrs.Handlers/SublotHandler.cs:line 193\n   at Rebus.Pipeline.Receive.HandlerInvoker`1.Invoke()\n   at Rebus.Pipeline.Receive.DispatchIncomingMessageStep.Process(IncomingStepContext context, Func`1 next)\n   at Rebus.Sagas.LoadSagaDataStep.Process(IncomingStepContext context, Func`1 next)\n   at Rebus.Sagas.Exclusive.NewEnforceExclusiveSagaAccessIncomingStep.Process(IncomingStepContext context, Func`1 next)\n   at Rebus.Pipeline.Receive.ActivateHandlersStep.Process(IncomingStepContext context, Func`1 next)\n   at Rebus.Pipeline.Receive.HandleRoutingSlipsStep.Process(IncomingStepContext context, Func`1 next)\n   at Rebus.Retry.Simple.FailedMessageWrapperStep.Process(IncomingStepContext context, Func`1 next)\n   at Rebus.Pipeline.Receive.DeserializeIncomingMessageStep.Process(IncomingStepContext context, Func`1 next)\n   at Rebus.Pipeline.Receive.HandleDeferredMessagesStep.Process(IncomingStepContext context, Func`1 next)\n   at Rebus.Retry.FailFast.FailFastStep.Process(IncomingStepContext context, Func`1 next)\n   at Rebus.Retry.Simple.SimpleRetryStrategyStep.DispatchWithTrackerIdentifier(Func`1 next, String identifierToTrackMessageBy, ITransactionContext transactionContext, String messageId, String secondLevelMessageId)"",
    ""RemoteStackTraceString"": null,
    ""RemoteStackIndex"": 0,
    ""ExceptionMethod"": null,
    ""HResult"": -2146232060,
    ""Source"": ""Core .Net SqlClient Data Provider"",
    ""WatsonBuckets"": null,
    ""Errors"": null,
    ""ClientConnectionId"": ""00000000-0000-0000-0000-000000000000""
    }";

    var settings = new JsonSerializerSettings()
    {
        TypeNameHandling = TypeNameHandling.All,
    }; ;

    var cmd = JsonConvert.DeserializeObject<System.Data.SqlClient.SqlException>(json, settings);
}

我已经用 TypeNameHandling.None(默认值)测试过它是否有效。但是我在生产环境中遇到了这个错误(使用 rebus v5.4.0)并且我没有信心修改所有消息的 TypeNameHandling。

问题似乎出在 属性 ""ClientConnectionId"": ""00000000-0000-0000-0000-000000000000""

的演员表中

事实上,如果您将它从 json 字符串中删除,它就会起作用。

问题是转换类型的代码在class System.Data.SqlClient.SqlException

这里是执行class反序列化的源代码:

private SqlException(SerializationInfo si, StreamingContext sc) : base(si, sc)
        {
            HResult = SqlExceptionHResult;
            foreach (SerializationEntry siEntry in si)
            {
                if ("ClientConnectionId" == siEntry.Name)
                {
                    _clientConnectionId = (Guid)siEntry.Value;
                    break;
                }
            }
        }

来源:https://github.com/dotnet/corefx/blob/master/src/System.Data.SqlClient/src/System/Data/SqlClient/SqlException.cs

问题在于演员: (指南)siEntry.Value;

我已经在 GitHub SqlClient 上发布了 the issue。项目团队实施的修复正在合并中,因此它应该在 Microsoft.Data.SqlClient v1.1.2 更新的版本中可用。 DummyRick,考虑一下(通过查看 SqlException 的堆栈跟踪)看起来您正在使用 legacy System.Data.SqlClient 包而不是 Microsoft.Data.SqlClient.