Sql-Server

為什麼使用外連接選擇 rowversion 永遠不會返回 null

  • October 13, 2019

使用 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

選擇的結果是:

Id  RowVersion  Id
NULL    0x      1

在與 BarTable 不匹配的地方,RowVersion 列不為空,而是 0x

這讓我們用來反序列化應用伺服器上的結果的 Dapper 感到困惑,認為它應該為行的 FooTable 部分構造一個對象——一個無效的對象,只包含一個空字節數組。

這種相當出乎意料的行為有什麼原因嗎?

有沒有比在 sql 語句中使用 CASE 將值轉換回 null 或在應用程式碼中處理它更智能的解決方案?

這似乎是 .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}");

               }

           }


       }
   }
}

輸出

SqlClient: AllowDbNull True IsDbNull: False Byte[] System.Byte[]
SqlClient (parameter): IsDbNull: True SqlBinary Null
ODBC:      AllowDbNull True IsDbNull: True DBNull

我在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

該問題已被提出,但並未作為重大更改進行修復。它可能會在 .NET Core 中得到修復,無論如何它都充滿了重大變化,並在 .NET Framework 中保持原樣。

引用自:https://dba.stackexchange.com/questions/250744