SqlCommand.ExecuteReader Fails to Report Errors - sql

I'm executing a SQL command to create a new record in a database table and get the ID of the created record. However, there's a constraint error generated by the SQL command (uninitialized non-null field) which is not being picked up by the VB code. The code is roughly:-
connection = New SqlConnection(connection_string)
connection.Open()
sql_command = New SqlCommand(command) 'command = the SQL command to execute
sql_command.Connection = connection
sql_command.Parameters.AddRange(sql_parameters.ToArray()) ' sql_parameters is a parameter to the function
reader = sql_command.ExecuteReader()
If reader IsNot Nothing Then
If reader.HasRows Then
While reader.Read
response_handler(reader, data) 'response handler is a callback which populates the data object
End While
End If
reader.Close()
End If
The reader object is non-null but contains no data and no exception is generated. The SQL command is:-
insert into [table] ([column1], [column2], [column3], [column4])
output Inserted.[pk]
values (#1, #2, #3, #4)
Executing the SQL statement using SQL Server Management Studio, I get the error:-
Msg 515, Level 16, State 2, Line 2
Cannot insert the value NULL into column 'somecolumn', table 'tablename'; column does not allow nulls. INSERT fails.
I have also added a handler for the InfoMessage event on the SqlConnection object but that doesn't get called, even when I set FireInfoMessageEventOnUserErrors to true.
Why am I not getting an error and what is the correct way to ensure the error is reported to VB?
I'm using Visual Studio 2008.

The HasRows call returns false if there is an error. That way you will never see the error. Remove both If statements. The null check is redundant, the other one suppresses errors.

The severity level is 16 which does not interrupt the current session. If you want to throw a hard error you could add something like this directly after your insert...
If ##Error <> 0
Begin
Raiserror('Constraint Error Encountered',20,1) With Log;
End
The security context will have to have sysadmin rights in order to perform the RAISERROR WITH LOG, but if you can't do this I'm sure there are other ways to throw a hard error. In any event a warning isn't going to throw an error to your VB code.

I don't know exactly how it works internally, but the property HasRows is set by a private method within the SqlDataReader class called something like TryGetNextResult, which uses a lot of try/catch blocks to set a boolean output parameter, so this method never throws any exception, or even provides any feedback about any errors. What I can't work out is exactly how it realises there will be errors without even attempting the insert, but it does.
With this sample table:
CREATE TABLE T (ID INT IDENTITY, A INT NOT NULL);
I ran:
string sql = #"INSERT T (A) OUTPUT inserted.ID, inserted.A VALUES (1);";
using (var connection = new SqlConnection(connectionString))
using (var command = new SqlCommand(sql, connection))
{
connection.Open();
using (var reader = command.ExecuteReader())
{
Console.WriteLine(reader.HasRows); // True
while (reader.Read())
{
Console.WriteLine(reader.GetInt32(1)); // 1
}
}
}
When I changed this to try and insert null, reader.HasRows was false, but by putting a breakpoint in I was able to test before reader.Read() was called, and this shows that the identity value of the table T was unchanged, so the reader can't be validating inserts by simply rolling back transactions. It is a mystery to me why a SqlDataReader is able to identify that this insert will fail before execution, but yet if the SQL is executed it will still attempt the insert before raising an error.
To further prove the behaviour I ran a similar test with a simple select command, and saw the same behaviour:
static void Main(string[] args)
{
string sql = #" SELECT Date = CAST(D AS DATE)
FROM (VALUES
(1, '20130129'),
(2, '20130130'),
(3, '20130131'),
(4, '20130132')
) t (A, D)
ORDER BY A;";
using (var connection = new SqlConnection(connectionString))
using (var command = new SqlCommand(sql, connection))
{
connection.Open();
using (var reader = command.ExecuteReader())
{
Console.WriteLine(reader.HasRows); // false
while (reader.Read()) // Exception
{
Console.WriteLine(reader.GetString(0));
}
}
}
}
Again this shows, that somehow the SqlDataReader has picked up that there will be an error casting 20130132 to a date. If I remove this row from the SQL the HasRows property becomes true.
I realise this doesn't actually answer your question but it was too long for a comment, hopefully it will help a little, and/or maybe prompt somebody who knows a lot more about c# than me to add their own answer.

Related

SQL Server update in C#

I try to UPDATE data in my SQL Server database and I get this error:
System.Data.SqlClient.SqlException
Incorrect syntax near 'de'
Unclosed quotation mark after the character string ')'
private void BtEnrMod_Click(object sender, EventArgs e)
{
SqlConnection con = new SqlConnection("Data Source=.\\BD4X4;Initial Catalog=BD4X4;Integrated Security=True");
con.Open();
SqlCommand cmd = new SqlCommand("UPDATE Service SET Type = " + TxBxService.Text + ", Prix = " + TxBxPrix.Text + "WHERE Code = " + LbCodeAff.Text + "')", con);
int i = cmd.ExecuteNonQuery();
if (i != 0)
{
MessageBox.Show("Service Modifié");
}
else
{
MessageBox.Show("Erreur");
}
this.Close();
con.Close();
}
Replace the one liner that declares your command with this code block:
SqlCommand cmd = new SqlCommand("UPDATE Service SET Type = #t, Prix = #p WHERE Code = #c", con);
cmd.Parameters.AddWithValue("#t", TxBxService.Text);
cmd.Parameters.AddWithValue("#p", TxBxPrix.Text);
cmd.Parameters.AddWithValue("#t", LbCodeAff.Text);
Always avoid writing an sql where you string concatenate in a value provided by the user in a text box; it's the number one security horror you can make with sql. Always use parameters to put values in, like you see here. For more info on this SQL injection hacking, see http://bobby-tables.com
If you ever fin yourself in a situation where you think you have to concatenate to make an sql, don't concatenate a value in; concatenate a parameter in and add the value into the parameters collection. Here's a hypothetical example:
var cmd = new SqlCommand("","connstr");
strSql = "SELECT * FROM table WHERE col IN (";
string[] vals = new[]{ "a", "b", "c" };
for(int x = 0; x<vals.Length; x++){
strSql += ("#p"+x+",");
cmd.Parameters.AddWithValue("#p"+x, vals[x]);
}
cmd.CommandText = strSql + ")";
This uses concatenation to make an sql of SELECT * FROM table WHERE col IN (#p0, #p1, #p2) and a nicely populated parameters collection
When you're done grokking that, read the link Larnu posted in the comments. There are good reasons to avoid using AddWithValue in various scenarios but it will always be preferable to concatenation of values. Never ditch the use of parameters "because I read a blog one time about how AddWithValue is bad" - form parameters using the new parameter constructor, or use AddWithValue shortcut, but never concat values
Or better still than all of this, use an ORM like Entity Framework, nHibernate or Dapper and leave most of this boring boilerplate low level SQL drudgery behind. These libraries do most of this wrangling for you; EF and nH even write th sql too, dapper you write it yourself but it takes care of everything else
Using a good ORM is like the difference between writing creating a UI manually line by line of position, font, anchor, event code for every button, label and text box versus using the windows forms designer; a world apart and there's no sense in taking hours to create manually what software can do more comprehensively, faster and safer for you in seconds

Getting Exception in Summing the values of a column of a table in SQL using ASP.Net

I am trying to add the values of a column of a table. My table looks like this:
enter image description here
I want to add the values of months column for a specific id. My code looks like this:
public int MonthSum(int id)
{
SqlConnection connection = new SqlConnection(connectionString);
string query = "select sum(months) from PayTable where ID=#ID group by ID";
SqlCommand command = new SqlCommand(query,connection);
command.Parameters.Clear();
command.Parameters.Add("ID", SqlDbType.Int);
command.Parameters["ID"].Value = id;
connection.Open();
int total = (int)command.ExecuteScalar();
connection.Close();
return total;
}
Why I am getting exception here??
Since you don't provide more information about the exception, it's only guessing, but you may have a problem adding the parameter as "ID" instead of "#ID". I think that SqlCommand expects the name with the #.
Here some Microsoft documentation with an example very similar to what you are doing.

Oracle Parameters in .net sql queries - ORA-00933: SQL command not properly ended

I am trying to do create a where clause to pass as a parameter to an Oracle command and it's proving to be more difficult than I thought. What I want to do is create a big where query based off user input from our application. That where query is to be the single parameter for the statement and will have multiple AND, OR conditions in it. This code here works however isn't exactly what I require:
string conStr = "User Id=testschema;Password=pass12341;Data Source=orapdex01";
Console.WriteLine("About to connect to Database with Connection String: " + conStr);
OracleConnection con = new OracleConnection(conStr);
con.Open();
Console.WriteLine("Connected to the Database..." + Environment.NewLine + "Press enter to continue");
Console.ReadLine();
// Assume the connection is correct because it works already without the parameterization
String block = "SELECT * FROM TEMP_VIEW WHERE NAME = :1";
// set command to create anonymous PL/SQL block
OracleCommand cmd = new OracleCommand();
cmd.CommandText = block;
cmd.Connection = con;
// since execurting anonymous pl/sql blcok, setting the command type
// as text instead of stored procedure
cmd.CommandType = CommandType.Text;
// Setting Oracle Parameter
// Bind the parameter as OracleDBType.Varchar2
OracleParameter param = cmd.Parameters.Add("whereTxt", OracleDbType.Varchar2);
param.Direction = ParameterDirection.Input;
param.Value = "MY VALUE";
// Get returned values from select statement
OracleDataReader dr = cmd.ExecuteReader();
// Read the identifier for each result and display it
while (dr.Read())
{
Console.WriteLine(dr.GetValue(0));
}
Console.WriteLine("Selected successfully !");
Console.WriteLine("");
Console.WriteLine("***********************************************************");
Console.ReadKey();
If I change the lines below to be the type of result I want then I get an error "ORA-00933: SQL command not properly ended":
String block = "SELECT * FROM TEMP_VIEW :1";
...
...
param.Value = "WHERE NAME = 'MY VALUE' AND ID = 5929";
My question is how do I accomplish adding my big where query dynamically without causing this error?
Sadly there is no easy way to achieve this.
One thing you will need to understand with parameterised SQL in general is that bind parameters can only be used for values, such as strings, numbers or dates. You cannot put bits of SQL in them, such as column names or WHERE clauses.
Once the database has the SQL text, it will attempt to parse it and figure out whether it is valid, and it will do this without taking any look at the bind parameter values. It won't be able to execute the SQL without all of the values.
The SQL string SELECT * FROM TEMP_VIEW :1 can never be valid, as Oracle isn't expecting a value to immediately follow FROM TEMP_VIEW.
You will need to build up your SQL as a string and also build up the list of bind parameters at the same time. If you find that you need to add a condition on the column NAME, you add WHERE NAME = :1 to the SQL string and a parameter with name :1 and the value you wish to add. If you have a second condition to add, you append AND ID = :2 to the SQL string and a parameter with name :2.
Hopefully the following code should explain a little better:
// Initialise SQL string and parameter list.
String sql = "SELECT * FROM DUAL";
var oracleParams = new List<OracleParameter>();
// Build up SQL string and list of parameters.
// (There's only one in this somewhat simplistic example. If you have
// more than one parameter, it might be easier to start the query with
// "SELECT ... FROM some_table WHERE 1=1" and then append
// " AND some_column = :1" or similar. Don't forget to add spaces!)
sql += " WHERE DUMMY = :1";
oracleParams.Add(new OracleParameter(":1", OracleDbType.Varchar2, "X", ParameterDirection.Input));
using (var connection = new OracleConnection() { ConnectionString = "..."})
{
connection.Open();
// Create the command, setting the SQL text and the parameters.
var command = new OracleCommand(sql, connection);
command.Parameters.AddRange(oracleParams.ToArray());
using (OracleDataReader reader = command.ExecuteReader())
{
while (reader.Read())
{
// Do stuff with the data read...
}
}
}

Are stored procedures executed from text string?

There are several SQL servers having stored procedures, for example Microsoft SQL Server or PostgreSQL. There are also several client side objects implementing stored procedures calls (TADOStoredProc in Delphi, SqlCommand in .NET Framework etc.).
The question I always wanted to ask is:
are stored procedures executed always in special efficient way with binary representation of their parameters or are the super-advanced objects which represent stored procedures' parameters always converted to a plain text string and stored procedure is always executed by sending this plain text string to SQL server? (Let's take one technology for example - let it be SQL Server and ADO.NET).
I noticed that for ADO.NET procedure's parameter names do not have any meaning - only their creation order is important which makes me think about an idea with plain text string.
Update for #Alex K.
I've tested following code in .NET:
CREATE PROCEDURE paramtest
#par1 nvarchar(50),
#par2 nvarchar(50),
#par3 nvarchar(50)
AS
SELECT Res = '#par1 = ' + #par1 + '; #par2 = ' + #par2 + '; #par3 = ' + #par3
RETURN 555
using System;
using System.Data.SqlClient;
using System.Data;
namespace SqlParamTest
{
class Program
{
private static void addParam(SqlCommand cmd, string parameterName, ParameterDirection direction, SqlDbType dbType, int size, object value)
{
SqlParameter par = new SqlParameter(parameterName, dbType, size);
par.Direction = direction;
par.Value = value;
cmd.Parameters.Add(par);
}
static void Main(string[] args)
{
using (SqlConnection conn = new SqlConnection(#"Data Source=localhost\sqlexpress;Initial Catalog=test;Integrated Security=True"))
{
SqlCommand cmd = conn.CreateCommand();
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = "paramtest";
addParam(cmd, "#par3", ParameterDirection.Input, SqlDbType.NVarChar, 50, "third");
addParam(cmd, "#par2", ParameterDirection.Input, SqlDbType.NVarChar, 50, "second");
addParam(cmd, "#par1", ParameterDirection.Input, SqlDbType.NVarChar, 50, "first");
addParam(cmd, "#Return", ParameterDirection.ReturnValue, SqlDbType.Int, 0, null);
conn.Open();
SqlDataReader rdr = cmd.ExecuteReader(CommandBehavior.CloseConnection);
if (rdr.Read()) Console.WriteLine((string)rdr["Res"]);
rdr.Close();
Console.WriteLine("Return value: {0}", cmd.Parameters["#Return"].Value);
}
Console.ReadKey();
}
}
}
and yes, it maintains parameters in a right way, but I think, it is .NET who adds additional checks to parameters, because following code in Delphi:
procedure TMyClass.Test(Conn: TADOConnection);
var SP:TADOStoredProc;
begin
SP := TADOStoredProc.Create(nil);
try
SP.Connection := Conn;
SP.ProcedureName := 'paramtest';
SP.Parameters.CreateParameter('#whatthehell', ftString, pdInput, 50, 'one');
SP.Parameters.CreateParameter('#AnotherCrap', ftString, pdInput, 50, 'two');
SP.Parameters.CreateParameter('?', ftString, pdInput, 50, 'three');
SP.ExecProc;
finally
SP.Free;
end;
end;
returns:
#par1 = one; #par2 = two; #par3 = three
and doesn't complain about missing parameters.
pdReturnValue works only if this parameter is created before any other parameters.
Not sure what answer your looking for, stored procedure command text & parameters are passed to the driver/provider or natively via ADO.NET which formats it as a TDS (tabular data stream) RPC (remote procedure call) Message which is then passed to the server over which ever network protocol is being used; pipes, tcp/ip et al. The data is sent in a binary stream.
The TDS spec is available from Microsoft if your interested.
SQLCommand Stored Procedure calls do need a parameter name, its OleDB/ODBC that only care about the order and use ? as the parameter placeholder rather than #NAME.
Regarding order
In your example the order is not relevant because you are providing the server with the correct names for the params, so this is what is sent to the server:
exec paramtest #par3=N'third',#par2=N'second',#par1=N'first'
This is enough information for the server to figure out the correct params/order.
If you changed to
addParam(cmd, "#xxpar3",
addParam(cmd, "#xxpar2",
addParam(cmd, "#xxpar1",
The server would detect that it does not have a param named xxxpar* and fail with a "missing #par1" error.
If you modified addParam so it did not set paramater names .net will create defaults:
exec paramtest #Parameter1=N'third',#Parameter2=N'second',#Parameter3=N'first'
Which would cause the above error.
If you modified addParam so it did not set paramater names and then overwrite the automatic ones;
cmd.Parameters.Add(par);
par.ParameterName = "";
This is what gets executed:
exec paramtest N'third',N'second',N'first'
resulting in
#par1 = third; #par2 = second; #par3 = first
I have no idea what Delphi does ... The full version of SQL Server ships with a tool called SQL Profiler which displays the textual data that gets sent to the server instance so you can see exactly whats going on.
What profiler to use with sql express?

Problems executing SQL-script using Firebird.NET 2.5 (Error Code = -104)

Sorry for my English first of all. I have a problem and need help.
I have a simple tool made by myself on c#. This tool makes connect to local or remote firebird server (v.2.5). And my tool can create specified .fdb file (database) somewhere on the server.
Also I have a file with SQL statements (create table, triggers and so on). I want to execute this file after database was created. Executing this file will fill structure of user database - not data, only structure.
But then I try to execute my SQL script - firebird server returns a
SQL error code = -104 Token unknown line xxx column xxx.
That's the line on this CREATE TABLE SQL statement, for example:
CREATE TABLE tb1
(
col1 INTEGER NOT NULL,
col2 VARCHAR(36)
);
/* This next create statement causes an error */
CREATE TABLE tb2
(
col1 INTEGER NOT NULL,
col2 VARCHAR(36)
);
If I will leave only one create statement in my file - all will be good... I don't know how I explained (it's clear or not)) - another words - why can't I execute full query with many create statements in one transaction? There is my main method which executes query:
public static string Do(string conString, string query)
{
using (FbConnection conn = new FbConnection())
{
try
{
conn.ConnectionString = conString;
conn.Open();
FbTransaction trans = conn.BeginTransaction();
FbCommand cmd = new FbCommand(query, conn, trans);
cmd.ExecuteNonQuery();
trans.Commit();
}
catch (Exception ex)
{
System.Windows.MessageBox.Show(ex.ToString());
return "Transaction Fail";
}
}
return "Transaction Commited";
}
There is a query is my SQL file.
As Victor already stated in his final comment, you can use the FBScript class for batch execution.
I was just confronted with the same task. This question pointed me in the right direction but i had to do some further digging.
I this example, the source of the statements is a external script file:
private void ExecuteScript(FbConnection myConnection, string scriptPath) {
if (!File.Exists(scriptPath))
throw new FileNotFoundException("Script not found", scriptPath);
FileInfo file = new FileInfo(scriptPath);
string script = file.OpenText().ReadToEnd();
// use FbScript to parse all statements
FbScript fbs = new FbScript(script);
fbs.Parse();
// execute all statements
FbBatchExecution fbe = new FbBatchExecution(myConnection, fbs);
fbe.Execute(true);
}
This will work fine, but you may wonder why this whole thing isn't surrounded by a transaction. Actually there is no support to "bind" FbBatchExecution to a transaction directly.
The first thing i tried was this (will not work)
private void ExecuteScript(FbConnection myConnection, string scriptPath) {
using (FbTransaction myTransaction = myConnection.BeginTransaction()) {
if (!File.Exists(scriptPath))
throw new FileNotFoundException("Script not found", scriptPath);
FileInfo file = new FileInfo(scriptPath);
string script = file.OpenText().ReadToEnd();
// use FbScript to parse all statements
FbScript fbs = new FbScript(script);
fbs.Parse();
// execute all statements
FbBatchExecution fbe = new FbBatchExecution(myConnection, fbs);
fbe.Execute(true);
myTransaction.Commit();
}
}
This will result in an exception stating: "Execute requires the Command object to have a Transaction object when the Connection object assigned to the command is in a pending local transaction. The Transaction property of the Command has not been initialized."
This means nothing more than that the commands that are executed by FbBatchExecution are not assigned to our local transaction that is surrounding the code block. What helps here is that that FbBatchExecution provides
the event CommandExecuting where we can intercept every command and assign our local transaction like this:
private void ExecuteScript(FbConnection myConnection, string scriptPath) {
using (FbTransaction myTransaction = myConnection.BeginTransaction()) {
if (!File.Exists(scriptPath))
throw new FileNotFoundException("Script not found", scriptPath);
FileInfo file = new FileInfo(scriptPath);
string script = file.OpenText().ReadToEnd();
// use FbScript to parse all statements
FbScript fbs = new FbScript(script);
fbs.Parse();
// execute all statements
FbBatchExecution fbe = new FbBatchExecution(myConnection, fbs);
fbe.CommandExecuting += delegate(object sender, CommandExecutingEventArgs args) {
args.SqlCommand.Transaction = myTransaction;
};
fbe.Execute(true);
// myTransaction.Commit();
}
}
Note that i have uncommented the myTransaction.Commit() line. I was a little bit surprised by this behavior, but if you keep that line the transaction will throw an exception stating that it has already been committed. The bool parameter fbe.Execute(true) is named "autoCommit", but changing this to false seems to have no effect.
I would like some feedback if you see any potential issues with assigning the local transaction this way, or if it has any benefits at all or could as well be omitted.
Probably error in launching two create statements in one batch. Would it work if you break it to separate queries? Does it work in your SQL tool?