SqlCommand shows bad performance when passing text parameter for LIKE - c#

I've encountered quite a wierd problem:
I form SQL server command dynamically and one of its part is a group of LIKE tests, that are used as textual search on several columns and tables. It looks like:
SET #text = '%' + REPLACE(REPLACE(REPLACE(#text, '!', '!!'), '%', '!%'), '_', '!_') + '%'
INSERT INTO
#textFiltered
FROM
#documents d
LEFT JOIN docData t1 WITH (NOLOCK)
ON t1.ID = d.ID AND
(t1.Topic like #text escape '!'
OR t1.Subject like #text escape '!')
LEFT JOIN docData2 t2 WITH (NOLOCK)
ON t2.ID = d.ID AND
t2.Name like #text escape '!'
WHERE
t1.ID IS NOT NULL
OR t2.ID IS NOT NULL
(Surely, that's not the best way to do textual search, still that's not the point)
Now, when I create SQLCommand in C#, like this:
using (var cmd = new SqlCommand())
{
cmd.CommandText = cmdText;
cmd.CommandType = CommandType.Text;
cmd.Connection = connection;
cmd.Parameters.Add("text", NVarChar, 4000).Value = searchText;
var reader = cmd.ExecuteReader();
....
}
performce of executing is very poor (say, 8 seconds), while executing same query in SQL Management Studio much faster (say, 500 ms).
However, if instead of passing text as parameter, I embed it into text of SQL with:
DECLARE #text nvarchar(max)
SET #text = '<embedded-text>'
then SqlCommand also runs fast.
What's even more strange this behavior seems to be correlated with set of columns that are used in LIKE clauses (didn't figure how). Types of those columns can be nvarchar(size), nvarchar(max), ntext.
I suspect that problem is with parameter - maybe it's type is incorrect or smth else.
P.S. Tried to create parameter with size = length of searched text + 1 - didn't help.

My best guess will be that it is related to the query plan that SQL Server is choosing for you.
there is big difference between using constant varchar which the SQL server can evaluate against existing statistics, and using arbitrary variable which the server know nothing about.
you can try OPTION (RECOMPILE) hint. although this hint will cause the Stored procedure to be compiled on every call, it'll also allow the SQL Server to search for the best plan for the given values, and maybe in your case it will be good trade off.
you could also add the query plan for both option of the stored procedure, and maybe someone will be able to see the difference, and pin point the exact problem.

Related

Parameters and SQL Injection asp.net

I used parameters in the whereclause, but what about the variables for this {0}. Do I need to create a parameter for it to prevent sql injection?
("...inner join db1.dbo.table1.id on db2.dbo.table2.id = {0}.dbo.table3.id where name=#name",abc)
var abc = ddl2.SelectedItem.Text;
cmd.Parameters.AddWithValue("#name", ddl1.selectedvalue);
To the best of my knowledge, you can't actually 'paramaterize' database names/table names.
String.Format does not solve SQL injection in this case since it is possible for the user to change ddl2.SelectedItem.Text to whatever they want.
If you need a dynamic value for the database name, I suggest you either keep that value as a const or store it somewhere that you have complete control over/ is never sent or interpreted client side.
I would suggest you to use any ORM (object relational mapping) i.e Entity framework or n-hibernate etc. and use linq to write queries, that will prevent you application from SQL Injection.
Unfortunately, as Abbath already mentioned, this type of construct is not parameterizable. As Abbath mentioned, the best solution is to keep such arguments under your absolute control, but there are times, where such constructs are needed, and it may not be possible to have complete control over them.
For such scenarios, the best recommendation in this case is to escape the arguments. In this case, the DB name represented by {0} on your sample code.
There are two potential mechanisms to achieve this:
a) Create a mechanism that allows you to parameterize the query
Advantage: You can reuse the same solution from any driver (.Net, ODBC, etc.)
Disadvantage: A bit more work. You will not use select directly anymore on this case.
For example (I am including a simple example that has an inner join, just like your code):
CREATE PROC sp_MyQuery( #target_db_name sysname, #name nvarchar(100))
AS
BEGIN
DECLARE #cmd nvarchar(max)
DECLARE #parameters nvarchar(max)
SELECT #cmd = N'SELECT * FROM msdb.sys.objects inner join '
+ quotename(#target_db_name) + N'.sys.sql_modules
on msdb.sys.objects.object_id = '
+ quotename(#target_db_name) + N'.sys.sql_modules.object_id WHERE name = #name'
print #cmd -- See the command before it is executed.
set #parameters = N'#name nvarchar(100)'
EXEC sp_executesql #cmd, #parameters, #name = #name
END
go
-- Example of usage
DECLARE #target_db_name sysname = 'msdb'
DECLARE #name nvarchar(100) = 'sp_help_operator'
EXEC sp_MyQuery #target_db_name, #name
go
At this point, you can use SqlParameter objects as you would normally do. For example:
sqlcmd.CommandText = #"[dbo].[sp_MyQuery]";
sqlcmd.CommandType = System.Data.CommandType.StoredProcedure;
sqlcmd.Parameters.AddWithValue("#target_db_name", ddl0.selectedvalue);
sqlcmd.Parameters.AddWithValue("#name", ddl1.selectedvalue);
SqlDataReader reader = sqlcmd.ExecuteReader();
b) Escape the DB name within your CLR code
Advantage: Easier to implement
Disadvantage: App-specific solution, you need to be careful with potential Unicode–DB collation translation issues.
For example (same query as above):
sqlcmd.CommandText = String.Format(#"
SELECT * FROM msdb.sys.objects inner join [{0}].sys.sql_modules on msdb.sys.objects.object_id = [{0}].sys.sql_modules.object_id WHERE name = #name;",
ddl0.selectedvalue.Replace("]", "]]"));
sqlcmd.CommandType = System.Data.CommandType.Text;
sqlcmd.Parameters.AddWithValue("#name", ddl1.selectedvalue);
SqlDataReader reader2 = sqlcmd.ExecuteReader();
I typically recommend using solution (a) whenever it is possible, but both solutions should help you protect against SQL injection.
BTW. The following link may also be quite useful: https://blogs.msdn.microsoft.com/raulga/2007/01/04/dynamic-sql-sql-injection/
I hope information helps.
you dont need create parameter for abbc if you use string.format like this
var abc = ddl2.SelectedItem.Text;
string.format("...inner join db1.dbo.table1.id on db2.dbo.table2.id = {0}.dbo.table3.id where name=#name",abc)
cmd.Parameters.AddWithValue("#name", ddl1.selectedvalue);

parameterized insertion of data half selected from other column half constant gives invalid pseudocolum

I have the following query
"INSERT INTO t1 select $v1,c2 FROM t2 WHERE c3= $v2";
which is executed as
SqlCommand cmd= new SqlCommand(query, conn);
cmd.Parameters.AddWithValue("$v2", data);
foreach (string value in list)
{
cmd.Parameters.AddWithValue("$v1", value);
cmd.ExecuteNonQuery();
}
however this results in an error:
An unhandled exception of type 'System.Data.SqlClient.SqlException' occurred in System.Data.dll
Additional information: Invalid pseudocolumn "$v1".
This is based on the following question: SQL Insert into ... values ( SELECT ... FROM ... )
I suspect that it's not understanding where the were clause goes or that $v1 is not the name of a column but an actual value, but does anybody know how to fix this (t1 only has 2 columns both ints c2 is an int and c3 is also an int).
The final goal of this code is to do basically the following (in pseudocode)
$insertv1=$v1
$insertv2= select c2 from t2 where c3=$v2
query = insert INTO t1 VALUES ($insertv1,$insertv2)
Note that deparameterizing the values $v1 and $v2 solves the problem but just parameterizing one of the two causes a problem.
As the past says, you cannot pass the column name to an OleDB SQL query as a parameter. Parameters only for values, not table or column names.
As failed passed attempts mention in their comments, this is not secure - since this could be dangerous. The main problem is if the table name comes from user input - if it is so, it will leave an SQL injection vulnerability in your application.
Thus, if it does indeed come from user input you'll need to protect yourself. The easiest way is to make sure that table name is a valid table, Attached table, or query name - To do that, you can use a query like so:
SELECT Name FROM Myssobjects Where Type in (1,5,6,)
This query will return you the valid values for tables in your database, which you can compare to the input you got. Notice you should still use this with caution, because most of the times you won't want to let your user have access to your entire database.
Anyhow, if you protected yourself well and made sure that you are no longer vulnerable, the way to do this is dynamically create the string for the query - like so:
string tableName = value;
string query = "INSERT INTO t1 select "+tableName+",c2 FROM t2 WHERE c3= $v2";
SqlCommand cmd= new SqlCommand(query, conn);
cmd.Parameters.AddWithValue("$v2", data);
// etc. - instead of the AddWithValue(), you add it to the string.
EDIT: Since this isn't exacly what you asked for, I'll explain what I mean in relation to your pseudo code:
$insertv1=$v1
$insertv2= select c2 from t2 where c3=$v2
query = insert INTO t1 VALUES ($insertv1,$insertv2)
Would actually require seperation of the two queries, and thus translate into:
string v1="Your value here.",v2="Your second value here.",v3="";
//first : get v2.
SqlCommand cmd= new SqlCommand("select c2 from t2 where c3=$v2", conn); // using the v2 query you stated.
cmd.Parameters.AddWithValue("$v2", v2);
cmd.ExecuteNonQuery();
// put the value from the query into v3
// then, make the entire, bigger query
string finalQuery = "INSERT INTO t1 VALUES($v1,$v2)";
SqlCommand cmd2= new SqlCommand(finalQuery, conn);
cmd2.Parameters.AddWithValue("$v1", v1);
cmd2.Parameters.AddWithValue("$v2", v3);
cmd2.ExecuteNonQuery();
Note that deparameterizing the values $v1 and $v2 solves the problem but just parameterizing one of the two causes a problem.
EDIT 2:
In chat, we looked further into the compliation error, since there was some strange behavior with parameterizing different values. I asked you what values are in $v1 and $v2, and we figured that the nvarchar in v2 is misinterpreted as a pseudo name. That also explains why the SQL works but the C# failed; the error was in fact in the way OleDB intrepreted the name. The solution is simple - add ' marks before and after v2, which will lead to it being read as a non-pseudo string, like so: "INSERT INTO t1 select $v1,c2 FROM t2 WHERE c3= '$v2'".

C# SQL parameter limitations?

I have the following SQL command in my .Net application:
sqlCommand.Text = "DECLARE #method NVARCHAR(MAX);
SET #method = ' WITH selectRows AS (SELECT *, row=ROW_NUMBER() OVER(ORDER BY(SELECT 1)) FROM [' + #param + ']) SELECT ' + #param_2 + ' FROM selectRows WHERE row BETWEEN 0 AND 30;'
EXEC(#method);";
sqlCommand.Parameters.AddWithValue("#param", tableName);
sqlCommand.Parameters.AddWithValue("#param_2", columnString);
Whenever '#param_2' is long the query won't execute. Lets say the column string looks like this:
[class],[partnr],[accessoriesidentifier],[canbelinedup],[certificate],[certificate_ce],[certificate_ul],[certificate_vde],[codeletter],[construction],[craftcooling],[craftelectro],[craftfluid],[crafthydraulic],[craftlubrication],[craftmechanic],[craftpneumatic],[craftprocess],[create],[depth],[depthspacingfront],[depthspacingrear],[description1],[description2],[description3],[discount],[ecabinetmacro],[erpnr],[externaldocument1],[externaldocument2],[externaldocument3],[externalplacement],[functiongroup],[graphicmacro],[groupnumber],[groupsymbolmacro],[height],[heightspacingabove],[heightspacingbelow],[identcode],[identtype],[isaccessory],[lastchange],[lifetime],[macro3d],[maintenance],[manufacturer],[mountinglocation],[mountingspace],[note],[ordernr],[packagingprice1],[packagingprice2],[packagingquantity],[picturefile],[piecetype],[priceunit],[productgroup],[productsubgroup],[producttopgroup],[purchaseprice1],[purchaseprice2],[quantityunit],[reportid],[salesprice1],[salesprice2],[snapheight],[snapheightmiddleoffset],[spare],[stress],[supplier],[terminal],[typenr],[uniqueid],[usage],[wear],[weight],[width],[widthspacingleft],[widthspacingright],[barcount],[bardistance],[bargeometry],[barmountingplatedistance],[bottompaneldepth],[bottompaneldistance],[bottompanelprojectionback],[bottompanelprojectionfront],[bottompanelprojectionleft],[bottompanelprojectionright],[busbarholderpartnr],[busbarholdervariant],[busbarrailpartnr],[busbarrailvariant],[deliverylength],[dooroffsetright],[dooroffsettop],[doorthickness],[doortrabbet],[doortype],[hingeposition],[insertpointoffsetx],[profiledepth],[profiledistance],[profileheight],[rearpaneldistance],[rearpaneldpepth],[rearpanelprojectionbottom],[rearpanelprojectionleft],[rearpanelprojectionright],[rearpanelprojectiontop],[sidepaneldepth],[sidepaneldistance],[sidepanelprojectionback],[sidepanelprojectionbottom],[sidepanelprojectionfront],[sidepanelprojectiontop],[toppaneldistance],[toppaneldpepth],[toppanelprojectionback],[toppanelprojectionfront],[toppanelprojectionleft],[toppanelprojectionright],[vprofiledepth],[vprofilewidth],[wallthickness],[widthbottom],[widthtop],[variant],[adjustrange],[adressrange],[advancecontacts],[airgap],[assemblyspreaded],[awgfrom],[awgtill],[bendingradius],[cabledesignation],[cabledisplayform],[cablelength],[cabletype],[cableweight],[coding],[color],[connection],[connectioncrosssection],[connectiondesignation],[connectionmethod],[contactarrangement],[contacttype],[coppernumber],[creepagedistance],[crosssectionfrom],[crosssectiontill],[currentcsa],[currentiec],[currentul],[degofprotection],[design],[doordepth],[doorheight],[doormountingspace],[doorwidth],[electricalcurrent],[electricalpower],[firmwareversion],[flow],[holdingpower],[innerdiameter],[intrinsicsafety],[material],[norm],[outerdiameter],[paneldepth],[panelheight],[panelmountingspace],[panelwidth],[pincount],[pipeclass],[plcdeviceid],[plcisbuscoupler],[plcisbusdistributor],[plciscpu],[plcispowersupply],[plcobjectdescription],[plctype],[powerdissipation],[pressure],[pressurelevel],[shortcircuitresistant],[standardinvers],[strokelength],[symbolfile],[symbolnr],[technicalcharacteristics],[thread],[triggercurrent],[voltage],[voltagecsa],[voltageiec],[voltagetype],[voltageul],[widthrating],[wirecount],[wirecrosssection],[wirecrosssectionanddiameter],[wirecrosssectionunit],[variant_1],[characteristics],[connectiondescription],[connectiondesignation_1],[description],[functiondefcategory],[functiondefgroup],[functiondefid],[hasled],[hasplugadapter],[idx1],[idx2],[intrinsicsafety_1],[nesteddevicetag],[pos],[safetyrelevant],[symbol],[symbolmacro],[terminalfunction],[terminalnr],[partnr_1],[variant_2],[count],[parentvariant],[pos_1],[22235.0],[22236.0],[22237.0],[22238.0],[22239.0],[22240.0],[22241.0],[22196.1],[22196.2],[22158.1],[22158.2],[22159.1],[22159.2],[22195.1],[22195.2],[22228.1],[22228.2],[22228.3],[22228.4],[22228.5],[22228.6],[22228.7],[22228.8],[22228.9],[22228.10]
The query won't execute on the server (SQL server 2005). When I remove 10 columns, the query executes without any issues. I used the sql profiler to look for any problems that the sql server receives from my application, but no problems found there. I'm really confused why it isn't working.
What I try to achieve is to get a result set of all the columns from the database table WITHOUT the row column. Because if I change the SELECT #param_2 to SELECT *, i'll get all the columns plus and extra column showing the row number. To get rid of this row number I thought I just send all the column names to the SELECT statement of the query. However I'm now running in the above problem. I hope someone knows a solution that either solves the problem from above or how to get a result set of all the columns excluding the row number with a better query than I'm using.
P.s: the size of the ('failing') column string is 3612 characters.
I do not have too much experience with SQL itself. So excuse me if I'm making silly mistakes.
EDIT:
The column names depends on the user, so they can't be hardcoded in the query.
Neither your string literals nor your passed parameters are, themselves, of type nvarchar(max). You're therefore falling foul of this remark about string concatenation:
If the result of the concatenation of strings exceeds the limit of 8,000 bytes, the result is truncated. However, if at least one of the strings concatenated is a large value type, truncation does not occur.
What I'd probably do is change the parameter to explicitly be an nvarchar(max):
sqlCommand.Parameters.Add("#param_2", SqlDbType.NVarChar,-1).Value =
columnString;
Alternatively you could fix it with:
SET #method = CONVERT(nvarchar(max),
' WITH selectRows AS (SELECT *, row=ROW_NUMBER() OVER(ORDER BY(SELECT 1)) FROM [') + ...
Maybe this doesn't answer the question directly but it might be helpful anyway. Why do you need to use dynamioc sql at all? Why can't you use this:
string sql = #"
WITH CTE AS
(
SELECT *,
row=Row_number() OVER (ORDER BY (SELECT 1))
FROM dbo.TableName
SELECT [class],[partnr],[accessoriesidentifier],[canbelinedup],[certificate],[certificate_ce],[certificate_ul],[certificate_vde],[codeletter],[construction],[craftcooling],[craftelectro],[craftfluid],[crafthydraulic],[craftlubrication],[craftmechanic],[craftpneumatic],[craftprocess],[create],[depth],[depthspacingfront],[depthspacingrear],[description1],[description2],[description3],[discount],[ecabinetmacro],[erpnr],[externaldocument1],[externaldocument2],[externaldocument3],[externalplacement],[functiongroup],[graphicmacro],[groupnumber],[groupsymbolmacro],[height],[heightspacingabove],[heightspacingbelow],[identcode],[identtype],[isaccessory],[lastchange],[lifetime],[macro3d],[maintenance],[manufacturer],[mountinglocation],[mountingspace],[note],[ordernr],[packagingprice1],[packagingprice2],[packagingquantity],[picturefile],[piecetype],[priceunit],[productgroup],[productsubgroup],[producttopgroup],[purchaseprice1],[purchaseprice2],[quantityunit],[reportid],[salesprice1],[salesprice2],[snapheight],[snapheightmiddleoffset],[spare],[stress],[supplier],[terminal],[typenr],[uniqueid],[usage],[wear],[weight],[width],[widthspacingleft],[widthspacingright],[barcount],[bardistance],[bargeometry],[barmountingplatedistance],[bottompaneldepth],[bottompaneldistance],[bottompanelprojectionback],[bottompanelprojectionfront],[bottompanelprojectionleft],[bottompanelprojectionright],[busbarholderpartnr],[busbarholdervariant],[busbarrailpartnr],[busbarrailvariant],[deliverylength],[dooroffsetright],[dooroffsettop],[doorthickness],[doortrabbet],[doortype],[hingeposition],[insertpointoffsetx],[profiledepth],[profiledistance],[profileheight],[rearpaneldistance],[rearpaneldpepth],[rearpanelprojectionbottom],[rearpanelprojectionleft],[rearpanelprojectionright],[rearpanelprojectiontop],[sidepaneldepth],[sidepaneldistance],[sidepanelprojectionback],[sidepanelprojectionbottom],[sidepanelprojectionfront],[sidepanelprojectiontop],[toppaneldistance],[toppaneldpepth],[toppanelprojectionback],[toppanelprojectionfront],[toppanelprojectionleft],[toppanelprojectionright],[vprofiledepth],[vprofilewidth],[wallthickness],[widthbottom],[widthtop],[variant],[adjustrange],[adressrange],[advancecontacts],[airgap],[assemblyspreaded],[awgfrom],[awgtill],[bendingradius],[cabledesignation],[cabledisplayform],[cablelength],[cabletype],[cableweight],[coding],[color],[connection],[connectioncrosssection],[connectiondesignation],[connectionmethod],[contactarrangement],[contacttype],[coppernumber],[creepagedistance],[crosssectionfrom],[crosssectiontill],[currentcsa],[currentiec],[currentul],[degofprotection],[design],[doordepth],[doorheight],[doormountingspace],[doorwidth],[electricalcurrent],[electricalpower],[firmwareversion],[flow],[holdingpower],[innerdiameter],[intrinsicsafety],[material],[norm],[outerdiameter],[paneldepth],[panelheight],[panelmountingspace],[panelwidth],[pincount],[pipeclass],[plcdeviceid],[plcisbuscoupler],[plcisbusdistributor],[plciscpu],[plcispowersupply],[plcobjectdescription],[plctype],[powerdissipation],[pressure],[pressurelevel],[shortcircuitresistant],[standardinvers],[strokelength],[symbolfile],[symbolnr],[technicalcharacteristics],[thread],[triggercurrent],[voltage],[voltagecsa],[voltageiec],[voltagetype],[voltageul],[widthrating],[wirecount],[wirecrosssection],[wirecrosssectionanddiameter],[wirecrosssectionunit],[variant_1],[characteristics],[connectiondescription],[connectiondesignation_1],[description],[functiondefcategory],[functiondefgroup],[functiondefid],[hasled],[hasplugadapter],[idx1],[idx2],[intrinsicsafety_1],[nesteddevicetag],[pos],[safetyrelevant],[symbol],[symbolmacro],[terminalfunction],[terminalnr],[partnr_1],[variant_2],[count],[parentvariant],[pos_1],[22235.0],[22236.0],[22237.0],[22238.0],[22239.0],[22240.0],[22241.0],[22196.1],[22196.2],[22158.1],[22158.2],[22159.1],[22159.2],[22195.1],[22195.2],[22228.1],[22228.2],[22228.3],[22228.4],[22228.5],[22228.6],[22228.7],[22228.8],[22228.9],[22228.10]
FROM CTE
WHERE row BETWEEN #StartRow AND #EndRow;";
using(var sqlCommand = new SqlCommand(sql, con))
{
sqlCommand.Parameters.Add("#StartRow", SqlDbType.int).Value = 1;
sqlCommand.Parameters.Add("#EndRow", SqlDbType.int).Value = 30;
// ...
}
Since the column-names are specified by the user you cannot add them as parameter without dynamic sql. But you know all valid column-names, so you could check if all are valid. If you don't know all columns of a the table, have a look here and use reader.GetSchemaTable to load all columns on application start.
Then you can simply add it to the sql:
string sql = #"
WITH CTE AS
(
SELECT *,
row=Row_number() OVER (ORDER BY (SELECT 1))
FROM dbo.TableName
SELECT {0}
FROM CTE
WHERE row BETWEEN #StartRow AND #EndRow;";
sql = string.Format(sql, string.Join(",", columnList));

How to SELECT with optional columns?

I'm currently working on a c# application that grabs a bunch of data from a user specified access(.mdb) database and does a bunch of stuff with that data. A problem that I've recently come across is that some of the a database is missing a column that has existed in all of the others.
How can I do a select on a database, but gracefully fail (throw null in the data or something) when a column doesn't exist in the database?
Currently, my code looks something like this:
OleDbConnection aConnection = new
OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + FileName);
string sqlQuery = "SELECT [Table1].[Index], [Table1].[Optional Info],
[Table2].[Other Info], ....
FROM [Table1] INNER JOIN [Table2] ON [Table1].[Index]=[Table2].[Index]
ORDER BY [Table1].[Index]";
OleDbCommand aCommand = new OleDbCommand(sqlQuery, aConnection);
OleDbDataReader aReader = aCommand.ExecuteReader();
(proceed to read data in line by line, using fabulous magic numbers)
I think it's obvious that this is one of my first experiences with databases. I'm not overly concerned as long as it works, but it's stopped working for a database that does not contain the [Table1].[Optional Info] column. It's throwing an OleDbException: "No value given for one or more required parameters."
Any help would be appreciated.
I might be missing something but...
SELECT Table1.*, Table2.otherInfo
FROM ...
Should do the trick, and let the client process the result set, with an important caveat: there is no way to exclude a column from Table1 in the above.
(I am not aware of any method to "dynamically shape" -- with the viewpoint of the caller -- a SELECT except with a * in the column list as above.)
Happy coding.
The way to do that is to not use magic numbers, but to fetch the field names from the reader and use them - for example GetName etc.
Alternatively, use a mapper like "dapper" that will do this for you.
There is no way to do this in a single query: you cannot run a query that includes columns that don't exist in the source tables. When the server tries to compile the query, it will simply fail.
If you absolutely need to support different scemas, you will need different queries for each of them.
To make things even more awesome, there is no documented way to check if an Access table has a particular column on it via SQL. In SQL Server, you could query the system schema, like sys.objects or sys.columns. In Access, the MsysObjects table has the information you need but it's schema is liable to change on you without notice.
Probably the safest way to go about this is to do a single, up front check where you execute a command such as
SELECT * FROM Table1
then scan the resulting column names to see if your optional column exists; your C# code would then become:
string sqlQuery = string.Empty;
if (optionalColumnExists)
{
sqlQuery = "SELECT [Table1].[Index], [Table1].[Optional Info], -- etc."
}
else
{
sqlQuery = "SELECT [Table1].[Index], '' AS [Optional Info], -- etc."
}
There is a way to extract the table schema using OleDbDataReader.GetSchemaTable and that can be used
OleDbConnection aConnection = new
OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + FileName);
OleDbCommand aCommand = new OleDbCommand("Table1", aConnection);
aCommand.CommandType = CommandType.TableDirect;
aConnection.Open();
OleDbDataReader aReader = cmd.ExecuteReader(CommandBehavior.SchemaOnly);
DataTable schemaTable = aReader.GetSchemaTable();
aReader.Close();
aConnection.Close();
bool optionalInfoColumnExists = schemaTable.Columns.Contains("Optional Info");
Now later in the code
string sqlQuery = #"SELECT [Table1].[Index], {0}
[Table2].[Other Info], ....
FROM [Table1] INNER JOIN [Table2] ON [Table1].[Index]=[Table2].[Index]
ORDER BY [Table1].[Index]";
if (optionalInfoColumnExists)
{
sqlQuery = string.Format(sqlQuery, "[Table1].[Optional Info],");
}
else
{
sqlQuery = string.Format(sqlQuery, "");
}
and while reading use similar logic.
I don't know what kind of application this is but the optionalInfoColumnExists should be populated at the application or session start and reused throughout the life of the app i.e. don't execute the GetSchemaTable everytime a query is run on this table (assuming that the mdb won't change while the app is active).
Either way, it seems like that it is going to make the code to have "if else" just to take care of presense and absence of a column in a table.

How to use string variable in sql statement

I have a WPF Application in which I am getting
string someone = TextBox.text;
I would like to use this in the following query
query = " Select * From Table Where Title = someone "
How should I go about using the variable someone in the query?
You can just do this
query = "Select * From Table Where Title = " + someone;
But that is bad and opens you to SQL Injection
You should just use a parameterized query
Something like this should get you started
using (var cn = new SqlClient.SqlConnection(yourConnectionString))
using (var cmd = new SqlClient.SqlCommand())
{
cn.Open();
cmd.Connection = cn;
cmd.CommandType = CommandType.Text;
cmd.CommandText = "Select * From Table Where Title = #Title";
cmd.Parameters.Add("#Title", someone);
}
From Jon Skeet's answer since his was more complete than mine
See the docs for SqlCommand.Parameters for more information.
Basically you shouldn't embed your values within the SQL itself for various reasons:
It's inelegant to mix code and data
It opens you up to SQL injection
attacks unless you're very careful about escaping
You have to worry about formatting and i18n details for things like numbers, dates and
times etc
When the query remains the same with only the values
changing, the optimizer has less work to do - it can look up the
previous optimized query directly as it'll be a perfect match in
terms of the SQL.
You should use a parameterized SQL query:
query = "SELECT * From TableName WHERE Title = #Title";
command.Parameters.Add("#Title", SqlDbType.VarChar).Value = someone;
See the docs for SqlCommand.Parameters for more information.
Basically you shouldn't embed your values within the SQL itself for various reasons:
It's inelegant to mix code and data
It opens you up to SQL injection attacks unless you're very careful about escaping
You have to worry about formatting and i18n details for things like numbers, dates and times etc
When the query remains the same with only the values changing, the optimizer has less work to do - it can look up the previous optimized query directly as it'll be a perfect match in terms of the SQL.
Easiest is to use a C# Prepared sql. Example on this post. You don't have to worry about escaping the characters in your sql string or anything
declare #SqlQuery varchar(2000), #Fromdate varchar(20), #Todate varchar(20)
set #Fromdate='01 jan 2017'
set #Todate='30 mar 2017'
set #SqlQuery='select * from tblEmployee where tblEmployee.JDate between '''+ #Fromdate + ''' and '''+ #Todate+ ''''
print #SqlQuery

Categories