Skip to content

ArgumentNullException on SqlDataRecord.GetValue when using Udt data type #2445

@LimeyChris

Description

@LimeyChris

Describe the bug

ArgumentNullException is thrown when calling GetValue on a SqlDataRecord for a column type defined as SqlMetaData("geog", SqlDbTypeUdt, typeof(SqlGeography))

Exception message: System.ArgumentNullException: Value cannot be null. (Parameter 'key')
Stack trace:
System.Collections.Concurrent.dll!System.ThrowHelper.ThrowArgumentNullException(string name) Line 14
System.Collections.Concurrent.dll!System.Collections.Concurrent.ConcurrentDictionary<System.Type, Microsoft.Data.SqlClient.Server.Serializer>.TryGetValue(System.Type key, out Microsoft.Data.SqlClient.Server.Serializer value) Line 476
Microsoft.Data.SqlClient.dll!Microsoft.Data.SqlClient.Server.SerializationHelperSql9.GetSerializer(System.Type t) Line 77
Microsoft.Data.SqlClient.dll!Microsoft.Data.SqlClient.Server.SerializationHelperSql9.Deserialize(System.IO.Stream s, System.Type resultType) Line 53
Microsoft.Data.SqlClient.dll!Microsoft.Data.SqlClient.Server.ValueUtilsSmi.GetUdt_LengthChecked(Microsoft.Data.SqlClient.Server.SmiEventSink_Default sink, Microsoft.Data.SqlClient.Server.ITypedGettersV3 getters, int ordinal, Microsoft.Data.SqlClient.Server.SmiMetaData metaData) Line 2263
Microsoft.Data.SqlClient.dll!Microsoft.Data.SqlClient.Server.ValueUtilsSmi.GetValue(Microsoft.Data.SqlClient.Server.SmiEventSink_Default sink, Microsoft.Data.SqlClient.Server.ITypedGettersV3 getters, int ordinal, Microsoft.Data.SqlClient.Server.SmiMetaData metaData, object context) Line 1033
Microsoft.Data.SqlClient.dll!Microsoft.Data.SqlClient.Server.ValueUtilsSmi.GetValue200(Microsoft.Data.SqlClient.Server.SmiEventSink_Default sink, Microsoft.Data.SqlClient.Server.SmiTypedGetterSetter getters, int ordinal, Microsoft.Data.SqlClient.Server.SmiMetaData metaData, object context) Line 926
Microsoft.Data.SqlClient.dll!Microsoft.Data.SqlClient.Server.SqlDataRecord.GetValueFrameworkSpecific(int ordinal) Line 23
Microsoft.Data.SqlClient.dll!Microsoft.Data.SqlClient.Server.SqlDataRecord.GetValue(int ordinal) Line 61
SqlGeographyTest.dll!SqlGeographyTest.Program.Main(string[] args) Line 20

To reproduce

The following code can be used to reproduce the issue.

using Microsoft.Data.SqlClient.Server;
using Microsoft.SqlServer.Types;

using System.Data;

namespace SqlGeographyTest
{
	class Program
	{
		static void Main(string[] args)
		{
			SqlGeography geog = SqlGeography.Point(43, -81, 4326);

			SqlMetaData[] metadata = new SqlMetaData[] { new SqlMetaData("geog", SqlDbType.Udt, typeof(SqlGeography), "Geography") };

			SqlDataRecord record = new SqlDataRecord(metadata);

			record.SetValue(0, geog);

			var checkValue = record.GetValue(0);

		}
	}
}

After debugging, the issue appears to arise from the following code in the SqlMetaDataToSmiExtendedMetaData method in MetaDataUtilsSmi (Microsoft.Data.SqlClient.Server).

            return new SmiExtendedMetaData(source.SqlDbType,
                                            source.MaxLength,
                                            source.Precision,
                                            source.Scale,
                                            source.LocaleId,
                                            source.CompareOptions,
#if NETFRAMEWORK
                                            source.Type,
#else
                                            null,
#endif
                                            source.Name,
                                            typeSpecificNamePart1,
                                            typeSpecificNamePart2,
                                            typeSpecificNamePart3);

In .NET 8, the type is set to null instead of source.Type due to the #if NETFRAMEWORK directive.

Then when SqlDataRecord.GetValue is called, the Deserialize method throws an exception because it tries to use a null type.

See GetUdt_LengthChecked method in ValueUtilsSmi (Microsoft.Data.SqlClient.Server). metaData.Type is null.

Stream stream = new SmiGettersStream(sink, getters, ordinal, metaData);
result = SerializationHelperSql9.Deserialize(stream, metaData.Type);

Expected behavior

The call to GetValue should return the value that was set in the call to SetValue.

Further technical details

Microsoft.Data.SqlClient version: 5.2.0
Microsoft.SqlServer.Server: 1.0.0
Microsoft.SqlServer.Types: 160.1000.6
.NET target: .NET 8
Operating system: Windows 11

Additional context

Metadata

Metadata

Assignees

No one assigned

    Labels

    Repro Available ✔️Issues that are reproducible with repro provided.

    Type

    No type

    Projects

    Status

    Closed

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions