Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

InvalidCastException deserializing a SqlException #524

Closed
amartini-n opened this issue Apr 15, 2020 · 8 comments · Fixed by #527
Closed

InvalidCastException deserializing a SqlException #524

amartini-n opened this issue Apr 15, 2020 · 8 comments · Fixed by #527

Comments

@amartini-n
Copy link

Describe the bug

When deserializing a SqlException from a JSON string by using Newtonsoft JSON I got the following exception.

Exception message:
System.InvalidCastException : Unable to cast object of type 'Newtonsoft.Json.Linq.JValue' to type 'System.Guid'.
Stack trace:
  SqlException.ctor(SerializationInfo si, StreamingContext sc)
  lambda_method(Closure , Object[] )
  JsonSerializerInternalReader.CreateISerializable(JsonReader reader, JsonISerializableContract contract, JsonProperty member, String id) line 1771
  JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue) line 581
  JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue) line 300
  JsonSerializerInternalReader.Deserialize(JsonReader reader, Type objectType, Boolean checkAdditionalContent) line 173
  JsonSerializer.DeserializeInternal(JsonReader reader, Type objectType) line 907
  JsonSerializer.Deserialize(JsonReader reader, Type objectType) line 886
  JsonConvert.DeserializeObject(String value, Type type, JsonSerializerSettings settings) line 837
  JsonConvert.DeserializeObject[T](String value, JsonSerializerSettings settings) line 792
  Issue2313.Test() line 88

To reproduce

    // stack trace in following JSON string have been lighten (since it is not meaningful)
    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.SqlClient.SqlInternalConnectionTds..ctor(DbConnectionPoolIdentity identity, SqlConnectionString connectionOptions, SqlCredential credential, Object providerInfo, String newPassword, SecureString newSecurePassword, Boolean redirectedUserInstance, SqlConnectionString userConnectionOptions, SessionData reconnectSessionData, Boolean applyTransientFaultHandling, String accessToken)\\n   at System.Data.SqlClient.SqlConnectionFactory.CreateConnection(DbConnectionOptions options, DbConnectionPoolKey poolKey, Object poolGroupProviderInfo, DbConnectionPool pool, DbConnection owningConnection, DbConnectionOptions userOptions)\\n   at System.Data.ProviderBase.DbConnectionFactory.CreatePooledConnection(DbConnectionPool pool, DbConnection owningObject, DbConnectionOptions options, DbConnectionPoolKey poolKey, DbConnectionOptions userOptions)\\n   at System.Data.ProviderBase.DbConnectionPool.CreateObject(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection)\\n   at System.Data.ProviderBase.DbConnectionPool.UserCreateRequest(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection)\\n   at System.Data.ProviderBase.DbConnectionPool.TryGetConnection(DbConnection owningObject, UInt32 waitForMultipleObjectsTimeout, Boolean allowCreate, Boolean onlyOneCheckConnection, DbConnectionOptions userOptions, DbConnectionInternal& connection)\\n   at System.Data.ProviderBase.DbConnectionPool.WaitForPendingOpen()\\n"",
        ""RemoteStackTraceString"": null,
        ""RemoteStackIndex"": 0,
        ""ExceptionMethod"": null,
        ""HResult"": -2146232060,
        ""Source"": ""Core .Net SqlClient Data Provider"",
        ""WatsonBuckets"": null,
        ""Errors"": null,
        ""ClientConnectionId"": ""90cdab4d-2145-4c24-a354-c8ccff903542""
    }
    ";
        
    var settings = new JsonSerializerSettings()
    {
        TypeNameHandling = TypeNameHandling.All,
    };

    var sqlEx = Newtonsoft.Json.JsonConvert.DeserializeObject<System.Data.SqlClient.SqlException>(json, settings);

Expected behavior

I expected to be able to deserialize with no problem an instance of SqlException from its JSON string representation.

Further technical details

Microsoft.Data.SqlClient version: 4.8.1
.NET target: Core 2.2, Core 3.0
SQL Server version: n/a
Operating system: Windows 10.0.17763

@ErikEJ
Copy link
Contributor

ErikEJ commented Apr 15, 2020

Looks like you are using the legacy System.Data.SqlClient - have you tried the provider in this repo: Microsoft.Data.SqlClient ?

@cheenamalhotra
Copy link
Member

Hi @amartini-n

I've made PR #527 to fix the issue, please give it a try and let us know!

@cheenamalhotra cheenamalhotra added this to To do in SqlClient v2.0.0 via automation Apr 15, 2020
@cheenamalhotra cheenamalhotra moved this from To do to Review in progress in SqlClient v2.0.0 Apr 15, 2020
@amartini-n
Copy link
Author

Looks like you are using the legacy System.Data.SqlClient - have you tried the provider in this repo: Microsoft.Data.SqlClient ?

You were absolutely right! Sorry for that.
Anyway the issue was still present in last Microsoft.Data.SqlClient

@amartini-n
Copy link
Author

Hi @amartini-n

I've made PR #527 to fix the issue, please give it a try and let us know!

Thank you @cheenamalhotra, it works.
Anyway:

  • Is there any reason on GetObjectData to call SerializationInfo.AddValue by providing object type instead of Guid (as it was in former implementation)? Sorry, but I didn't get the reason :$
  • Wouldn't be better improving the implementation of the ISerializable ctor by replacing the for...each + if statements with just the following line of code?
    _clientConnectionId = = new Guid( si.GetString(nameof(ClientConnectionId)));

@malylemire1
Copy link

malylemire1 commented Apr 21, 2020

Hi,
Using
si.GetValue(nameof(ClientConnectionId), typeof(Guid));
would actually invoke the SerializationInfo IFormatterConverter implementation (JsonFormatterConverter) that is used by Json.Net.

This would also support any IFormatterConverter implementations.

I have not tested it but there might be a problem with since the internal JsonReader of the JsonFormatterConverter has already processed the value.

@cheenamalhotra
Copy link
Member

Hi @malylemire1

Could you elaborate your concern with an example and how the PR impacts it?

@malylemire1
Copy link

malylemire1 commented Apr 22, 2020

Hi,

What I mean is that changing

foreach (SerializationEntry siEntry in si)
{
    if ("ClientConnectionId" == siEntry.Name)
    {
        _clientConnectionId = (Guid)siEntry.Value;
        break;
    }
}

To

foreach (SerializationEntry siEntry in si)
{
    if (nameof(ClientConnectionId) == siEntry.Name)
    {
        _clientConnectionId = (Guid)si.GetValue(nameof(ClientConnectionId), typeof(Guid));
        break;
    }
}

Is enough change to SqlException to make the test pass. I have pulled the repo and tried it.
si.GetValue will invoke the JsonFormatterConverter or any IFormatterConverter implementation.

And it will also support any custom json.net converter for Guid

@"""ClientConnectionId"":""GuidUsingCustomConverterValue""}";

@cheenamalhotra
Copy link
Member

Thanks @malylemire1

I've included your suggestion in the PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
No open projects
Development

Successfully merging a pull request may close this issue.

4 participants