I need to do an update but the column name is dynamic.
Code Snippet:
using (var cn = Connection)
{
var sql = #"UPDATE Teste
SET #columnName = #parameter,
DT_PROCESSAMENTO = #vDtProcessamento
WHERE ID = #ID";
var resultado = cn.Execute(sql, new
{
columnName,
parameter,
ID
});
}
Is it possible to pass the column name as a parameter?
This code is what I did, but it does not work. No exceptions but the update does not work.
You should use String.Format:
var columnName = "Name";
var sql = String.Format(#"UPDATE Teste
SET {0} = #parameter
WHERE ID = #ID", columnName);
But here you can get SQL injection.
So it's better to check that column name is really column name in your table.
No you cannot do that that way because the column name cannot be a variable.
To do it you do need dynamic SQL like this:
using (var cn = Connection)
{
var sql = $#"UPDATE Teste
SET {columnName} = #parameter,
DT_PROCESSAMENTO = #vDtProcessamento
WHERE ID = #ID";
var resultado = cn.Execute(sql, new
{
parameter,
ID
});
}
In the above snippet code you can combine # to use linebreaks inside the string and $ to insert variables in the string - it's a bit clearer and shorter than using String.Format.
I already use something like this with dapper for a few specific scenarios.
As noted, to do this you need to dynamically construct your SQL, either in SQL then use EXEC(), or in your C# with string.Format() or StringBuilder. Just be aware that by doing this, you will identify yourself as a savage in the eyes of some people! You have no guarantee that users of your method will provide a valid column name. You have to run your program to see what SQL it generates, and if it runs. You have no type safety around the input parameter. There must be a better way. disclaimer: I wrote QueryFirst
Related
I'm building a WinForms project in C# using a PostgreSQL database and the Npgsql framework.
For inserting a record, I need to return the ID of the new record. This SO question says to add SELECT SCOPE_IDENTITY() to the query string passed to cmd. So my query string looks like this:
string insertString = "INSERT INTO sometable (company_name, category, old_value, old_desc, new_value, new_desc, reference1, reference2) VALUES (#comp, #cat, #oldValue, #oldDesc, #newValue, #newDesc, #ref1, #ref2); SELECT SCOPE_IDENTITY();";
and then get the ID with something like
int modified = cmd.ExecuteNonQuery();
But that's likely SQL Server-specific. If I use that method, I get an exception at the above line saying, "fuction scope_identity() does not exist".
I wasn't able to find anything that seemed to address this on the Npgsql documentation.
Per the linked SO question and Denis' suggestions I've tried adding both
RETURNING id;
and
CURRVAL(pg_get_serial_sequence('my_tbl_name','id_col_name'))
to the query string, replacing SELECT SCOPE_IDENTITY(); with those statements in the code above. In both cases they work as intended in DBeaver on an insert, but in my C# code in my WinForm project, modified was set to "1".
NOTE: I re-titled the question and added more information about what I've done.
Add "returning idcolumn" to the end of the sql query, then run the command with the ExecuteScalar() method instead of ExecuteNonQuery(). It should return with an int.
string insert = "insert into table1 (col1) values (something) returning idcol";
int id = cmd.ExecuteScalar();
All the comments above were almost nearly spot on and got me to a solution but didn't exactly wrap it in a bow -- so I thought i'd post my implementation that works (with silly fake example tables of course).
private int? InsertNameIntoNamesTable(string name)
{
int? id = null;
using (var dbcon = new NpgsqlConnection(_connectionString))
{
dbcon.Open();
StringBuilder sb = new StringBuilder();
var sql = $#"
insert into names_table
(name)
values
({name})
returning id;
";
sb.Append(sql);
using (var cmd = new NpgsqlCommand(sql, dbcon))
{
id = (int)cmd.ExecuteScalar();
}
dbcon.Close();
}
return id;
}
I have a C# based api and I send queries to a mysql server. I wonder how can i read the id from a select to a table on C# Note that I am using MySql.Data.MySqlClient;
My code until the execute is this one below. But in this step I wonder how can I retrieve the desired id. I used ExecuteNotQuery but it seems it does not fit on what I need.
string connectionString = #"server=x.x.x.x;userid=xxxx;password=xxxxxx;database=testdatabase";
string getLastStoryIdQuery = "SELECT MAX(ID) FROM testdatabase.test";
MySqlCommand getLastTestIdCommand = new MySqlCommand(getLastStoryIdQuery, mySqlConnection);
int lastId = getLastStoryIdCommand.ExecuteNonQuery();
How can I retrieve the result as an Integer or in worst case as a string response? Thank you in advance. :)
int lastId = Convert.ToInt32(getLastStoryIdCommand.ExecuteScalar());
You can find the documentation on MySqlCommand here: https://dev.mysql.com/doc/dev/connector-net/8.0/html/T_MySql_Data_MySqlClient_MySqlCommand.htm
The method ExecuteNonQuery returns the number affected by the query, while ExecuteScalar returns the first column of the first row. You can also use ExecuteReader to get a datareader so that you can read a resultset the database produces.
In practice, I rarely use DbCommand/DbReader anymore and prefer to just use Dapper for database access in most cases where performance isn't absolutely critical. It simplifies parameter creation, and object filling which serves the vast majority of my use cases.
Dapper would look like this:
string connectionString = #"server=x.x.x.x;userid=xxxx;password=xxxxxx;database=testdatabase";
string getLastStoryIdQuery = "SELECT MAX(ID) FROM testdatabase.test";
int lastId;
using(var conn = new MySqlConnection(connectionString))
{
lastId=conn.Query<int>(getLastStoryIdQuery).First();
// you can also do the following in this instance, but you will use the
// above for results that return multiple rows or multiple columns
//lastId=conn.ExecuteScalar<int>(getLastStoryIdQuery);
// Here is how you use parameters:
// var something = conn.ExecuteScalar<int>("SELECT id FROM testdatabase.test WHERE id=#param",new {param = 10});
// This gets multiple columns and rows into a List<person> (assuming you have a person class with fname,lname,dob properties):
// var people = conn.Query<person>("SELECT fname,lname,dob FROM persons WHERE dob>#start", new {start=new DateTime(2000,1,1)}).ToList();
}
So I have this query:
var retval = db.v_AViewOfJoinedTables.SqlQuery("SELECT * FROM dbo.v_AViewOfJoinedTables WHERE #filter = '#value';",
new SqlParameter("#filter", columnname),
new SqlParameter("#value", value)).ToList();
But I'm not getting any result. If I run this query through SSMS, it returns data. I know if I use a table instead that it works. So can SqlQuery only work on tables or is something wrong my query or code?
UPDATED CODE:
SqlCommandBuilder scb = new SqlCommandBuilder();
string columnname = userNameFilterType;
string escapedColumnName = scb.QuoteIdentifier(columnname);
string sqlQuery = String.Format("SELECT * FROM dbo.v_AViewOfJoinedTables WHERE {0} = '#username';", escapedColumnName);
var retval = db.v_AViewOfJoinedTables.SqlQuery(sqlQuery, new SqlParameter("username", currentUser)).ToList();
return retval.AsQueryable();
The problem isn't the view. You can't use SQL parameters for table or column names.
... where #filter = ...
That won't work. It must be text. Just use string interpolation to insert it:
var retval = db.v_AViewOfJoinedTables.SqlQuery($"SELECT * FROM dbo.v_AViewOfJoinedTables WHERE {columnname} = #value;",
new SqlParameter("#value", value)).ToList();
The SQL Execution Engine will cache the execution plan of queries you run, but it can't do that if the query itself changes. The plan might change depending on the column name, for instance. So, SQL parameters will only ever work as values supplied to the query.
You also need to remove the single quotes around #value. The SQL Server provider will handle the quote insertion for you.
I am constructing a sql_insert_string to be used in Microsoft.ApplicationBlocks.Data.SqlHelper to be used as follows:
SqlHelper.ExecuteNonQuery(Transaction, CommandType.Text, sql_insert_string)
When I hover over the SQL statement it looks like below:
string sql_insert_string = "Insert into images_table(image_id, image_byte_array) values ('123', System.Byte[])
One of the insert value is a byte array as shown above. The variable has value in the byte array, say like byte[6738] . But after the sql_insert_string is constructed, it comes as System.Byte[]. The image_byte_array column type is varbinary(max). The database is SQL Server 2008. Because of this the database throws the following error:
An object or column name is missing or empty. For SELECT INTO statements, verify each column has a name. For other statements, look for empty alias names. Aliases defined as \"\" or [] are not allowed. Change the alias to a valid name.
you can insert the byte array like so:
private void FireSql(byte[] input)
{
const string sql_insert_string =
"Insert into images_table(image_id, image_byte_array) values (#image_id, #image_byte_array)";
SqlTransaction transaction = null; //wherever you get the transaction obj from.
var imageIdParam = new SqlParameter("#image_id", SqlDbType.Int, 4)
{
Direction = ParameterDirection.Input,
Value = 123
}; //change the data type to whatever data type you are expecting
var byteParam = new SqlParameter("#image_byte_array", SqlDbType.VarBinary)
{
Direction = ParameterDirection.Input,
Size = input.Length,
Value = input
}; //change the data type to whatever data type you are expecting
SqlHelper.ExecuteNonQuery(transaction, CommandType.Text, sql_insert_string, imageIdParam, byteParam);
}
I would suggest looking at an ORM (https://en.wikipedia.org/wiki/Object-relational_mapping) like Entity Framework(http://www.asp.net/entity-framework) to do all of this for you while increasing security and future changes much easier.
You should be using the Parameters while constructing the SQL Query which obviously will avoid SQL Injection attacks. How your queries are getting constructed is still unclear here.
Something like this should do it for you.
SqlParameter sParam = new SqlParameter("#image_byte_array", SqlDbType.VarBinary)
{
Value = image
};
SqlHelper.ExecuteNonQuery(Transaction, CommandType.Text, sql_insert_string, sParam)
You may use
string sql_insert_string =
String.Format("INSERT INTO images_table(image_id, image_byte_array) VALUES ('123', CAST('{0}' AS VARBINARY(MAX)))", System.Byte[].ToString());
And yes, as #marc_s commented, you shouldn't be constructing SQL statements as a security concern.
That's what I tried & failed:
string sql = "... WHERE [personID] IN (#sqlIn) ...";
string sqlIn = "1,2,3,4,5";
SqlCeCommand cmd.Parameters.Add("#sqlIn", SqlDbType.NText).Value = sqlIn;
SqlCeDataAdapter da = new SqlCeDataAdapter(cmd);
da.Fill(ds); // > Error
Error details:
The ntext and image data types cannot be used in WHERE, HAVING, GROUP BY, ON, or IN clauses, except when these data types are used with the LIKE or IS NULL predicates.
Can't I pass all the IDs as one parameter? Should I add one by one all IDs?
P.S: Notice SqlCE
You can't parameterise that as a single parameter. Your query is doing an "in" on a single value, so is essentially:
... Where personId = '1,2,3,4,5'
(give or take a parameter). This is usually also an invalid or sub-optimal equality test, and certainly isn't what you were trying to query.
Options;
use raw concatenation: often involves a SQL injection risk, and allows poor query plan re-use
on full SQL server: use a UDF to split a single param
on full SQL server, use a TVP
add parameters dynamically, and add the various #param3 etc to the TSQL
The last is the most reliable, and "dapper-dot-net" has a feature built in to do this for you (since it is commonly needed):
int[] ids = ...
var rows = conn.Query<SomeType>(
#"... Where Id in #ids",
new { ids }).ToList();
This, when run via dapper-dot-net, will add a parameter per item in "ids", giving it the right value etc, and fixing the SQL so it executes, for example:
"... Where Id in (#ids0, #ids1, #ids2)"
(if there were 3 items in "ids")
You'll need to split the sqlIn string by comma, convert each to an integer, and build the IN statement manually.
string sqlIn = "1,2,3,4,5";
string inParams = sqlIn.Split(',');
List<string> paramNames = new List<string>();
for(var i = 0; i < inParams.Length; ++i){
string paramName = "#param" + i.ToString();
SqlCeCommand cmd.Parameters.Add(paramName, SqlDbType.Int).Value = int.Parse(inParams[i]);
paramNames.Add(paramName);
}
string sql = "... WHERE [personID] IN (" +
string.Join(",", paramNames) +
") ...";