【问题标题】:C# parameterized query with Domain Aggregate functions (DMin)使用域聚合函数 (DMin) 的 C# 参数化查询
【发布时间】:2016-05-03 16:30:20
【问题描述】:

我想在一个小型 C# 应用程序中使用以下 SQL 语句。

SQL 语句在 Access 2013 中运行完美。

INSERT INTO adressed_to (dialog,listener)
VALUES (
    DMin("id", "FIGURE", "char_name='Doe' AND forename='John'"),
    DMin("id", "DIALOG", "speaker=3 AND dialog_text='some text'")
);

当我尝试运行以下 C# 代码时,我得到一个“参数太少”异常,我三次检查拼写,我什至从 Access 查询中复制了字符串。 在双引号中更改单引号不起作用,我得到了同样的例外。甚至可以在 C# 中运行此查询吗?

其他查询工作正常。

string addListeners = @"INSERT INTO adressed_to (dialog,listener)
    VALUES (
        DMin('id', 'FIGURE', 'char_name =? AND forename =?'),
        DMin('id', 'DIALOG', 'speaker=? AND dialog_text=?')
    ); ";
foreach (Character listener in d.addressed_to)
{
    using (OleDbCommand cmd = new OleDbCommand(addListeners, dbConn))
    {
        cmd.Parameters.AddWithValue("?", listener.name);
        cmd.Parameters.AddWithValue("?", listener.forename);
        cmd.Parameters.AddWithValue("?", speakerID);
        cmd.Parameters.AddWithValue("?", d.dialog_text);
        cmd.ExecuteNonQuery();
    }
}

按照建议将字符串更改为以下内容无效:

@"INSERT INTO adressed_to (dialog,listener)
    VALUES (
        DMin(""id"", ""FIGURE"", ""char_name =? AND forename =?""),
    DMin(""id"", ""DIALOG"", ""speaker=? AND dialog_text=?"")
); ";

例外:

“System.Data.OleDb.OleDbException”类型的异常发生在 System.Data.dll 但未在用户代码中处理 附加信息:参数太少。预计 2。

【问题讨论】:

  • 您在 addListeners 中使用 ' 而不是 "。尝试使用 @"... ""id"",...", double " 是使用 @ 时的转义方式

标签: c# sql ms-access ms-access-2013


【解决方案1】:

它看起来相当丑陋,但这对我使用 Provider=Microsoft.ACE.OLEDB.12.0 有用:

string addListeners =
        @"INSERT INTO adressed_to (dialog,listener)
        VALUES (
            DMin('id', 'FIGURE', 'char_name=""' & ? & '"" AND forename=""' & ? & '""'),
            DMin('id', 'DIALOG', 'speaker=' & ? & ' AND dialog_text=""' & ? & '""')
        ); ";
using (var cmd = new OleDbCommand(addListeners, dbConn))
{
    cmd.Parameters.Add("?", OleDbType.VarWChar, 255).Value = "Doe";
    cmd.Parameters.Add("?", OleDbType.VarWChar, 255).Value = "John";
    cmd.Parameters.Add("?", OleDbType.Integer).Value = 3;
    cmd.Parameters.Add("?", OleDbType.VarWChar, 255).Value = "some text";
    cmd.ExecuteNonQuery();
}

它也适用于

    cmd.Parameters.Add("?", OleDbType.VarWChar, 255).Value = "O'Reilly";

但如果文本参数值包含单独的双引号,它确实会失败。 :(

【讨论】:

  • 从安全角度来看,这可能是迄今为止最丑陋但最省钱的解决方案。 :D 我会用它,双引号可以被截取。谢谢。
【解决方案2】:

您可以通过从INSERT ... VALUES 切换到INSERT ... SELECT 语句来避免DMin() 的报价挑战和注入风险。

INSERT INTO adressed_to (dialog,listener)
SELECT
    Min(FIGURE.id) AS dialog,
    (
        SELECT Min(id)
        FROM DIALOG
        WHERE speaker=[p1] AND dialog_text = [p2]
    ) AS listener
FROM FIGURE
WHERE FIGURE.char_name=[p3] AND FIGURE.forename=[p4];

我使用[p1][p4] 作为参数名称来指示Access 期望接收参数值的顺序。在您的查询版本中,您可以将 ? 替换为每个参数名称。

【讨论】:

  • @SandroKoch 当我在你的previous question 中建议DMin 时,我不知道它会成为c# 中的参数查询。对于那个很抱歉!我认为INSERT ... SELECT 在这里应该更容易。
  • 是的,这是个好东西。我刚刚在 C# 中对其进行了测试,它工作正常,即使对于包含双引号的参数值也是如此。
【解决方案3】:

我不认为你可以这样做。

是的,OLE DB 支持命名参数。但是,这些参数只能用于预期值的地方,而不是值内部。让我通过一个例子来说明我的意思:

这可行:SELECT a FROM myTable WHERE b = ?

这不是:SELECT a FROM myTable WHERE b = 'X ? Y'

在第一个示例中,? 用作值的占位符。在第二个示例中,? 是字符串中的文字问号。

你在做什么符合第二个例子:

INSERT INTO adressed_to (dialog)
VALUES (
    SomeMethod("foo", "bar", "foobar ? baz"),
);

OLE DB 不知道DMin 是Access 数据库的特殊 函数,它提供某种动态SQL 功能。它所看到的只是您正在使用带有问号的字符串文字。因此,问号没有特殊含义。

就个人而言,我会尝试将INSERT INTO ... VALUES ... 重写为INSERT INTO ... SELECT ... 语句,该语句使用标准SQL 聚合方法而不是Access 特定的域聚合函数。

(如果所有其他方法都失败并且您决定使用字符串连接而不是参数:请进行适当的转义和输入清理以避免 SQL 注入。)

【讨论】:

  • 啊,是的,我忘了 D* 函数在 oledb 上不可用
  • @Brad - 我不知道,DMin() worked for me 使用 Microsoft.ACE.OLEDB.12.0。
  • @GordThompson 我对此感到有些惊讶。那么为什么不能使用数据宏中的 D* 函数(如更改前)?我认为这些没有义务在 Access 中运行,这就是它们功能有限的原因?
  • @Brad - 我不能肯定地说,但我的印象是事件驱动的数据宏具有可能超出技术上可能的限制。可能存在与性能相关的限制,或者可能施加一些限制以使它们与 Sharepoint 集成“玩得很好”。
【解决方案4】:

发生的情况是您在每次迭代时不断向此命令对象添加参数,这是一个问题,您应该只在循环之前添加一次,然后在循环内为其分配不同的值。

string addListeners = @"INSERT INTO adressed_to (dialog,listener)
VALUES (
    DMin('id', 'FIGURE', char_name =@CharName AND forename =@Forename),
    DMin('id', 'DIALOG', speaker=@Speacker AND dialog_text=@DialogText)
); ";

using (OleDbCommand cmd = new OleDbCommand(addListeners, dbConn))
{
    cmd.Parameters.Add("@CharName", SqlDbType.VarChar);
    cmd.Parameters.Add("@Forename", SqlDbType.VarChar);
    cmd.Parameters.Add("@Speacker", SqlDbType.VarChar);
    cmd.Parameters.Add("@DialogText", SqlDbType.VarChar);

    foreach (Character listener in d.addressed_to)
    {
        cmd.Parameters["@CharName"].Value = listener.name; 
        cmd.Parameters["@Forename"].Value = listener.forename; 
        cmd.Parameters["@Speacker"].Value = speakerID; 
        cmd.Parameters["@DialogText"].Value = d.dialog_text; 
        cmd.ExecuteNonQuery();
    }
}

PS: 注意我不确定何时可以使用命名占位符或不使用 Oledbcommand,但你应该明白我的意思

【讨论】:

  • 我将循环放在 using 语句之前,以便每次创建新命令,但异常保持不变。
  • @SandroKoch 什么是例外,哦,我想我知道为什么,你在参数周围有单引号
  • @SandroKoch 看到我的编辑,删除引号,它会工作
  • 我担心会打击你的信心,但仅仅删除引号并不能解决问题。 DMin 函数需要引号来确定参数。
【解决方案5】:

我认为这不会像您希望的那样工作。为了让您有效地测试这一点,您需要访问以在您测试查询时提示您输入参数在 Access 中。

所以我可以运行这个

INSERT INTO adressed_to (dialog,listener)
VALUES (
    DMin("id", "FIGURE", "char_name='" & pchar_name& "' AND forename='" & pforename & "'"),
    DMin("id", "DIALOG", "speaker="& pSpeaker & " AND dialog_text='" & pDialog_text & "'")
);

并获得弹出窗口,这意味着当您通过 OLEDB 连接时,参数也将被预期。

这里的缺点是您基本上是在制作动态 SQL 而不是真正的参数化查询。虽然我希望它没问题,因为 Access 一次只会处理一个查询。也就是说,你不会在这里做太多的 SQL 注入,因为 ACE 会抛出异常。

Heinzi 是对的,但 D* 函数在 Access 之外不可用(即,当您像通过 OleDb 一样直接连接到 ACE 时)。这些是 VBA 函数,不在 ACE 的上下文中。您可以将查询编写为直接 SQL。

INSERT INTO adressed_to (dialog,listener)
Select (select min(id) from Figure where  char_name= pchar_name AND forename= pforename)
    , (Select min(id) from DIALOG where speaker= pSpeaker AND dialog_text=pDialog_text) T

【讨论】:

  • 当您尝试使用子查询在 Access INSERT ... VALUES 语句中提供 VALUES 时,Access 会报错“查询输入必须包含至少一个表或查询”。在那种情况下,这似乎并不是最好的信息,但也许 Access 只是感到困惑。 ;-)
  • 您应该能够在任何 Access 查询中使用域聚合函数,无论它是在 Access 会话中运行还是从外部运行并与 OleDb 或 ODBC 连接。
  • @HansUp 是的,你是对的。但是,如果您将VALUES 部分取出并使用常规的选择语句,它就可以正常工作。请参阅我的comment above,了解为什么我对域聚合函数的工作感到困惑。
  • 当我在你的答案中测试了最后一个查询的最新版本时,Access 再次抱怨“查询输入必须包含至少一个表或查询”(并且抱怨似乎更多在这种情况下对我来说是明智的)。如果您在末尾加上FROM Dual,它将起作用,其中Dual 是任何只返回1 行的表或查询的占位符。我的回答有点类似,但不需要额外的表或查询。
猜你喜欢
  • 1970-01-01
  • 2014-02-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-12-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多