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列はnullではなく、0xです。
これは、アプリサーバーで結果をデシリアライズするために使用するDapperを混乱させ、行のFooTable部分のオブジェクト(空のバイト配列のみを含む無効なオブジェクト)を構築する必要があると考えます。
この予想外の振る舞いには何らかの理由がありますか?
SQLステートメントでCASEを使用して値をnullに変換し直すか、アプリケーションコードで処理するよりも優れた解決策はありますか?
これは、.NETのSqlClientのバグのようです。結果でnull
とマークされていても、列は空のbyte[]
として返されます。
例えば
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にそのまま残される可能性があります。
「バグです」という正しい承認された回答を除いて、あなたができることは、ypercubeがコメントで提案するものです:VARBINARY
にキャストしてください
SELECT ft.[Id],
CAST(ft.[RowVersion] AS varbinary(8)),
bt.[Id]
FROM FooTable ft
FULL OUTER JOIN BarTable bt
ON ft.Id = Bt.Id ;