使用域聚合函数 (DMin) 的 C# 参数化查询
C# parameterized query with Domain Aggregate functions (DMin)
我想在小型 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=?"")
); ";
异常:
An exception of type 'System.Data.OleDb.OleDbException' occurred in
System.Data.dll but was not handled in user code
Additional information: Too few parameters. Expected 2.
我认为这不会像您希望的那样工作。为了让您有效地测试它,您需要在 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 之外不可用(即当直接连接到 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
发生的事情是你在每次迭代中不断向这个命令对象添加参数,这是一个问题,你应该只在循环之前添加一次,然后在循环内为它分配一个不同的值。
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,但你应该明白我的意思
我不认为你可以这样做。
是的,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-specific 域聚合函数。
(如果所有其他方法都失败并且您决定使用字符串连接而不是参数:请进行适当的转义和输入清理以避免 SQL 注入。)
它看起来很丑陋,但我使用 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";
但如果文本参数值包含单独的double-quotes,它确实会失败。 :(
您可以通过从 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 期望接收参数值的顺序。在您的查询版本中,您可以将每个参数名称替换为 ?
。
我想在小型 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=?"")
); ";
异常:
An exception of type 'System.Data.OleDb.OleDbException' occurred in System.Data.dll but was not handled in user code Additional information: Too few parameters. Expected 2.
我认为这不会像您希望的那样工作。为了让您有效地测试它,您需要在 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 之外不可用(即当直接连接到 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
发生的事情是你在每次迭代中不断向这个命令对象添加参数,这是一个问题,你应该只在循环之前添加一次,然后在循环内为它分配一个不同的值。
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,但你应该明白我的意思
我不认为你可以这样做。
是的,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-specific 域聚合函数。
(如果所有其他方法都失败并且您决定使用字符串连接而不是参数:请进行适当的转义和输入清理以避免 SQL 注入。)
它看起来很丑陋,但我使用 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";
但如果文本参数值包含单独的double-quotes,它确实会失败。 :(
您可以通过从 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 期望接收参数值的顺序。在您的查询版本中,您可以将每个参数名称替换为 ?
。