2

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.

Gord Thompson
  • 116,920
  • 32
  • 215
  • 418
Sandro Koch
  • 303
  • 1
  • 4
  • 11
  • You are using ' instead of " in addListeners. Try using @"... ""id"",...", double " is the way to escape when using @ – tede24 Jan 26 '16 at 22:35

5 Answers5

3

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. :(

Gord Thompson
  • 116,920
  • 32
  • 215
  • 418
  • From the safety-perspective, this might be the ugliest, but savest solution so far. :D I will use it, the double quotes can be intercepted. Thank you. – Sandro Koch Jan 26 '16 at 23:44
3

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.

HansUp
  • 95,961
  • 11
  • 77
  • 135
  • @SandroKoch When I suggested `DMin` in your [previous question](http://stackoverflow.com/q/35021840/77335), I had no idea it would become a parameter query in c#. Sorry about that! I think `INSERT ... SELECT` should be easier here. – HansUp Jan 27 '16 at 00:08
  • 1
    Yup, this is good stuff. I just tested it in C# and it worked, even for parameter values that contain a double-quote. – Gord Thompson Jan 27 '16 at 00:17
1

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.

Enter Parameter Value dialog

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
Brad
  • 11,934
  • 4
  • 45
  • 73
  • When you try to use subqueries to supply `VALUES` in an Access `INSERT ... VALUES` statement, Access complains *"Query input must contain at least one table or query"*. That doesn't really seem like the best message in that situation, but perhaps Access just gets confused. ;-) – HansUp Jan 27 '16 at 00:16
  • You should be able to use Domain Aggregate functions in any Access query, regardless of whether it is run within an Access session or from outside and connecting with OleDb or ODBC. – HansUp Jan 27 '16 at 00:27
  • @HansUp yes you're right. But if you take the `VALUES` part out and instead use a regular select statement it works fine. See my [comment above](http://stackoverflow.com/questions/35025218/c-sharp-run-a-parametrized-access-domain-aggregate-function/35025517?noredirect=1#comment57779438_35025763) for why i was confused about the domain aggregate functions working. – Brad Jan 27 '16 at 00:37
  • 1
    When I tested the latest version of the last query in your answer, Access complained *"Query input must contain at least one table or query"* again (and the complaint seems more sensible to me in that context). It will work if you tack `FROM Dual` on the end, where `Dual` is a placeholder for any table or query which returns only 1 row. My answer is somewhat similar but does not require an extra table or query. – HansUp Jan 27 '16 at 00:49
1

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

meda
  • 45,103
  • 14
  • 92
  • 122
  • I put the loop before the using statement to create every time a new command, but the exception stays the same. – Sandro Koch Jan 26 '16 at 23:02
  • @SandroKoch what is the exeption, oh I think I know why, you have the single quotes around the params – meda Jan 26 '16 at 23:03
  • @SandroKoch see my edit, remove the quotes and it will work – meda Jan 26 '16 at 23:05
  • I am afraid to damp your confidence, but just removing the quotes did not do the trick. The DMin function needs the quotes to determine the parameter. – Sandro Koch Jan 26 '16 at 23:22
1

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.)

Heinzi
  • 167,459
  • 57
  • 363
  • 519
  • Ah yes, I forgot that the D* functions are not available over oledb – Brad Jan 26 '16 at 23:31
  • 1
    @Brad - I dunno, `DMin()` [worked for me](http://stackoverflow.com/a/35026074/2144390) with Microsoft.ACE.OLEDB.12.0. – Gord Thompson Jan 26 '16 at 23:34
  • @GordThompson I'm a little surprised by that. Why are you not able to use the D* functions from a Data Macro (like Before Change) then? I'm thought these were not obligated to be running within Access and that's why they have the limited functionality? – Brad Jan 27 '16 at 00:12
  • 1
    @Brad - I can't say for sure, but I get the impression that event-driven Data Macros have restrictions that may go beyond what is technically *possible*. There could be performance-related restrictions, or possibly some limitations imposed to make them "play nice" with Sharepoint integration. – Gord Thompson Jan 27 '16 at 00:23