【问题标题】:How to set ExecuteSqlCommand with Table variable如何使用表变量设置 ExecuteSqlCommand
【发布时间】: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 好的,谢谢你的帮助。
  • 我已经更新了解释我为什么要这样做的问题。我希望这对你有意义。在我来到这里之前,项目开发人员严重滥用了这个问题的表格。

标签: c# sql entity-framework


【解决方案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
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-02-04
  • 2021-05-16
  • 2019-11-30
  • 2018-01-01
  • 1970-01-01
  • 2011-12-29
相关资源
最近更新 更多