如何使用表变量设置 ExecuteSqlCommand

Posted

技术标签:

【中文标题】如何使用表变量设置 ExecuteSqlCommand【英文标题】:How to set ExecuteSqlCommand with Table variable 【发布时间】:2021-12-29 06:28:08 【问题描述】:

我有一个想通过 C# 应用程序运行的查询。在应用程序之外没有执行此操作的选项。我有以下代码:

var keyGroupsToCleanUp = new List<string>

    "Address",
    "Manufacturer",
    "Product",
    "Customer",
    "Picture",
    "Category",
    "Vendor",
    "SS_A_Attachment",
    "SS_A_AttachmentDownload",
    "SS_MAP_EntityMapping",
    "SS_MAP_EntityWidgetMapping",
;

foreach (var keyGroup in keyGroupsToCleanUp)

    _databaseFacade.ExecuteSqlCommand($@"
    DELETE
    FROM GenericAttribute
    WHERE KeyGroup = keyGroup AND [Key] = 'CommonId'
    AND EntityId NOT IN (SELECT Id FROM [keyGroup]);
    ");

我想遍历列表中的每个名称并为每个名称运行以下查询。当我尝试这样做时,我收到以下错误:

System.Data.SqlClient.SqlException (0x80131904): Invalid object name '@p1'.

从网上查到的,这是因为表名不能是字符串。您必须声明一个变量并将此变量用作表名。我了解到一个 Table 变量有需要声明的列,我感到一阵恐惧冲刷着我。这些表都没有相同的列结构。

我正在尝试做的事情可能吗?如果是这样,我该怎么做?


GenericAttributes 表是一个包含六列的大表。

当我加入正在使用它的项目时,它已经被使用到无法替代的地步。通过将 KeyGroup 指定为数据库表,您可以在此处保存数据库表的其他数据。我们有一个名为“地址”的表,我们在 GenericAttributes 表中为地址保存了额外的数据(我知道这没有意义)。这会导致很多问题,因为关系数据库不适用于此。我在上面编写的查询在 GenericAttributes 表中查找现在已分离的行。例如,EntityId 0 在 Address 中不作为 Id 存在,因此将在此处返回。然后必须删除该行,因为它链接到一个不存在的 entityId。

这是一个可以实现这一目标的查询示例:

// Address
_databaseFacade.ExecuteSqlCommand(@"
DELETE
FROM GenericAttribute
WHERE KeyGroup = 'Address' AND [Key] = 'CommonId'
AND EntityId NOT IN (SELECT Id FROM [Address]);
");

我必须为 11 个表执行此操作,所以我想让它更容易执行。每个查询都以相同的方式编写。唯一改变的是 KeyGroup 和它要查找的表。它们都将始终具有相同的名称。

这是另一个产品调用的示例。它们是相同的,唯一的区别是NOT IN语句中的KeyGroup和Table。

// Product
_databaseFacade.ExecuteSqlCommand(@"
DELETE
FROM GenericAttribute
WHERE KeyGroup = 'Product' AND [Key] = 'CommonId'
AND EntityId NOT IN (SELECT Id FROM Product);
");

【问题讨论】:

我理解你的问题。当我在 SSMS 中输入 SQL 命令时,它会起作用。这是一个用于 NopCommerce 框架中的表概念的查询。它被称为 GenericAttributes 表,我希望我从未见过它。你可以在这里阅读更多信息:docs.nopcommerce.com/en/developer/tutorials/db-schema.html @Charlieface 你能举个例子来说明如何做到这一点,还是根本不可能? @Charlieface 好的,谢谢你的帮助。 我已经更新了解释我为什么要这样做的问题。我希望这对你有意义。在我来到这里之前,项目开发人员严重滥用了这个问题的表格。 【参考方案1】:

为确保不存在注入漏洞,可以使用QUOTENAME动态SQL

_databaseFacade.ExecuteSqlRaw(@"
DECLARE @sql nvarchar(max) = N'
    DELETE
    FROM GenericAttribute
    WHERE KeyGroup = @keyGroup AND [Key] = ''CommonId''
    AND EntityId NOT IN (SELECT Id FROM ' + 0 + ');
';

EXEC sp_executesql @sql,
    N'@keyGroup nvarchar(100)',
    @keyGroup = 0;
    ", keyGroup);

注意ExecuteSqlRaw 将如何插入字符串。 不要自己用$插入它

【讨论】:

感谢您的回复。感谢您对像我这样的菜鸟的耐心。我复制了您的代码并将其放入 foreach 外观中。它指出 ExecuteSqlInterpolated 仅使用一个参数,但使用两个参数调用。我最后删除了“,keyGroup”。尝试后,它说“参数类型'string'不可分配给参数类型'System.FormattableString'”。我查找了 FormattableString 是什么,但我看不出错误想说什么。查询是 FormattableString 吗? 我尝试使用 ExecuteSqlRaw 进行查询,发现“CommonId”的语法错误。在不更改任何内容的情况下,它给出了错误:“Microsoft.Data.SqlClient.SqlException(0x80131904):'CommonId' 附近的语法不正确。必须声明标量变量“@sql”。”然后我删除了 CommonId 周围的单个 qoutes 并再次运行它并得到以下错误:“Microsoft.Data.SqlClient.SqlException (0x80131904): Must declare the scalar variable "@keyGroup"." 我环顾四周,它看起来不错,但它说“Microsoft.Data.SqlClient.SqlException (0x80131904): ')' 附近的语法不正确。”。我用它玩了一下,但我找不到错误。 多余的逗号,现已修复 非常感谢!它奏效了,现在我可以研究它,看看我将来应该做什么!【参考方案2】:

猜测,您使用的是 Entity Framework Core。 ExecuteSqlCommand 方法接受FormattableString,并将任何占位符转换为命令参数。但是您的占位符似乎是列/表名称,不能作为参数传递。

由于还有一个接受 string 的重载,它具有不同的行为,因此此方法已被标记为过时,并由 ExecuteSqlInterpolatedExecuteSqlRaw 取代。

假设您的任何价值观都不会受到用户的影响,并且您很高兴您不打算介绍a SQL Injection vulnerability,您可以改用ExecuteSqlRaw

_databaseFacade.ExecuteSqlRaw($@"
    DELETE
    FROM GenericAttribute
    WHERE KeyGroup = [keyGroup] AND [Key] = 'CommonId'
    AND EntityId NOT IN (SELECT Id FROM [keyGroup]);
    ");

【讨论】:

你好 Richard,我想我需要一个名为“Microsoft.EntityFrameworkCore.Relational”的 Nuget 包?我尝试安装它,基于另一个答案。我的项目与此不兼容。我有一个 netcoreapp2.1 应用程序。我会考虑将我的应用程序移植到更新的版本。 如果您使用的是 Entity Framework Core,您应该已经安装了该包。如果您仍在使用 Entity Framework 6,那么我的回答不适用。您使用的是哪种 ORM? 来自文档:“永远不要将带有未经验证的用户提供的值的连接或插值字符串 ($"") 传递给 FromSqlRawExecuteSqlRaw正确使用ExecuteSqlInterpolated @Charlieface 因此我对 SQL 注入发出警告。但是,您不能将表或列名作为参数传递。【参考方案3】:

尝试以下操作:

foreach (var keyGroup in keyGroupsToCleanUp)

    var sql = @"DELETE FROM GenericAttribute
                WHERE KeyGroup = @Group
                AND [Key] = 'CommonId'
                AND EntityId NOT IN (SELECT Id FROM @Group)"; // Or [@Group], depends on schema

    _databaseFacade.ExecuteSqlCommand(
        sql,
        new SqlParameter("@Group", keyGroup));

此代码假定您的外观中的 ExecuteSqlCommand 遵循标准的 Microsoft 模式(与 Microsoft 的相同覆盖)。

【讨论】:

我收到一个错误:“System.InvalidCastException:SqlParameterCollection 只接受非空的 SqlParameter 类型对象,不接受 SqlParameter 对象。” 这可能是因为您可能使用了较旧的 Sql Client。在适用的情况下,在您的项目中将 using System.Data.SqlClient 更改为 using Microsoft.Data.SqlClient。这需要 &lt;PackageReference Include="Microsoft.Data.SqlClient" Version="1.1.2" /&gt; 在您的 .csproj 文件中(版本可能不同,请使用最新的 nuget)。 我在回复我的回复之前查看了错误,并找到了您引用的同一帖子。我开始使用 Microsoft.Data.SqlClient。在我的整个项目中没有对 System.Data.SqlClient 的引用。 唯一想到的其他东西是this。

以上是关于如何使用表变量设置 ExecuteSqlCommand的主要内容,如果未能解决你的问题,请参考以下文章

VBA 使用变量表名设置记录

如何将数据设置为临时表中的变量?

如何将DEFINE变量设置为等于PL / SQL中另一个已定义表的选择

spss如何输出变量说明表

如何在 python 中使用 sqlalchemy 在查询中创建 sql server 表变量

如何在具有不同库共享变量的 monorepo 中管理 SCSS 样式表?