简体   繁体   English

如何使用非默认列顺序构造 Sql Server TVP?

[英]How to construct Sql Server TVP with a non default order of columns?

My scenario is simple - I create a TVP with an IEnumerable<SqlDataRecord> as its value and execute the query using it.我的场景很简单 - 我创建了一个以IEnumerable<SqlDataRecord>作为其值的 TVP 并使用它执行查询。

The results I get back do not make any sense.我得到的结果没有任何意义。 So, I wrote a TraceSql function that dumps the SQL along with its parameters in a way ready to be copied and pasted into the SSMS.因此,我编写了一个TraceSql函数,该函数以一种准备好复制并粘贴到 SSMS 中的方式转储 SQL 及其参数。

I was puzzled to discover, that the SQL displayed by my function produces the correct results, when pasted to SSMS and ran there.我很困惑地发现,当粘贴到 SSMS 并在那里运行时,我的函数显示的 SQL 会产生正确的结果。

Finally, I started Sql Server profiler and found out the source of the mystery.最后,我启动了Sql Server profiler,找到了其中的奥秘。

Here is the SQL produced by my TraceSql function:这是我的TraceSql函数生成的 SQL:

DECLARE @AdmSiteId Int = 93
DECLARE @src dbo.TableOfObjectChecksum2
INSERT INTO @src (Id,Checksum,AuxId) VALUES
 (8395,258295360,1)
,(8395,1114574098,2)
,(8395,-19039848,3)
,(8395,337145572,4)
,(8395,1083939112,5)


SELECT ISNULL(src.Id, dst.ClientId) Id, ISNULL(src.AuxId, dst.ClientLegalId) AuxId,
  CASE
    WHEN src.Checksum IS NULL THEN 0
    WHEN dst.Checksum IS NOT NULL THEN 1
    ELSE 2
  END Checksum
FROM @src src
FULL JOIN AdmCustomerInfoLegal dst ON src.Id = dst.ClientId AND src.AuxId = dst.ClientLegalId
LEFT JOIN AdmClientSite cs ON cs.AdmClientMasterId = dst.ClientId
WHERE (cs.AdmSiteId = @AdmSiteId OR cs.AdmSiteId IS NULL) AND (src.Checksum IS NULL OR dst.Checksum IS NULL OR src.Checksum <> dst.Checksum)
ORDER BY Checksum, Id, AuxId

Now here is the SQL as captured by the profiler:现在这里是分析器捕获的 SQL:

declare @p4 dbo.TableOfObjectChecksum2
insert into @p4 values(8395,258295360,1)
insert into @p4 values(8395,1114574098,2)
insert into @p4 values(8395,-19039848,3)
insert into @p4 values(8395,337145572,4)
insert into @p4 values(8395,1083939112,5)

exec sp_executesql N'
SELECT ISNULL(src.Id, dst.ClientId) Id, ISNULL(src.AuxId, dst.ClientLegalId) AuxId, 
  CASE 
    WHEN src.Checksum IS NULL THEN 0
    WHEN dst.Checksum IS NOT NULL THEN 1
    ELSE 2 
  END Checksum
FROM @src src
FULL JOIN AdmCustomerInfoLegal dst ON src.Id = dst.ClientId AND src.AuxId = dst.ClientLegalId
LEFT JOIN AdmClientSite cs ON cs.AdmClientMasterId = dst.ClientId
WHERE (cs.AdmSiteId = @AdmSiteId OR cs.AdmSiteId IS NULL) AND (src.Checksum IS NULL OR dst.Checksum IS NULL OR src.Checksum <> dst.Checksum)
ORDER BY Checksum, Id, AuxId',N'@AdmSiteId int,@src [dbo].[TableOfObjectChecksum2] READONLY',@AdmSiteId=93,@src=@p4

Notice the INSERT statements in the SQL captured by the profiler do not specify the column list.请注意,探查器捕获的 SQL 中的INSERT语句未指定列列表。 However, when I create the TVP I specify a different order of the columns.但是,当我创建 TVP 时,我指定了不同的列顺序。

Here is the schema of the dbo.TableOfObjectChecksum2 table UDT:这是dbo.TableOfObjectChecksum2表 UDT 的架构:

CREATE TYPE [dbo].[TableOfObjectChecksum2] AS TABLE(
    [Id] [int] NOT NULL,
    [AuxId] [int] NOT NULL,
    [Checksum] [int] NOT NULL,
    PRIMARY KEY ([Id],[AuxId])
)

Here is how I create the respective TVP in C#:以下是我在 C# 中创建相应 TVP 的方法:

var items = GetItemsSomehow();
var metadata = new[]
{
  new SqlMetaData("Id", SqlDbType.Int),
  new SqlMetaData("Checksum", SqlDbType.Int),
  new SqlMetaData("AuxId", SqlDbType.Int)
};
new SqlParameter("src", SqlDbType.Structured)
{
  TypeName = "dbo.TableOfObjectChecksum2",
  SqlValue = items.Select(x =>
  {
    var rec = new SqlDataRecord(metadata);
    rec.SetInt32(0, x.ClientId);
    rec.SetInt32(1, x.Checksum);
    rec.SetInt32(2, x.ClientLegalId);
    return rec;
  }),
};

I thought that the column order I specified in the metadata array defines the matching between the ordinals and the fields.我以为我在元数据数组中指定的列顺序定义了序数和字段之间的匹配。 But it is not.但事实并非如此。

Now I understand that the best is to change my TVP to pass the columns in the same order as defined in the database.现在我明白最好的方法是更改​​我的 TVP 以按照数据库中定义的相同顺序传递列。 However, out of curiousity, how am I to create a TVP with a different column order, like in my example (which does not work)?但是,出于好奇,我如何创建具有不同列顺序的 TVP,就像我的示例(不起作用)一样?

EDIT编辑

My English is obviously inadequate to explain myself.我的英语显然不足以解释我自己。

Please, examine the SqlMetadata[] argument passed to SqlDataRecord constructor.请检查传递给SqlDataRecord构造函数的SqlMetadata[]参数。 Does the order of the items matter?物品的顺序重要吗? I thought it does.我以为可以。 So, if I pass所以,如果我通过

new[]
{
  new SqlMetaData("Id", SqlDbType.Int),
  new SqlMetaData("Checksum", SqlDbType.Int),
  new SqlMetaData("AuxId", SqlDbType.Int)
};

then it means something like this "In the batch insert SQL statement specify the columns in the order Id,Checksum,AuxId".那么它的意思是这样的“在批量插入 SQL 语句中,按照 Id、Checksum、AuxId 的顺序指定列”。 In other words, I thought that the batch insert statement generated by ADO.NET given the aforementioned metadata is something like this:换句话说,我认为在给定上述元数据的情况下,ADO.NET 生成的批量插入语句是这样的:

INSERT INTO WhatEver (Id,Checksum,AuxId) VALUES (...),(...),...,(...)

I think it is legitimate to have assumed such a logic.我认为假设这样的逻辑是合理的。 Given this assumption the code鉴于此假设代码

rec.SetInt32(0, x.ClientId);
rec.SetInt32(1, x.Checksum);
rec.SetInt32(2, x.ClientLegalId);

is absolutely valid, because:绝对有效,因为:

  • ordinal 0 is Id metadata - given ClientId序号 0 是 Id 元数据 - 给定 ClientId
  • ordinal 1 is Checksum metadata - given Checksum序号 1 是校验和元数据 - 给定校验和
  • ordinal 2 is AuxId metadata - given ClientLegalId序号 2 是 AuxId 元数据 - 给定 ClientLegalId

However, what I have stumbled upon is that the order of the metadata items does not matter to ADO.NET internals, because the insert statement actually produced by it is something like this:但是,我偶然发现元数据项的顺序与 ADO.NET 内部无关,因为它实际生成的插入语句是这样的:

INSERT INTO WhatEver VALUES (...)
INSERT INTO WhatEver VALUES (...)
...
INSERT INTO WhatEver VALUES (...)

Meaning, no matter how I order the metadata items, the actual insert SQL goes by the order of the columns in the database schema.意思是,无论我如何对元数据项进行排序,实际的插入 SQL 都是按照数据库模式中列的顺序进行的。 And of course, the code当然,代码

rec.SetInt32(0, x.ClientId);
rec.SetInt32(1, x.Checksum);
rec.SetInt32(2, x.ClientLegalId);

becomes invalid, because in the database:变得无效,因为在数据库中:

  • ordinal 1 corresponds to AuxId and now it is given the Checksum序号 1 对应于 AuxId,现在它被赋予校验和
  • ordinal 2 corresponds to Checksum and now it is given the ClientLegalId序号 2 对应于校验和,现在它被赋予 ClientLegalId

So, my assumption is wrong.所以,我的假设是错误的。

Now my question - is it still possible to batch insert using a collection of SqlDataRecord objects and have a custom order of the columns?现在我的问题 - 是否仍然可以使用SqlDataRecord对象集合批量插入并自定义列顺序? Or pass only the required columns, relying on the defaults and NULLs to kick in?还是仅传递所需的列,依靠默认值和 NULL 来启动? Because an insert SQL without giving an explicit column list must provide the values for each and every column, even the nullable and default ones .因为没有给出显式列列表的插入 SQL 必须提供每一列的值,即使是可为空的和默认的 Otherwise Sql Server spits out Column name or number of supplied values does not match table definition.否则 Sql Server 会吐出列名或提供的值数量与表定义不匹配。

I'm experiencing this issue and unfortunately there's no way to ignore the order of columns.我遇到了这个问题,不幸的是,无法忽略列的顺序。 The only minor improvement I was able to do was strong-typing:我能够做的唯一小改进是强类型:

 var record = new SqlDataRecord(
        new SqlMetaData(nameof(x.Id), SqlDbType.Int),
        new SqlMetaData(nameof(x.Checksum), SqlDbType.Int),
        new SqlMetaData(x.nameof(x.AuxId), SqlDbType.Int));
    
    var index = record.GetOrdinal(nameof(x.Id));
    record.SetInt32(index, x.Id);
    index = record.GetOrdinal(nameof(x.Checksum));
    record.SetInt32(index, x.Checksum);
    index = record.GetOrdinal(nameof(x.AuxId));
    record.SetInt32(index, x.AuxId);

Still the order matters and the name of columns and entities' property name should match.顺序仍然很重要,列的名称和实体的属性名称应该匹配。

But the select does not match.但是选择不匹配。
And why inconsistent names?为什么名称不一致?

 rec.SetInt32(0, x.ClientId);
 rec.SetInt32(1, x.Checksum);
 rec.SetInt32(2, x.ClientLegalId);


CREATE TYPE [dbo].[TableOfObjectChecksum2] AS TABLE(
    [Id] [int] NOT NULL,
    [AuxId] [int] NOT NULL,
    [Checksum] [int] NOT NULL,
    PRIMARY KEY ([Id],[AuxId])
)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM