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

如何使用非默认列顺序构造Sql Server TVP?,sql,.net,sql-server,Sql,.net,Sql Server,我的场景很简单——我创建一个TVP,将IEnumerable作为其值,并使用它执行查询 我得到的结果毫无意义。因此,我编写了一个TraceSql函数,该函数将SQL及其参数转储,以便复制并粘贴到SSM中 我困惑地发现,当粘贴到SSMS并在那里运行时,我的函数显示的SQL会产生正确的结果 最后,我启动了SQLServerProfiler并找到了谜团的来源 以下是我的TraceSql函数生成的SQL: DECLARE @AdmSiteId Int = 93 DECLARE @src dbo.Tabl

我的场景很简单——我创建一个TVP,将IEnumerable作为其值,并使用它执行查询

我得到的结果毫无意义。因此,我编写了一个TraceSql函数,该函数将SQL及其参数转储,以便复制并粘贴到SSM中

我困惑地发现,当粘贴到SSMS并在那里运行时,我的函数显示的SQL会产生正确的结果

最后,我启动了SQLServerProfiler并找到了谜团的来源

以下是我的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
以下是我如何在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;
  }),
};
我认为我在元数据数组中指定的列顺序定义了序数和字段之间的匹配。但事实并非如此

现在我明白了,最好的方法是更改TVP,以便按照数据库中定义的相同顺序传递列。然而,出于好奇,我如何创建一个具有不同列顺序的TVP,就像我的示例中不起作用的那样

编辑

我的英语显然不足以解释我自己

请检查传递给SqlDataRecord构造函数的SqlMetadata[]参数。物品的顺序重要吗?我想是的。所以,如果我通过

new[]
{
  new SqlMetaData("Id", SqlDbType.Int),
  new SqlMetaData("Checksum", SqlDbType.Int),
  new SqlMetaData("AuxId", SqlDbType.Int)
};
然后,在批插入SQL语句中,它的意思是这样的:指定顺序Id、校验和、AuxId中的列。换句话说,我认为ADO.NET在给定上述元数据的情况下生成的batch insert语句如下所示:

INSERT INTO WhatEver (Id,Checksum,AuxId) VALUES (...),(...),...,(...)
INSERT INTO WhatEver VALUES (...)
INSERT INTO WhatEver VALUES (...)
...
INSERT INTO WhatEver VALUES (...)
我认为采用这种逻辑是合理的。基于这个假设,代码

rec.SetInt32(0, x.ClientId);
rec.SetInt32(1, x.Checksum);
rec.SetInt32(2, x.ClientLegalId);
绝对有效,因为:

序号0是Id元数据-给定ClientId 序号1是校验和元数据-给定校验和 序号2是AuxId元数据-给定ClientLegalized 然而,我偶然发现,元数据项的顺序对ADO.NET内部并不重要,因为它实际生成的insert语句如下所示:

INSERT INTO WhatEver (Id,Checksum,AuxId) VALUES (...),(...),...,(...)
INSERT INTO WhatEver VALUES (...)
INSERT INTO WhatEver VALUES (...)
...
INSERT INTO WhatEver VALUES (...)
这意味着,无论我如何对元数据项排序,实际的insert SQL都是按照数据库模式中列的顺序进行的。当然,还有代码

rec.SetInt32(0, x.ClientId);
rec.SetInt32(1, x.Checksum);
rec.SetInt32(2, x.ClientLegalId);
变得无效,因为在数据库中:

序号1对应于AuxId,现在它被赋予校验和 序号2对应于校验和,现在它被赋予clientlegalized 所以,我的假设是错误的


现在我的问题是,是否仍然可以使用SqlDataRecord对象的集合进行批插入,并具有自定义的列顺序?或者只传递所需的列,依靠默认值和空值来输入?因为不提供显式列列表的insert SQL必须为每一列提供值,即使是可为空的列和默认列。否则,Sql Server吐出的列名称或提供的值的数量与表定义不匹配。

但选择不匹配。 为什么名字不一致

 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])
)

我遇到了这个问题,不幸的是,无法忽略列的顺序。我所能做的唯一一个小小的改进就是打字能力强:

 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);

但是顺序很重要,列的名称和实体的属性名称应该匹配。

TableOfObjectChecksum2充当复合Id为int,int的任何对象的校验和持有者。它是一个通用UDT。在这个特定的示例中,我的对象具有复合键ClientId,ClientLegalId。我有另一种类型的对象,其键为ClientId,ClientContactId。两者都可以由相同的UDT提供服务。select与TVP填充期间传递给SqlDataRecord构造函数的元数据数组中字段的顺序不匹配。这正是我困惑的根源——我认为匹配元数据才是最重要的。真的吗?那是你的地图吗?校验和到AuxId,客户端合法化到校验和。如果是这样的话,你真的需要在命名上下功夫。当然不是。查看元数据数组-var metadata=new[]{new SqlMetaDataId,SqlDbType.Int,new SqlMetaDataChecksum,SqlDbType.Int,new SqlMetaDataAuxId,SqlDbType.Int};-Id首先是校验和,然后是AuxId。因此,我认为这个元数据数组将用于在生成的查询中按该顺序生成列名。但事实并非如此,生成的查询根本不包含列列表,因此使用列在数据库中的显示顺序。我的问题是,我是否可以以某种方式影响生成的查询以包括列列表?我正在寻找-你呢?真的,校验和应该映射到AuxID,ClientLegalId应该映射到[Checksum]?不,你不能。使用订单有什么困难?如果你有一个空值,你只需要传递一个空值。那么默认值呢?假设我想省略我想要默认值的列。你有什么建议吗?如果默认值是GETDATE怎么办?你知道日期时间。现在可能根本不是一回事。假设有很多东西。你试过什么?我花了3分钟才到达
为了让你意识到你的代码中甚至没有正确的顺序,你需要大量的评论。很抱歉,我已经在帖子中明确地解释了这一点。诚然,我的英语还远远不够完美,所以让我们把困惑归咎于此。你能说得更中肯些吗?在我看来,为了在批插入/更新中使用SqlDataRecord项集合,我必须知道数据库方案中定义的默认值,这是一个麻烦。带有GETDATE默认值的示例演示了这一点。你看不出有什么问题吗?我没有责任解释微软产品的行为,因为我不为微软工作。考虑到您花了三次迭代来确认您发布的代码中的不一致性,我理解您将很难处理它。另外,过账表定义没有默认值。