How to run SQL transactions using C# (Using TSql110Parser) - c#

I have created an ASP.Net C# application to run the SQL server (MSSQL) queries.
The application reads all the user input queries in the text box provided and using the TSql110Parser, it will break down to individual SQL statements.
Its running fine in almost all the cases except when the SQL statements are like the below
DECLARE #user VARCHAR(50)
SET #user = 'ABC'
PRINT #user
SELECT * FROM user_table WHERE username = #user
The execution stops at line 2 and error from SQL server saying that
Must declare the scalar variable "#user"
even though its defined in the first line.
Then I figured out the below way to run. Adding a BEGIN and END statements.
BEGIN
DECLARE #user VARCHAR(50)
SET #user = 'ABC'
PRINT #user
SELECT * FROM user_table WHERE username = #user
END
But still, then there is a limitation that we won't be able to return the results from the SELECT query.
C# code snippet is below
protected void btnQuery_Click(object sender, EventArgs e)
{
if (qry.Length > 0)
{
using (sqlCon = new SqlConnection())
{
dbConnString = dbConnString + "database=" + ddlDBNames.SelectedValue + ";";
sqlCon.ConnectionString = dbConnString;
sqlCon.Open();
cmd = new SqlCommand();
cmd.Connection = sqlCon;
IList<ParseError> Errors;
var parser = new TSql110Parser(false);
var script = parser.Parse(new StringReader(qry), out Errors) as TSqlScript;
if (Errors.Count > 0)
{
lblErrorMessage.Text = "***** Error: No statements executed *****";
}
else
{
foreach (var ts in script.Batches)
{
foreach (var st in ts.Statements)
{
q = qry.Substring(st.StartOffset, st.FragmentLength);
ExecStatement(st, q);
}
}
}
}
}
}
protected void ExecStatement(TSqlStatement statement, string qry)
{
cmd.CommandText = qry;
if (statement is SelectStatement)
{
SqlDataReader dr = cmd.ExecuteReader();
//code to populate the tabular result
}
else
{
cmd.ExecuteNonQuery();
//code to show the non query execution result
}
}
Is there any possible way i can run the SQL queries with persistent connection to DB server so that the declaration in the first line will be able to use through out the queries?
The application screenshot below.

Use CONTEXT_INFO. It is the connection specific global variable.
Its usage is not simple since it is just a 128 bit value. To set it, run the following code (N = the int you want to store connectionwise)
DECLARE #BinVar binary(128);
SET #BinVar = cast(N as binary(128));
set context_info #BinVar
You can get the N in subsequent queries by calling the context_info() function. You should convert it with something like this.
convert(int, context_info())

Related

Request replacing colum name by #variable

I'm trying to develop an application on C# WPF and I try to set the name of the column in the request as #variable. I explain, i have a comboBox with some options. These options are my column's names. When I select one of these options, I have to write in a textBox a word. And when I click on the validation button, the function which executes the display of the table needs two parameters : the value of the combo box and the textbox. And would like to know if I can make a request like
SELECT * FROM customer WHERE #boxContent = #text
I think this is where the problem is.
Here is my code for for xml.cs :
private void Button_Click_1(object sender, RoutedEventArgs e)
{
//InitializeComponent();
text = testBox.Text;
afficheListe.ListeModif(text,boxContent);
}
private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (ID.IsSelected)
{
boxContent = "id_client";
}
else if (Nom.IsSelected)
{
boxContent = "nom";
}
else if (Prenom.IsSelected)
{
boxContent = "prenom";
}
else if (Sexe.IsSelected)
{
boxContent = "sexe";
}
else if (DateNaissance.IsSelected)
{
boxContent = "date_Naiss";
}
else if (Mail.IsSelected)
{
boxContent = "mail";
}
else if (Adresse.IsSelected)
{
boxContent = "adresse";
}
else if (Pays.IsSelected)
{
boxContent = "pays";
}
}
Here is my code for request' function :
public void ListeModif(string text, string boxContent)
{
bdd.connection.Open();
MySqlCommand cmd = bdd.connection.CreateCommand();
cmd.Parameters.AddWithValue("#text", text);
cmd.Parameters.AddWithValue("#boxContent", boxContent);
cmd.CommandText = " SELECT * FROM client WHERE #boxContent = #text";
MessageBox.Show(cmd.ToString());
cmd.ExecuteNonQuery();
MySqlDataReader reader = cmd.ExecuteReader();
clients.Clear();
while (reader.Read())
{
clients.Add(new Client()
{
Id = Convert.ToInt32(reader["id_Client"]),
Nom = Convert.ToString(reader["nom"]),
Prenom = Convert.ToString(reader["prenom"]),
Sexe = Convert.ToString(reader["sexe"]),
Date_Naissance = Convert.ToDateTime(reader["date_Naiss"]),
Mail = Convert.ToString(reader["mail"]),
Adresse = Convert.ToString(reader["adresse"]),
Pays = Convert.ToString(reader["pays"])
});
}
reader.Close();
bdd.connection.Close();
}
When I use the debugger, the value of cmd variable is:
MySql.Data.MySqlClient.MySqlCommand}
Hoping I explained my problem well.
And would like to know if i can make a request like
"SELECT * FROM customer where #boxContent = #text "
No, you cannot. In SQL (in general, not just in SQL Server) you can replace constant values with parameters. You cannot replace other types of values:
Identifiers (table names, column names, etc.)
Operators
Function names
SQL key words
In other words, parameter replacement is not string substitution. You can do that in the application when you are constructing the query string. But you cannot pass the value as a parameter. Note: Be careful doing this in the application because the code is subject to SQL injection attacks.
Sometimes, I handle the identifier replacement by doing something like:
SET #SQL = 'SELECT * FROM customer where #boxContent = #text';
SET #SQL = REPLACE(#SQL, '#boxContent', #boxContent);
(This is T-SQL syntax.) That is, the value is replaced before the query is executed.
This may seem like an arcane restriction. But one of the purposes of prepared statements is to pre-compile the query -- saving on the expensive of parsing, compiling, and optimizing the query. In an environment where you have many small queries all of the same form being processed, this can be an important performance enhancement.
No, that's wrong. You can't have the column name dynamically replaced like that. Rather you can do like below and replace the value of boxContent variable
cmd.CommandText = $"SELECT * FROM client where {boxContent} = #text ";
If you are using C# version lower than 6 then use string.Format()
cmd.CommandText = string.Format("SELECT * FROM client where {0} = #text ", boxContent);

Application returns data for me but not other users, even when I log in under their username - SQL Server/C#/WPF

Having a little bit of a strange error here that I have never encountered before. I have an application where users can type in a list of accounts in a datagrid and a date range and press a button and it will return the data for these accounts in a datagrid and give them the option to export it to an excel file. This works perfectly for me, logged in under my username and even when I log in under other people's username. The problem is when they try it, they get no data back. No errors, just it doesn't pull any data.
The interesting thing is this is all in the same database as the other information which they access without any problem. The only difference, which I think might be the explanation is I am calling this SQL code directly from the Application whereas everything else is called using stored procedures that sit on the server. The reason for this is I have to concatenate the SQL Query string for each item in the accounts field. Since they are able to enter as many accounts as they want, I cannot use a stored procedure since I don't know how many parameters it will have ultimately(if someone could let me know a method of doing this, I would actually prefer this way for keeping things consistent). Obviously the query string is working properly, as it's pulling data back for me, but the question I have is why is it failing to return data for others? The connection string is an SQL Authentication, so it shouldn't have anything to do with them not having Windows Authentication on the server, plus they are already able to log in to the application and it displays data on their dashboard, which couldn't happen...
Anyone that can point me in the right direction with this I would appreciate it...the only thing I can think of is it is an issue with using an in-code SQL string versus a stored procedure, but this doesn't make any sense since other people do this all the time in applications without issue.
public ICommand GetData
{
get => new RelayCommand(() =>
{
//call the SQL Code to lookup the account numbers
var SQLStr = "SELECT * FROM [Clients].[Data] WHERE (Account_Number = '";
for (var i = 0; i< AccountNums.Count; i++)
{
if (!String.IsNullOrEmpty(AccountNums[i].accNum)) SQLStr += i == 0 ? $"{AccountNums[i].accNum}'" : $" OR Account_Number = '{AccountNums[i].accNum}'";
}
SQLStr += $") AND SUB_QUERY_CREATED_ON BETWEEN '{StartDate.ToString()}' AND '{EndDate.ToString()}'";
_Data = DBMethods.GetSQLData(_Data, new Models.Clients.Data(), SQLStr, new List<string> { "ID" }, true);
ShowResPnl = true; //there are results, toggle the panel visibility bound variable
});
}
public static ObservableCollection<T> GetSQLData<T>(ObservableCollection<T> myCollection, T myClass, String SQLString, List<string> remParams, bool UseSQLQuery) where T : class
{
var conn = new SqlConnection();
try
{
var paramList = GenerateSQLParameters(myClass, remParams);
using (getConnection(conn))
{
conn.Open();
using (SqlCommand cmd = new SqlCommand(SQLString, conn))
{
cmd.CommandType = CommandType.Text;
SqlDataReader reader;
reader = cmd.ExecuteReader();
//only execute if the reader has data
if (reader.HasRows)
{
while (reader.Read())
{
var tempModel = Global.GenerateNewInstance(myClass) as T;
Type model = tempModel.GetType();
var prop = model.GetProperties();
PropertyInfo pi;
//set the values for each property in the model
foreach (var p in prop)
{
if (!remParams.Contains(p.Name))
{
pi = tempModel.GetType().GetProperty(p.Name);
if (reader[p.Name] == DBNull.Value)
{
pi.SetValue(tempModel, null);
}
else
{
pi.SetValue(tempModel, reader[p.Name]);
}
}
}
myCollection.Add(tempModel);
}
reader.Close();
cmd.Dispose();
}
}
}
}
catch (Exception ex)
{
ErrorWindow errWin = new ErrorWindow("There was a problem trying to Get the Data with the Query '" + SQLString + "'! Error: " + ex.Message);
errWin.Show();
}
return myCollection;
}
UPDATE: OK I got it working perfectly with help from THIS thread:
How do I split a string so I can access item x?
and more specifically this post:
What about using string and values() statement?
DECLARE #str varchar(max)
SET #str = 'Hello John Smith'
DECLARE #separator varchar(max)
SET #separator = ' '
DECLARE #Splited TABLE(id int IDENTITY(1,1), item varchar(max))
SET #str = REPLACE(#str, #separator, '''),(''')
SET #str = 'SELECT * FROM (VALUES(''' + #str + ''')) AS V(A)'
INSERT INTO #Splited
EXEC(#str)
SELECT * FROM #Splited
I created a stored procedure using this, then did a left join on Account numbers from the Data Table and used a WHERE clause to set the Start and End Dates and exclude items that were NULL(checked one of the columns). Works perfectly and only took about 2 or 3 seconds to return the data. I had another working method as detailed here https://sqlperformance.com/2012/07/t-sql-queries/split-strings#comments using a function which was taking well over a minute to return data for only 4 accounts...obviously was not going to work well enough so I found the method mentioned prior and it works excellently!

How do i display a select stored procedure result in a textbox?

I need to display the result from a select statement in a stored procedure onto the textbox and I can't figure out how to do it. The select statement doesn't use a WHERE clause. The stored procedure goes
CREATE PROCEDURE NewCustomer
AS
BEGIN
SELECT MAX(ID) + 1 FROM Database
END
This is what I've tried
protected void btnNew_Click(object sender, EventArgs e)
{
Clear();
int num;
try
{
using (SqlCommand command = new SqlCommand("NewCustomer"))
{
command.Connection = conn;
command.CommandType = CommandType.StoredProcedure;
command.Parameters.Add("#CustID", SqlDbType.Int).Value = Int32.TryParse(txtCID.Text, out num); // Use tryparse if needed
conn.Open();
txtCID.Text = (string)command.ExecuteScalar();
}
}
catch (Exception ex)
{
lblMessage.Text = ex.Message;
}
}
It gives me a "Procedure NewCID has no parameters and arguments were supplied." Error
You are not executing the procedure that you ware given. The procedure is named as yadayada(The worst name that you can give) and you are executing the procedure NewCustomer as the command text. Both has to be same. Then you are using the Wrong statement for executing the query.
The ExecuteNonQuery to perform catalog operations (for example,
querying the structure of a database or creating database objects such
as tables), or to change the data in a database without using a
DataSet by executing UPDATE, INSERT, or DELETE statements.
But you are using it for executing the select query. Here you are selecting a single value from the table so the ExecuteScalar will be the best option for you. Your code will be like this: assume the procedure name is GetNewCustomerID;
using (SqlCommand exeCommand = new SqlCommand("GetNewCustomerID"))
{
exeCommand.Connection = conn;
exeCommand.CommandType = CommandType.StoredProcedure;
exeCommand.Parameters.Add("#CustID",SqlDbType.Int).Value=Convert.ToInt32(txtCID.Text); // Use tryparse if needed
conn.Open();
txtCID.Text = (string)exeCommand.ExecuteScalar();
}

ASP.NET, C# How to Pass a StringQuery to a custom SQL Command

I have a slight issue, I have a ASP.NET Webforms application. I'm sending over a url?id=X were X is my database index or id.
I have a C# class file to run my SQL connection and query. Here is the code:
public DataTable ViewProduct(string id)
{
try
{
string cmdStr = "SELECT * Products WHERE Idx_ProductId = " + id;
DBOps dbops = new DBOps();
DataTable vpTbl = dbops.RetrieveTable(cmdStr, ConfigurationManager.ConnectionStrings["MyDatabase"].ConnectionString);
return vpTbl;
}
catch (Exception e)
{
return null;
}
}
So as you can see my problem lies within string cmdStr = "SQL Query" + variable;
I'm passing over my index or id through the URL then requesting it and turning it into a string then using ViewProduct(productId).
I don't know what syntax or how to add the id into my C# string sql query. I've tried:
string cmdStr = "SELECT * Products WHERE Idx_ProductId = #0" + id;
string cmdStr = "SELECT * Products WHERE Idx_ProductId = {0}" + id;
also what I have currently to no avail.
I was so sure this would be a duplicate of some canonical question about parameterized queries in C#, but apparently there isn't one (see this)!
You should parameterize your query - if you don't, you run the risk of a malicious piece of code injecting itself into your query. For example, if your current code could run against the database, it would be trivial to make that code do something like this:
// string id = "1 OR 1=1"
"SELECT * Products WHERE Idx_ProductId = 1 OR 1=1" // will return all product rows
// string id = "NULL; SELECT * FROM UserPasswords" - return contents of another table
// string id = "NULL; DROP TABLE Products" - uh oh
// etc....
ADO.NET provides very simple functionality to parameterize your queries, and your DBOps class most assuredly is not using it (you're passing in a built up command string). Instead you should do something like this:
public DataTable ViewProduct(string id)
{
try
{
string connStr = ConfigurationManager.ConnectionStrings["MyDatabase"].ConnectionString;
using (SqlConnection conn = new SqlConnection(connStr))
{
conn.Open();
using (SqlCommand cmd = conn.CreateCommand())
{
// #id is very important here!
// this should really be refactored - SELECT * is a bad idea
// someone might add or remove a column you expect, or change the order of columns at some point
cmd.CommandText = "SELECT * Products WHERE Idx_ProductId = #id";
// this will properly escape/prevent malicious versions of id
// use the correct type - if it's int, SqlDbType.Int, etc.
cmd.Parameters.Add("#id", SqlDbType.Varchar).Value = id;
using (SqlDataReader reader = cmd.ExecuteReader())
{
DataTable vpTbl = new DataTable();
vpTbl.Load(reader);
return vpTbl;
}
}
}
}
catch (Exception e)
{
// do some meaningful logging, possibly "throw;" exception - don't just return null!
// callers won't know why null got returned - because there are no rows? because the connection couldn't be made to the database? because of something else?
}
}
Now, if someone tries to pass "NULL; SELECT * FROM SensitiveData", it will be properly parameterized. ADO.NET/Sql Server will convert this to:
DECLARE #id VARCHAR(100) = 'NULL; SELECT * FROM SensitiveData';
SELECT * FROM PRoducts WHERE Idx_ProductId = #id;
which will return no results (unless you have a Idx_ProductId that actually is that string) instead of returning the results of the second SELECT.
Some additional reading:
https://security.stackexchange.com/questions/25684/how-can-i-explain-sql-injection-without-technical-jargon
Difference between Parameters.Add and Parameters.AddWithValue
SQL injection on INSERT
Avoiding SQL injection without parameters
How do I create a parameterized SQL query? Why Should I? (VB.NET)
How can I prevent SQL injection in PHP? (PHP specific, but many helpful points)
Is there a canonical question telling people why they should use SQL parameters?
What type Products.Idx_ProductId is?
Probably it is string, than you need to use quotes: "... = '" + id.Trim() + "'";

Executing an update stored procedure in C#

I need to know, if I am writing the stored procedure correctly and If the C# code for executing is correct. for some reason the error being returned as is Incorrect syntax near 'c16b'. Old Error
The new error now is: Procedure or function 'sptimeupdate' expects parameter '#date', which was not supplied.
the nvarchar string for validating and updating in the column by ClientID is 3fc8ffa1-c16b-4d7b-9e55-1e88dfe15277, but the part in bold is only showing in the debug test intel sense in error handling
ALTER PROCEDURE sptimeupdate
#id nvarchar(50),
#date datetime
AS
BEGIN
SET NOCOUNT ON;
UPDATE ClientTable
SET Today_Date=(#date)
WHERE ClientID=(#id)
END
//--------------above stored procedure--------------------------------
//--------------Executing the stored procedure in C#
IEnumerable<XElement> searchClientID =
from clientid in main.XPathSelectElements("Network/ClientID")
where (string)clientid.Attribute("id") == IntializedPorts[i].ToString()
select clientid;
foreach (string clientid in searchClientID)
{
for (int up = 0; up < IntializedPorts.Count(); up++)
{
//Update the current time in the clientid tble.
//Renames the table copy for groups
try
{
string[] Clientid; //client id array
Clientid = new string[IntializedPorts.Count()]; //Intialization of the array
Clientid[up] = clientid.ToString();
DateTime td = Convert.ToDateTime(toolDate.Text); //Just added a datetime object withdate
SqlConnection sqlConnectionCmdString = new SqlConnection(#"Data=.\SQLEXPRESS;AttachDbFilename=C:\Users\Shawn\Documents\Visual Studio 2010\Projects\Server\database\ClientRegit.mdf;Integrated Security=True;User Instance=True");
//EXECUTE THE STORED PROCEDURE sptimedate
// string UpdateCommand = "sptimeupdate" + Clientid[up].ToString() + toolDate.Text;
string UpdateCommand = "sptimeupdate" + "'" + Clientid[up].ToString() + "'" + "'" +td.ToString()+ "'"; //this is the new UpdateCommand string as to pass parameters to stored procedure
SqlCommand sqlRenameCommand = new SqlCommand(UpdateCommand, sqlConnectionCmdString);
sqlConnectionCmdString.Open();
sqlRenameCommand.ExecuteNonQuery();
sqlConnectionCmdString.Close();
}
catch(DataException ex)
{ MessageBox.Show("Failed to UpdateCurrentTime","DataError",MessageBoxButtons.OK,MessageBoxIcon.Error);
}
}
}
When you call a stored procedure from code you need to create a command with its command type set to StoredProcedure, otherwise the engine tries to use your command text as it was an sql text like SELECT INSERT etc... But the mose important thing is that you need to pass the parameters required by the stored procedure in the Parameters collection of the command
So this could be the code to replace the actual one
string UpdateCommand = "sptimeupdate";
using(SqlConnection sqlConnectionCmdString = new SqlConnection(......))
using(SqlCommand sqlRenameCommand = new SqlCommand(UpdateCommand, sqlConnectionCmdString))
{
DateTime td = Convert.ToDateTime(toolDate.Text);
sqlRenameCommand.CommandType = CommandType.StoredProcedure;
sqlRenameCommand.Parameters.Add("#id", SqlDbType.NVarChar).Value = Clientid[up].ToString();
sqlRenameCommand.Parameters.Add("#date", SqlDbType.DateTime).Value = td;
sqlConnectionCmdString.Open();
sqlRenameCommand.ExecuteNonQuery();
}
Notice two things. The using statement is the best practice to follow when you create a connection to ensure the correct closing and disposing of the connection, second, the parameter for the DateTime expected by the sp should be passed as a DateTime not as a string- Of course this means that you should be certain that the content of toolDate is convertible to a DateTime value.
Your error is originating from this line of code:
string UpdateCommand = "sptimeupdate" + Clientid[up].ToString() + toolDate.Text;
There you are just concatenating the Clientid[up].ToString() as a string into the other string, same with the toolDate.Text, both without and sql markup.
Your resulting SQL query would look like this (assuming toolDate.Text is '2014-10-23'):
sptimeupdate3fc8ffa1-c16b-4d7b-9e55-1e88dfe152772014-10-23
which as you can see is not a proper SQL command.
You should always use parametrized command statements when calling simple SQL commands.
However in your case, you are actually calling a stored procedure.
So change your code to handle it like a stored procedure, example below.
// Create the connection object once
using (SqlConnection sqlConnectionCmdString = new SqlConnection(#"Data=.\SQLEXPRESS;AttachDbFilename=C:\Users\Shawn\Documents\Visual Studio 2010\Projects\Server\database\ClientRegit.mdf;Integrated Security=True;User Instance=True"))
{
// Same with the SqlCommand object and adding the parameters once also
SqlCommand sqlRenameCommand = new SqlCommand("sptimeupdate", sqlConnectionCmdString);
sqlRenameCommand.CommandType = CommandType.StoredProcedure;
sqlRenameCommand.Parameters.Add("#id", SqlDbType.NVarChar);
sqlRenameCommand.Parameters.Add("#datetime", SqlDbType.DateTime);
// Open the connection once only
sqlConnectionCmdString.Open();
foreach (string clientid in searchClientID)
{
for (int up = 0; up < IntializedPorts.Count; up++)
{
try
{
// The below three lines seem redundant.
// Clientid[up] will be equal to clientid after it all, so just use clientid
//string[] Clientid;
//Clientid = new string[IntializedPorts.Count];
//Clientid[up] = clientid.ToString();
sqlRenameCommand.Parameters["#id"].Value = clientid;
sqlRenameCommand.Parameters["#datetime"].Value = toolDate.Text;
sqlRenameCommand.ExecuteNonQuery();
}
// Might want to move this try..catch outside the two loops,
// otherwise you will get this message each time an error happens
// which might be alot, depending on the side of searchClientID
catch (SqlException)
{
MessageBox.Show("Failed to UpdateCurrentTime", "DataError", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
}
NOTE:
Please read the comments inside the code above for additional advice and suggestions.
Recreating a SqlConnection and SqlCommand for each iteration will have a performance impact on your application. So rather create them once and reuse them until you are done.
Further reading can be done here:
SqlCommand (There are nice examples at the bottom of that page)
dotnetperls version of SqlCommand
P.S. your sql procedure's code looks fine, you could remove the SET NOTCOUNT ON since that does not really do much in this scenario

Categories