SqlClient: LegacyRowVersionNullBehavior causes InvalidCastException when using SqlDataReader.GetFieldValue[T]

Describe the bug

I updated my project, which uses EF Core 5.0.8, to use the latest version SqlClient, 3.0.0, and got an InvalidCastException exception. Turning off SqlServerDbContextOptionsBuilder.EnableRetryOnFailure() prevents the error, but I need it on.

Investigating further, the issue seems to come from SqlDataReader.GetFieldValue[T](Int32 i).

Exception message: Unable to cast object of type 'System.DBNull' to type 'System.Byte[]'.

Stack trace: 
System.InvalidCastException: Unable to cast object of type 'System.DBNull' to type 'System.Byte[]'.
   at Microsoft.Data.SqlClient.SqlDataReader.GetFieldValueFromSqlBufferInternal[T](SqlBuffer data, _SqlMetaData metaData)
   at Microsoft.Data.SqlClient.SqlDataReader.GetFieldValueInternal[T](Int32 i)
   at Microsoft.Data.SqlClient.SqlDataReader.GetFieldValue[T](Int32 i)

To reproduce

I’ve recreated the issue in a repo: https://github.com/andygjp/NullRowVersionIssue

Expected behavior

It should not throw an exception - the GetFieldValueFromSqlBufferInternal() function needs to have the same behaviour as TryReadColumnInternal(Int32, Bool), ie respect LocalAppContextSwitches.LegacyRowVersionNullBehavior

Further technical details

dotnet --info
.NET SDK (reflecting any global.json):
 Version:   5.0.204
 Commit:    84d1fe1bb7

Runtime Environment:
 OS Name:     fedora
 OS Version:  34
 OS Platform: Linux
 RID:         fedora.34-x64
 Base Path:   /usr/lib64/dotnet/sdk/5.0.204/

Host (useful for support):
  Version: 5.0.7
  Commit:  556582d964

.NET SDKs installed:
  3.1.116 [/usr/lib64/dotnet/sdk]
  5.0.204 [/usr/lib64/dotnet/sdk]

.NET runtimes installed:
  Microsoft.AspNetCore.App 3.1.16 [/usr/lib64/dotnet/shared/Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 5.0.7 [/usr/lib64/dotnet/shared/Microsoft.AspNetCore.App]
  Microsoft.NETCore.App 3.1.16 [/usr/lib64/dotnet/shared/Microsoft.NETCore.App]
  Microsoft.NETCore.App 5.0.7 [/usr/lib64/dotnet/shared/Microsoft.NETCore.App]

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Reactions: 2
  • Comments: 15 (12 by maintainers)

Most upvoted comments

Goto !?

Yes. Sometimes I find that it’s the cleanest way to express things. This approach doesn’t duplicate work and clearly separates decision and action. I could break the two action blocks into location functions or dedicated functions but it feels wasteful doing all that work setting up a stackframe and pushing/popping args for such small reasons.

I intend to look at it so you can assign it to me.