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.
Related
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'".
I'm trying to get the following code to work:
String connStr = sqlRoutines.connectionString;
SqlConnection sqlConn = new SqlConnection(connStr);
sqlConn.Open();
SqlCommand cmd = new SqlCommand();
cmd.Connection = sqlConn;
cmd.CommandType = CommandType.Text;
cmd.CommandText = "SELECT * FROM TABEL=#tabel";
cmd.Parameters.Add("#tabel", SqlDbType.Text).Value = DataContainer.sqlTabel;
SqlDataReader reader = cmd.ExecuteReader();
Console.WriteLine(reader.FieldCount.ToString());
reader.Close();
sqlConn.Close();
Somehow the value "DataContainer.sqlTabel" is not added to the command. Am I missing something here?
Whenever I use cmd.CommandText = "SELECT * FROM" + DataContainer.sqlTabel; everything is working fine. However I want to avoid this method because of the SQL Injection.
Thanks in advance!
EDIT:
I want to achieve a command that uses a variable (which is changed by the user). So I want to have something like this: SELECT * FROM * a variable defined by the use *;. When I use:
cmd.CommandText = "SELECT * FROM #tablename";
cmd.Parameters.Add("#tablename", SqlDbType.Text).Value = DataContainer.sqlTabel;
It doesn't work as well.
I think you try to parameterize table name which you can't. I don't understand what is TABEL either. FROM (Transact-SQL) doesn't have a syntax like that.
You can only parameterize values. Not table names or column names. Specify the table name as part of the SQL. But when you do that, you need very strong validation on the table name before putting it into the SQL, or have a whitelisted set of valid table names, in order to avoid SQL injection attacks.
If you really want to parameterize it, you can use (but not recommended) Dynamic SQL.
SELECT * FROM #tablename
As we have seen, we can make this procedure work with help of dynamic
SQL, but it should also be clear that we gain none of the advantages
with generating that dynamic SQL in a stored procedure. You could just
as well send the dynamic SQL from the client. So, OK: 1) if the SQL
statement is very complex, you save some network traffic and you do
encapsulation. 2) As we have seen, starting with SQL 2005 there are
methods to deal with permissions. Nevertheless, this is a bad idea.
Also use using statement to dispose your database connections.
using(SqlConnection sqlConn = new SqlConnection(connStr))
using(SqlCommand cmd = sqlConn.CreateCommand())
{
...
...
using(SqlDataReader reader = cmd.ExecuteReader())
{
...
}
}
You can't do this. You can only use dynamic SQL. You need to decide for yourself whether SQL injection is a risk and if so you need to code for this, perhaps checking that the value of table doesn't contain anything other than a valid table name. You can check the table name against the system tables to ensure it is valid.
we can't use parameters for either table name or column name. parameters only limited for values. if you want to use parameter for table name then you've to create a stored procedure and then pass the table name as parameter to store procedure. more information can be found here
Table name as parameter
In C# I want to execute a query that use 2 different databases (One is Access for local, and other is distant and is MySQL)
I'm able to do it in VBA Access, but how I can make the same thing in C# ??
This is how I made it in Access:
Link my 2 differents table/databases in Table
In VBA:
sSQL = "INSERT INTO DB1tblClient SELECT * FROM DB2tblClient"
CurrentDb.Execute sSQL
How I can execute this SQL in C# ? (What object to use, etc... Example code if you can)
Thanks !
There are two ways to do this. One is to set up linked tables on Access and run a single query. The other is to run both queries from c# and join them with linq.
The first way is better. If you really have to do it with linq, here is some sample code:
dWConnection.Open();
dWDataAdaptor.SelectCommand = dWCommand1;
dWDataAdaptor.Fill(queryResults1);
dWDataAdaptor.SelectCommand = dWCommand2;
dWDataAdaptor.Fill(queryResults2);
dWConnection.Close();
IEnumerable<DataRow> results1 = (from events in queryResults1.AsEnumerable()
where events.Field<string>("event_code").ToString() == "A01"
|| events.Field<string>("event_code").ToString() == "ST"
select events ) as IEnumerable<DataRow>;
var results2 = from events1 in queryResults1.AsEnumerable()
join events2 in queryResults2.AsEnumerable()
on (string)events1["event_code"] equals (string)events2["event_code"]
select new
{
f1 = (string)events1["event_code"],
f2 = (string)events2["event_name"]
};
DataTable newDataTable = new DataTable();
newDataTable = results1.CopyToDataTable<DataRow>();
See why I said linked tables is better?
You should be able to run the same SQL command from any app, really. This is assuming:
You're connecting to Access from your C# app
DB1tblClient is a local Access table
DB2tblClient is a link table in Access
Given these, you might try the following:
using (OleDbConnection conn = new OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\Stuff\MyAccessdb.mdb"))
{
conn.Open();
using (OleDbCommand cmd = conn.CreateCommand())
{
cmd.CommandText = "INSERT INTO DB1tblClient SELECT * FROM DB2tblClient";
cmd.ExecuteNonQuery();
}
}
You might want to check connectionstrings.com if you can't get the connection string right, and you may need to install some components (MDAC or ACE) for connections that use those providers.
Well it is not possible to run this such complex query with single statement.
Basically each query execution object initialized by particular database information,
so need two different object for each database first think.
Now 2 Object need with initialized with its own connection object.
Just fetch data by first object and insert it to another database by usin second connection object.
You need to keep following points in mind before trying this type of query
Both the databases are accessible from your code.
There is inter-connectivity between both the database.
Both the databases are available for the user that you are using to execute this query.
You need to specify the query in following format
DATABASE_NAME.SCHEMA_NAME.TABLE_NAME instead of just TABLE_NAME
EDIT
If you don't have inter-connectivity between databases you can follow following steps
Connect to Source database using one connection.
Read the data from source database into a dataset or datatable using SELECT query.
Connect to target database using a second connection.
Insert all the records one by one using a loop to TARGET Database using standard INSERT query
As a reaction to MySqlParameter as TableName, it seems that MySQL's Connector/NET library doesn't support table names from user input in the following way:
MySqlCommand cmd = new MySqlCommand("SELECT * FROM #table");
cmd.Parameters.AddWithValue("#table",TableNameFromUserInput);
So I tried to find another way, but I couldn't find any libraries that did this for me.
When searching for how to do this manually, I couldn't found anything that didn't tell you to use "prepared statements" from some already defined library, no matter the language.
To my limited knowledge of SQL, I only think it's necessary to put backticks (`) around the user input and then check the input for backticks - but I wanted to be sure.
Question
How do you protect a user specified table name from SQL-Injection when the MySQL Connector doesn't support it?
Check if
TableNameFromUserInput
is an existing table, before executing the query.
That prevents other errors like "table doesnt exist" aswell.
you cannot add tableName (as well as columnName) as parameter in prepared statements because it only supports for a value. For your safety, add additional code to validate tableName, ex.
string tableName = "hello";
tableName = UDFunctionClean(tableName);
string query = String.Format("SELECT * FROM `{0}`", tableName);
MySqlCommand cmd = new MySqlCommand(query);
I want to retrieve the table schema information using C#.
I used the sp_help <table_name> query to retrieve it, which works well when I execute it in query window but fail to retrieve it from C#. I want to retrieve all table information which is necessary for mapping between two tables such as name, datatype, size, isprimary, key, etc.
I had write down following code
SqlCommand cmd = sourceCon.CreateCommand();
cmd.CommandText = string.Format("sp_help '{0}'", cmbSourceTable.SelectedItem.ToString()); //cmd.CommandType = CommandType.StoredProcedure;
sourceDataTable = new DataTable();
SqlDataReader dr = cmd.ExecuteReader();
sourceDataTable.Load(dr);
dr.Close();
It will returns only info about table that when it is created or not
Instead of using sp_help, you could try the following options:
1) use INFORMATION_SCHEMA.COLUMNS view:
SELECT *
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'YourTable'
2) query sys.columns
SELECT c.*, t.name
FROM sys.columns c
JOIN sys.types t ON c.user_type_id = t.user_type_id
WHERE c.object_id = OBJECT_ID('YourTable')
These are just 2 options that will return a single resultset. INFORMATION_SCHEMA.COLUMNS doesn't tell you (e.g.) if a column is an IDENTITY column, wherease the sys.columns route does. There's more information you can get from the catalog views, just depends what else you need. Here's the MSDN reference.
You might find it easier to play with the INFORMATION_SCHEMA views - you can build queries that will extract most of the data relating to the structure of and relationship between tables within a SQL Server database schema.
See here: http://msdn.microsoft.com/en-us/library/ms186778.aspx
I got a simple way to get schema of a table.
Just create a connection with any provider like SqlConnection object.
Create a command object for select query (select only top 1 record as you want only schema of table)
Execute that query which return a DataReader object.
Every DataReader object have a method called GetTableSchema() which returns DataTable object which have schema of a perticulat table.
This way you can easily get schema of any table with less effort.
Here I will provide a simple code for this
SqlConnection con=new SqlConnection("connString");
con.Open();
SqlCommand cmd= new SqlCommand("select top 0 * from Person.Address",con);
DataTable table = cmd.ExecuteReader().GetTableSchema();
Now this table object have schema information of Person.Address table.
I think sp_help returns multiple result sets so you'll need to use NextResult, there are more details here - http://support.microsoft.com/kb/311274 (the example is not sp_help specific but it should give you the general idea)
Also, you can use the GetSchema method from SqlConnection. http://msdn.microsoft.com/en-us/library/ms136367.aspx
like so:
var tableSchema = con.GetSchema(SqlClientMetaDataCollectionNames.Tables, new string[] { null, null, "[tableName]" });
The string array is a 'filter', here you can find the columns you can filter on: http://msdn.microsoft.com/en-us/library/ms254969.aspx
For other database servers and more info:
http://msdn.microsoft.com/en-us/library/kcax58fh.aspx
sp_help doesn't work in your code? maybe you need to include an execute statement: exec sp_help. or maybe the application is running under an account that doesn't have permission for sp_help.
Since .net 2.0 the SqlConnection class offers GetSchemaMethod you can use to retrieve the requested info.
http://msdn.microsoft.com/en-us/library/ms136364(v=vs.80).aspx
See this one ... it explains how
How To Retrieve Schema Information by Using GetOleDbSchemaTable and Visual C# .NET