使用域聚合函数(DMin)的C#参数化查询

时间:2016-01-26 22:24:52

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

我想在小型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#代码时,我得到一个“Too few parameters”异常,我检查了拼写,我甚至从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。

5 个答案:

答案 0 :(得分:3)

它看起来相当丑陋,但这适用于我使用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";

但如果text参数值包含单引号,则它会失败。 :(

答案 1 :(得分:3)

通过从DMin()转换为INSERT ... VALUES语句,您可以避免使用INSERT ... SELECT引发质询和注入风险。

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期望接收参数值的顺序。在您的查询版本中,您可以为每个参数名称替换?

答案 2 :(得分:1)

我认为这不会像你想要的那样起作用。为了让您有效地测试它,您需要访问以在Access 中测试查询时提示您输入参数。

Enter Parameter Value dialog

所以我可以运行这个

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之外是不可用的(即直接连接到ACE,就像你通过OleDb一样)。这些是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

答案 3 :(得分:1)

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

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,但你应该明白我的观点

答案 4 :(得分:1)

我不认为你可以这样做。

是的,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注入。)