C# parameterized query with Domain Aggregate functions (DMin) - c#

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.

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

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.

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

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

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

Related

Update command with parameters gives "Data type mismatch in criteria expression"

I have the following code:
/*
// it works
cmd_oper = "UPDATE [Model Elements] SET [Record Status] = \"Disabled\" WHERE [Index] = #db_idx";
/*/
// it doesn't work
cmd_oper = "UPDATE [Model Elements] SET [Record Status] = #stat WHERE [Index] = #db_idx";
//*/
using( OleDbCommand cmd = new OleDbCommand( cmd_oper, svr_conn ) )
{
cmd.Parameters.Add( "#db_idx", OleDbType.Integer ).Value = 2;
//cmd.Parameters.Add( "#stat", OleDbType.VarChar ).Value = "Disabled";
cmd.Parameters.AddWithValue( "#stat", "Disabled" );
cmd.ExecuteNonQuery();
}
With the second variant of cmd_oper (the one not commented in the beginning of the code) I get "Data type mismatch in criteria expression". The other one works. The type of Record Status column is set in database as Short Text. I know there are many posts related to this error on StackOverflow but I couldn't find an exact fit. Thanks.
The fine manual, https://msdn.microsoft.com/en-us/library/system.data.oledb.oledbcommand.parameters(v=vs.110).aspx has the following to say about parameters
The OLE DB .NET Provider does not support named parameters for passing
parameters to an SQL statement or a stored procedure called by an
OleDbCommand when CommandType is set to Text. In this case, the
question mark (?) placeholder must be used. For example:
SELECT * FROM Customers WHERE CustomerID = ?
Therefore, the order in which OleDbParameter objects are added to the
OleDbParameterCollection must directly correspond to the position of
the question mark placeholder for the parameter in the command text.
Ergo, I think you need your code to be like:
cmd_oper = "UPDATE [Model Elements] SET [Record Status] = ? WHERE [Index] = ?";
using( OleDbCommand cmd = new OleDbCommand( cmd_oper, svr_conn ) )
{
cmd.Parameters.AddWithValue( "anything-name-doesnt-matter", "Disabled" );
cmd.Parameters.Add( "its-position-that-matters", OleDbType.Integer ).Value = 2;
cmd.ExecuteNonQuery();
}
For what it's worth, you should probably name your parameters sensibly (I named them silly above to demonstrate that the name is irrelevant) because the idea is that once prepared, you can execute a statement many times just changing the parameters:
cmd.Parameters["its-position-that-matters"].Value = 3;
cmd.ExecuteNonQuery();
cmd.Parameters["its-position-that-matters"].Value = 4;
cmd.ExecuteNonQuery();
cmd.Parameters["its-position-that-matters"].Value = 5;
cmd.ExecuteNonQuery();
cmd.Parameters["its-position-that-matters"].Value = 6;
cmd.ExecuteNonQuery();
Here i've run the update for index values 3, 4, 5 and 6 too, just by changing the parameter value and re-running. It would hence have been better of me to choose a sensible name for the "index" parameter, to make the code more readable
Steve, in his comment, has noted he believes that you can put named parameters in the query, but the names are ignored (they're essentially treated as ? marks anyway) so you'll still need to add the parameter values in the same order as the placeholders appear. If you have repeated a placeholder in a query, you'll have to repeat-add it to the parameters collection. I've no comment on the accuracy of steve's assertion; I've always used ?
Ultimately, this is all good evidence that really you should get into learning to use a data access library like Entity Framework, and stop writing SQL strings in your button click event handlers - it's not a good way to code. If you'd used EF from the outset, you'd never even have hit this problem. Good on you for using parameterised queries though. Now go check out EF and leave this '90s donkey-way of doing data access behind :)

Invalid Column Name: "value" - Error Even though it works in another form.

I am stuck at one problem and I just can't solve this.
I get this Error:
Error Message
That's the relevant table
The Code:
SqlConnection connection = new SqlConnection(connectionString);
connection.Open();
string query = "UPDATE CAC SET nextMaintainance = #nextMaintainance WHERE department = " + #departmentCB.Text;
SqlCommand command = new SqlCommand(query, connection);
command.Parameters.AddWithValue("#nextMaintainance", nextMaintainanceDT.Value);
command.ExecuteNonQuery();
The weird thing I don't understand is that a similar code works just fine without any error in my project:
query = "UPDATE LDV SET received = #received, department = #department WHERE Id =" + #idTxt.Text;
command = new SqlCommand(query, connection);
command.Parameters.AddWithValue("#received", inDT.Value);
command.Parameters.AddWithValue("#department", departmentCb.Text);
command.ExecuteNonQuery();
MessageBox.Show("Lungenautomat wurde aktualisiert");
If relevant, my connection string:
connectionString = ConfigurationManager.ConnectionStrings["SCBA_Manager_0._1.Properties.Settings.SCBAmanagerConnectionString"].ConnectionString;
I really hope you can help me :(
Thank you!
The department column is a text column, so comparing it to a value means the value should be wrapped in quotes.
// This fix is not the recommended approach, see the explanation after this code block
string query = "UPDATE CAC SET nextMaintainance = #nextMaintainance WHERE department = '" + departmentCB.Text + "'";
// ^--------------------------^------ single quote added to wrap the value returned by departmentCB.Text
On the other hand, this error does not occur in your second example, because there you're correctly using the Parameters.AddWithValue() method to add the value for the #department parameter, and because id is a numeric column, so it doesn't require the value wrapped in quotes.
However, while the code shown above does the job, it is not the right way of doing the job. The correct way is to used parameters for all values to be injected into a query. The queries you've shown above are already correctly using parameters for some values (e.g. nextMaintenance in the first query, received and department in the second), but are incorrectly doing string concatenation for other values (e.g. department in the first query, id in the second).
Usage of Parameterized SQL
The benefit of using parameterized SQL is that it automatically takes care of adding quotes, prevents SQL injection, etc.
Therefore, its best to change your first code block to:
SqlConnection connection = new SqlConnection(connectionString);
connection.Open();
string query = "UPDATE CAC SET nextMaintainance = #nextMaintainance WHERE department = #department";
SqlCommand command = new SqlCommand(query, connection);
command.Parameters.AddWithValue("#department", departmentCb.Text);
command.Parameters.AddWithValue("#nextMaintainance", nextMaintainanceDT.Value);
command.ExecuteNonQuery();
Notice how the string query is a single string without any messy concatenation, and that it contains two parameters #nextMaintenance and #department? And how the values for those parameters are correctly injected using Parameters.AddWithValue() in the following lines?
Your second code block can be similarly improved by using a parameter for the Id column.
query = "UPDATE LDV SET received = #received, department = #department WHERE Id = #Id ";
command.Parameters.AddWithValue("#Id", idTxt.Text);
Further Information
Do read up about SQL injection ( https://technet.microsoft.com/en-us/library/ms161953(v=sql.105).aspx ) to see how using string concatenation like your original code can lead to various security issues, and why parameterized queries are the preferred way of injecting dynamic values into SQL queries.
You can read up more about parameterized queries here: https://msdn.microsoft.com/en-us/library/yy6y35y8(v=vs.110).aspx
In your first example, the WHERE clause evaluates to
WHERE department = Kasseedorf
wheras it should be
WHERE department = 'Kasseedorf'
So the line should be
string query = "UPDATE CAC SET nextMaintainance = #nextMaintainance WHERE department = '" + #departmentCB.Text +"'";
It works in the second example, because id is an integer and doesn't neet quotes.

C# SQL string formatting

I am new to .net/C#. Coming from PHP and some Java, I am finding the new languages interesting and challenging.
I have an issue with a sql string
string query = #"select * from Users where role='member' and
SUBSTRinG(lname, 1, 1) = '"+querystring + "' ORDER BY lname ASC";
Which to me, looks fine. however when run my solution and output the query as it is not working, I get this as my output:
select * from Users where role='member' and SUBSTRinG(lname, 1, 1)
= ' O ' ORDER BY lname ASC
This is output into my Firebug console (the page that uses this query is accessed via AJAX).
Is their a reason my 's are being turned into their code version, ie '&#39'
Thanks
In C# you should be using SqlCommand to excute the query, and to prevent sql injection using the parameter collection.
Your query seems fine - The issue might be the way you are running it or the parameters being supplied. Update your question with more details on what you are expecting vs what is happening, include any error messages generated.
Below is a general guideline of how to get data from a sql table to a c# Data Table object.
SqlConnection conn = new SqlConnection("YourConnectionString");
SqlCommand cmd = new SqlCommand(#"select * from Users where role='member' and
SUBSTRinG(lname, 1, 1) = #query ORDER BY lname ASC");
cmd.Parameters.AddWithValue("#query", querystring);
DataTable resultTable = new DataTable();
try
{
SqlDataAdapter da = new SqlDataAdapter(cmd);
da.Fill(resultTable);
} finally {
if (conn.State != ConnectionState.Closed) conn.Close();
}
Console.WriteLine(String.Format("Matched {0} Rows.", resultTable.Rows.Count));
For SQL injection protection:
You can provide escape sequence for single quotes by replacing them with two single quotes '' so that it will be treated as a single quote inside SQL strings. Otherwise it is considered as a start or end of the string value in SQL.
Replacing single quotes using ' in .net is also preferred but its better going with two single quotes.

How does JET OLEDB Parameters compare strings with text field in and Access DB

I have the following update query in C# using a JET OLEDB connection, connecting to a ms access DB file. The query fails to change the fields, it runs correctly but just 0 rows changed.
I think the problem is how parameters are processed and compared against the DB but have no idea how to fix it.
The "User" column is set as text. I have an insert statement that works perfectly set up in the same fashion with the parameters.
com.CommandText = "UPDATE [ExamMaster] SET [User] = (DLookup('LName', 'Users', 'ID' = '#correctUser') WHERE [User] = '#user'";
com.Parameters.AddWithValue("#correctUser", correctUser);
com.Parameters.AddWithValue("#user", userName);
If I do not use a parameter for the where clause and just insert it into the command string like so:
WHERE [User] = '"+userName+"'";</code>
it will update the DB just fine. What am I missing here?
UPDATE:
With or with single quotes makes no difference and rearranging the order of the parameters does not work either.
The order matters. I "think" in your query user is being called first before the correctUser due to the DLOOKUP function.
com.Parameters.AddWithValue("#user", userName);
com.Parameters.AddWithValue("#correctUser", correctUser);
You don't need to single quote parameters:
WHERE [User] = #user";
and I'll guess that the DLOOKUP doesn't need the single quotes either, just [brackets] if the field name has a space or is a reserved word (which [User] might be).
You will need to change that a bit, try:
OleDbConnection cn = new OleDbConnection(aconnectionstring);
cn.Open();
//testing
int correctUser = 1;
string userName = "1";
OleDbCommand com = new OleDbCommand();
com.Connection = cn;
//You cannot have a parameter in DLookUp
com.CommandText = "UPDATE [ExamMaster] SET [User] = " +
"DLookup('LName', 'Users', 'ID = " + correctUser + "') WHERE [User] = #user";
com.Parameters.AddWithValue("#user", userName);
//You must execute the query
com.ExecuteNonQuery();

Return last inserted ID without using a second query

I'm working on an ASP.NET project (C#) with SQL Server 2008.
When I insert a row into a table in the database, I would like to get the last inserted ID, which is the table's IDENTITY (Auto Incremented).
I do not wish to use another query, and do something like...
SELECT MAX(ID) FROM USERS;
Because - even though it's only one query - it feels lame...
When I insert something I usually use ExecuteNonQuery(), which returns the number of affected rows.
int y = Command.ExecuteNonQuery();
Isn't there a way to return the last inserted ID without using another query?
Most folks do this in the following way:
INSERT dbo.Users(Username)
VALUES('my new name');
SELECT NewID = SCOPE_IDENTITY();
(Or instead of a query, assigning that to a variable.)
So it's not really two queries against the table...
However there is also the following way:
INSERT dbo.Users(Username)
OUTPUT inserted.ID
VALUES('my new name');
You won't really be able to retrieve this with ExecuteNonQuery, though.
You can return the id as an output parameter from the stored procedure, e.g. #userId int output
Then, after the insert, SET #userId = scope_identity()
even though it's only one query - it feels lame...
It actually is also wrong as you can have multiple overlapping iserts.
That is one thing that I always fuind funny - people not reading the documentation.
SELECT SCOPE_IDENTITY()
returns the last identity value generated in a specific scope and is syntactically correct. It also is properly documented.
Isn't there a way to return the last inserted ID without using another query?
Yes. Ask for the number in the saame SQL batch.
INSERT (blablab9a); SELECT SCOPE_IDENTITY ();
as ONE string. ExecuteScalar.
You can have more than one SQL statement in one batch.
If you want to execute query from C# code & want to get last inserted id then you have to find the following code.
SqlConnection connection = new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString);
connection.Open();
string sql = "Insert into [Order] (customer_id) values (" + Session["Customer_id"] + "); SELECT SCOPE_IDENTITY()";
SqlCommand cmd = new SqlCommand();
cmd.Connection = connection;
cmd.CommandText = sql;
cmd.CommandType = CommandType.Text;
var order_id = cmd.ExecuteScalar();
connection.Close();
Console.Write(order_id);

Categories

Resources