如何使用表变量设置 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
的重载,它具有不同的行为,因此此方法已被标记为过时,并由 ExecuteSqlInterpolated
和 ExecuteSqlRaw
取代。
假设您的任何价值观都不会受到用户的影响,并且您很高兴您不打算介绍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? 来自文档:“永远不要将带有未经验证的用户提供的值的连接或插值字符串 ($""
) 传递给 FromSqlRaw
或 ExecuteSqlRaw
”正确使用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
。这需要 <PackageReference Include="Microsoft.Data.SqlClient" Version="1.1.2" />
在您的 .csproj
文件中(版本可能不同,请使用最新的 nuget)。
我在回复我的回复之前查看了错误,并找到了您引用的同一帖子。我开始使用 Microsoft.Data.SqlClient。在我的整个项目中没有对 System.Data.SqlClient 的引用。
唯一想到的其他东西是this。以上是关于如何使用表变量设置 ExecuteSqlCommand的主要内容,如果未能解决你的问题,请参考以下文章
如何将DEFINE变量设置为等于PL / SQL中另一个已定义表的选择