问题
I want to use the following SQL statement in a small C# application.
The SQL statement runs perfect in 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'")
);
When i try to run the following C# code, i get a "Too few parameters" exception, i tripple checked the spelling, i even copied the string from the Access query. Changing the single quotes in double quotes did not work, i got the same exception. Is it even possible to run this query within C#?
Other queries are working fine.
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();
}
}
Changing the string to the following as suggested did not work:
@"INSERT INTO adressed_to (dialog,listener)
VALUES (
DMin(""id"", ""FIGURE"", ""char_name =? AND forename =?""),
DMin(""id"", ""DIALOG"", ""speaker=? AND dialog_text=?"")
); ";
The exception:
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.
回答1:
It looks rather ugly, but this works for me using 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();
}
It also works okay with
cmd.Parameters.Add("?", OleDbType.VarWChar, 255).Value = "O'Reilly";
but it does fail if the text parameter value contains solitary double-quotes. :(
回答2:
You can avoid the quote challenges with DMin()
, and injection risk, by switching from an INSERT ... VALUES
to an INSERT ... SELECT
statement.
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];
I used [p1]
through [p4]
as the parameter names to indicate the order Access will expect to receive the parameter values. In your version of the query, you can substitue ?
for each parameter name.
回答3:
I don't think that you can do it like this.
Yes, OLE DB supports named parameters. However, these parameters can only be used where a value is expected, not inside a value. Let me show what I mean by way of an example:
This works: SELECT a FROM myTable WHERE b = ?
This doesn't: SELECT a FROM myTable WHERE b = 'X ? Y'
In the first example, ?
serves as a placeholder for a value. In the second example, ?
is a literal question mark inside a string.
What you are doing matches the second example:
INSERT INTO adressed_to (dialog)
VALUES (
SomeMethod("foo", "bar", "foobar ? baz"),
);
OLE DB has no idea that DMin
is a special function for Access databases which provides some kind of dynamic SQL functionality. All it sees is that you are using a string literal with question mark inside. Thus, the question mark has no special meaning.
Personally, I would try to rewrite the INSERT INTO ... VALUES ...
to an INSERT INTO ... SELECT ...
statement which uses standard SQL aggregation methods instead of the Access-specific domain aggregate functions.
(If all else fails and you decide to use string concatenation instead of parameters: Please do proper escaping and input sanitation to avoid SQL injection.)
回答4:
What is happenning is you keep adding params to this command object at each iteration, that is a problem, you should only add it once before the loop, and then assign it a different value inside of the loop.
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: Note Im not sure when if you can use named placeholder or not using Oledbcommand, but you should get my point
回答5:
I don't think this is going to work like you want it to. In order for you to effectively test this you need access to prompt you for a parameter when you test the query in Access.
So I can run this
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 & "'")
);
and get the popups which means that when you connect over OLEDB the parameters will also be anticipated.
The disadvantage here is that you are basically making dynamic SQL and not a true parameterized query. Though I expect it is ok because Access will only process one query at a time. i.e. you're not going to do much SQL injection here because ACE will throw an exception.
Heinzi is right though, the D* functions are not available outside of Access (i.e. when connecting directly to ACE like you would be via OleDb). These are VBA functions and that is not in context for ACE. You can write your query as straight 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
来源:https://stackoverflow.com/questions/35025218/c-sharp-parameterized-query-with-domain-aggregate-functions-dmin