为什么使用外连接选择 rowversion 永远不会返回 null

Iga*_*and 6 join sql-server sql-server-2014

使用 SQL Server 2014。

给定带有rowversion列的表,要加入的其他表和这样的选择:

 CREATE TABLE dbo.FooTable(
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [RowVersion] [rowversion] NULL,
 CONSTRAINT [PK_FooTable] PRIMARY KEY CLUSTERED 
(
    [Id] ASC
)) 

 CREATE TABLE dbo.BarTable(
    [Id] [int] IDENTITY(1,1) NOT NULL
 CONSTRAINT [PK_BarTable] PRIMARY KEY CLUSTERED 
(
    [Id] ASC
)) 

Insert into BarTable default values
GO


SELECT *
  FROM FooTable ft
  FULL OUTER JOIN BarTable bt on ft.Id = Bt.Id
Run Code Online (Sandbox Code Playgroud)

选择的结果是:

Id  RowVersion  Id
NULL    0x      1
Run Code Online (Sandbox Code Playgroud)

如果与 BarTable 不匹配,则RowVersion 列不是 null,而是 0x

这让我们用来在应用服务器上反序列化结果的 Dapper 感到困惑,认为它应该为行的 FooTable 部分构造一个对象——一个无效的对象,只包含一个空字节数组。

这种相当意外的行为有什么原因吗?

有没有比在 sql 语句中使用 CASE 将值转换回 null 或在应用程序代码中处理它更聪明的解决方案?

Dav*_*oft 4

这似乎是 .NET 的 SqlClient 中的一个错误。byte[]尽管该列null在结果中被标记为空,但它仍被返回为空。

例如

using System;
using System.Data.Odbc;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using System.Threading;
using System.Threading.Tasks;
using System.Transactions;

namespace ConsoleApp14
{
    class Program
    {

        static void Main(string[] args)
        {

            using (var con = new SqlConnection("Server=localhost;database=tempdb;Integrated Security=true"))
            {
                con.Open();
                var cmd = con.CreateCommand();
                cmd.CommandText = "select cast(null as rowversion) rv";
                using (var rdr = cmd.ExecuteReader())
                {
                    rdr.Read();
                    var allowDbNull = rdr.GetColumnSchema()[0].AllowDBNull;
                    var isNull = rdr.IsDBNull(0);
                    var val = rdr[0];

                    Console.WriteLine($"SqlClient: AllowDbNull {allowDbNull} IsDbNull: {isNull} {val.GetType().Name} {val}");

                }
            }


            using (var con = new SqlConnection("Server=localhost;database=tempdb;Integrated Security=true"))
            {
                con.Open();
                var cmd = con.CreateCommand();
                cmd.CommandText = "select @val = cast(null as rowversion) ";

                var p = cmd.Parameters.Add(new SqlParameter("@val", System.Data.SqlDbType.Timestamp));
                p.Direction = System.Data.ParameterDirection.Output;

                cmd.ExecuteNonQuery();
                {

                   SqlBinary val = (SqlBinary) p.SqlValue;
                   Console.WriteLine($"SqlClient (parameter): IsDbNull: {val.IsNull} {val.GetType().Name} {val}");

                }
            }

            using (var con = new OdbcConnection("Driver={ODBC Driver 17 for SQL Server};Server=localhost;Trusted_Connection=yes"))
            {
                con.Open();
                var cmd = con.CreateCommand();
                cmd.CommandText = "select cast(null as rowversion) rv";
                using (var rdr = cmd.ExecuteReader())
                {
                    rdr.Read();
                    var allowDbNull = rdr.GetSchemaTable().Rows[0]["AllowDBNull"];
                    var isNull = rdr.IsDBNull(0);
                    var val = rdr[0];

                    Console.WriteLine($"ODBC:      AllowDbNull {allowDbNull} IsDbNull: {isNull} {val.GetType().Name} {val}");

                }

            }


        }
    }
}
Run Code Online (Sandbox Code Playgroud)

输出

SqlClient: AllowDbNull True IsDbNull: False Byte[] System.Byte[]
SqlClient (parameter): IsDbNull: True SqlBinary Null
ODBC:      AllowDbNull True IsDbNull: True DBNull
Run Code Online (Sandbox Code Playgroud)

我在https://github.com/dotnet/SqlClient/issues/255上打开了一个问题 ,但它很可能会作为 WontFix 关闭。根据源中的注释

// Dev10 Bug #479607 - this should have been the same as SqlDbType.Binary, but it's a rejected breaking change
Run Code Online (Sandbox Code Playgroud)

该问题已被提出,但并未作为重大更改得到解决。它可能会在 .NET Core 中得到修复,无论如何,它都充满了重大更改,并在 .NET Framework 中保持原样。

  • 从代码中的注释来看,这是一个已知问题https://github.com/dotnet/SqlClient/blob/master/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDataReader。 cs#L2905 - 也在这里 https://github.com/dotnet/SqlClient/blob/master/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs#L5989 (3认同)