sqlCommand and programatically retrieving and setting parameters of stored procedures - c#

I'm new to .NET but need to swim in the deep end for a bit. I've been tasked with expanding an existing unit test that currently just attempts to receive the columns for all stored procedures (as a means of testing the integrity of the db connections and the schema therein). The goal is to retrieve the parameters for all stored procedures and attempt to run them with null values, testing to verify that no record sets are returned. I've been beating my head against the wall trying to get up to speed with ADO.NET but cannot for the life of me figure out how to do this. This is as far as I've gotten (taken somewhat out of context). The test complains that no parameter is being passed in, but I thought I was setting the existing parameter set to null and simply passing it back.
// bind our sql schema gridview
foreach (SqlSchemaItem item in items)
{
// pull a connection
using (var sqlConnection = new SqlConnection(connectionString))
{
// open connection
sqlConnection.Open();
// get schema
try
{
/*
* Part 1 - test that the schema is ok...
*/
var sqlCommand = new SqlCommand(item.ObjectName, sqlConnection);
sqlCommand.CommandType = CommandType.StoredProcedure;
SqlCommandBuilder.DeriveParameters(sqlCommand);
sqlCommand.ExecuteReader(CommandBehavior.SchemaOnly);
// success to console
Console.WriteLine(item.ObjectName + " is ok.");
/*
* Part 2 - test that the stored procedure does not return any data.
*/
// set all the parameters to NULL
foreach (SqlParameter parameter in sqlCommand.Parameters)
{
if (parameter.Direction == ParameterDirection.Input || parameter.Direction == ParameterDirection.InputOutput)
{
parameter.Value = null;
Console.WriteLine("Parameter {0} set to null", parameter.ParameterName, parameter.Value);
}
}
var temp = sqlCommand.ExecuteReader(CommandBehavior.SingleResult);
if (temp.HasRows) {
Console.WriteLine(string.Format("A record was returned in {0} with value {0}", temp.GetName(0), temp.GetString(0)));
}
else {
Console.WriteLine("No result was returned");
}
Console.WriteLine(" ");
}
catch ...
finally ...
etc.

Use DBNull.Value instead of null.

Related

No value given for one or more required parameters. C#

I have a C# program and I made a code for subtracting the amount of sold products from the amount of these products in the stock (access datatable) so I used this code :
foreach (DataGridViewRow r in dataGridView1.Rows)
{
OleDbCommand command = new OleDbCommand("Update BookInserting set Amount = Amount - '" + Convert.ToInt32(r.Cells[1].Value) + "' where BookName = " + r.Cells[0].Value.ToString() + "", connection);
connection.Open();
command.ExecuteNonQuery();
connection.Close();
}
but when I run it gives me this error :
(No value given for one or more required parameters) .
I tried solving it several times but failed, I hope you can help me solve it.
Thanks in advance.
Your problem is probably caused by Access unable to recognize some part of your query as an object of the underlying table (or the table itself).
This problem and a more serious one called Sql Injection could be avoided using parameters. (And a side benefit your code becomes a lot clearer without all those strings concatenations)
So let's try to change your code in this way:
// String sql with parameters placeholders
string cmdText = #"Update BookInserting
set Amount = Amount - #amt
where BookName = #book";
connection.Open();
// Just build the command just one time outside the loop and
// add the two parameters required (without a value and in the exact order
// expected by the placeholders
OleDbCommand command = new OleDbCommand(cmdText, connection);
command.Parameters.Add("#amt", OleDbType.Integer);
command.Parameters.Add("#book", OleDbType.VarWChar);
// Inside the loop just change the parameters values and execute
foreach (DataGridViewRow r in dataGridView1.Rows)
{
// If the cell with the parameter for the WHERE
// clause is invalid skip the update
if(!r.IsNewRow && r.Cells[0].Value != null
&& r.Cells[0].Value.ToString() != "")
{
cmd.Parameters["#amt"].Value = Convert.ToInt32(r.Cells[1].Value);
cmd.Parameters["#book"].Value = r.Cells[0].Value.ToString();
command.ExecuteNonQuery();
}
}
connection.Close();
Final note. A connection object should be created each time you require it. From your code it is not clear if this is the case. Use the following pattern. (Using Statement)
using(OleDbConnection connection = new OleDbConnection(....))
{
... code that uses the connection ....
} // <- here the connection is closed and disposed

Insert new row to Database table if two fields does not match, otherwise sum value in certain column

I have data in Database table:
Here is the method for adding data:
public static void AddRecordToDatatable(string WindowTitle, int TimeSpent,
DateTime DateToday, string Project, string Username)
{
string sql = #"INSERT INTO dbo.Log (WindowTitle,TimeSpent,DateToday,Project,Username)" +
" VALUES (#WindowTitle,#TimeSpent,#DateToday,#Project,#Username)";
// Create the connection (and be sure to dispose it at the end)
using (SqlConnection cnn = new SqlConnection(DBconnectionString))
{
try
{
// Open the connection to the database.
// This is the first critical step in the process.
// If we cannot reach the db then we have connectivity problems
cnn.Open();
// Prepare the command to be executed on the db
using (SqlCommand cmd = new SqlCommand(sql, cnn))
{
// Create and set the parameters values
cmd.Parameters.Add("#WindowTitle", SqlDbType.NVarChar).Value = WindowTitle;
cmd.Parameters.Add("#TimeSpent", SqlDbType.Int).Value = TimeSpent;
cmd.Parameters.Add("#DateToday", SqlDbType.DateTime).Value = DateTime.Now.Date;
cmd.Parameters.Add("#Project", SqlDbType.NVarChar).Value = Project;
cmd.Parameters.Add("#Username", SqlDbType.NVarChar).Value = Username;
// Let's ask the db to execute the query
int rowsAdded = cmd.ExecuteNonQuery();
if (rowsAdded > 0)
{
//MessageBox.Show("Row inserted");
}
else
{
// This should never really happen, but let's leave it here
//MessageBox.Show("No row inserted");
}
}
cnn.Close();
}
catch (Exception ex)
{
// We should log the error somewhere,
// for this example let's just show a message
MessageBox.Show("ERROR:" + ex.Message);
}
}
}
How it is possible to check for existing record before inputting data to Database table and sum on certain value if it exists?
So basically check if WindowTitle = WindowTitle and DateToday = DateToday, if these two match, then take TimeSpent and sum it to existing TimeSpent in Database Table without inputting a new row.
I have tried to test ON DUPLICATE KEY UPDATE WindowTitle = #WindowTitle, DateToday = #DateToday after INSERT but Visual Studio is giving an error in Debugger for such a command pointing to ON (Incorrect syntax near ON). Also I am not sure if ON DUPLICATE is the best approach for this kind of case.
You need to expand your SQL to check for the existence of the record you think could exist.
IF EXISTS (SELECT 1 FROM dbo.Log WHERE WindowTitle = #WindowTitle AND DateToday = #DateToday)
BEGIN
--UPDATE HERE
END
ELSE
BEGIN
-- INSERT HERE
END
Alternatively you can create a query method and call that first, before calling AddRecordToDatatable
Personally I would do all of these CRUD operations using an ORM such as EF Core or preferably, NHibernate. But this all depends on requirements, limitations etc.

How to determine if ExecuteReader returned a result set or not

I'm working on a concept to retrieve the Schema from an executed stored procedure. The goal is to loop through multiple stored procedures to generate a data dictionary.
I have a CLR Assembly that's retrieving the data. For performance reasons, the stored procs have parameters automatically generated to produce no rows so the expectation is to get an empty result set that I can retrieve the schema from. The issue I'm running in to is that it's possible for a stored procedure to be passed into the function that does not have a result set. I need a way to handle and identify these stored procedures.
Here is the function:
public static void Return_ColumnData(SqlString query)
{
DataTable schema = null;
SqlConnection connection = new SqlConnection("context connection=true");
string sQuery = query.ToString();
using (SqlCommand command = new SqlCommand(sQuery, connection))
{
try
{
connection.Open();
SqlDataReader reader = command.ExecuteReader();
schema = reader.GetSchemaTable();
reader.Close();
string insertQuery = "INSERT INTO #tmpColData (ColumnName, Length, Precision, Scale, DataType) SELECT #ColumnName, #Length, #Precision, #Scale, #DataType";
foreach (DataRow myField in schema.Rows)
{
using (SqlCommand insertCommand = new SqlCommand(insertQuery, connection))
{
insertCommand.Parameters.Add("#ColumnName", SqlDbType.NVarChar, 100).Value = myField[0].ToString();
insertCommand.Parameters.Add("#Length", SqlDbType.Int).Value = Convert.ToInt32(myField[2]);
insertCommand.Parameters.Add("#Precision", SqlDbType.Int).Value = Convert.ToInt32(myField[3]);
insertCommand.Parameters.Add("#Scale", SqlDbType.Int).Value = Convert.ToInt32(myField[4]);
insertCommand.Parameters.Add("#DataType", SqlDbType.NVarChar, 100).Value = myField[24].ToString();
insertCommand.ExecuteNonQuery();
}
}
connection.Close();
}
catch (SqlException exception)
{
SqlContext.Pipe.Send(exception.Errors[0].Message);
}
}
}
If I don't handle it it will throw this error from the stored procedure that calls the assembly (Return_ColumnData):
Msg 50000, Level 16, State 1, Procedure dbo.Procedure2DataDict, Line 236 [Batch Start Line 0]
6522 A .NET Framework error occurred during execution of user-defined routine or aggregate "Return_ColumnData":
System.NullReferenceException: Object reference not set to an instance of an object.
System.NullReferenceException:
at Procedure2DataDict.Return_ColumnData(SqlString query)
I tried adding a reader.HasRows to determine whether or not there was a result set but it will return false if there is no result set and if there is 0 rows which does not work for me.
if (reader.HasRows)
{
schema = reader.GetSchemaTable();
} else
{
SqlContext.Pipe.Send("Failed to get schema. No rows output.");
reader.Close();
connection.Close();
return;
}
I have to assume something to handle no result set with ExecuteReader() exists. I know there is a difference between ExecuteReader() and ExecuteNonQuery() but since the function will be run for an iteration of multiple procedures, I don't know beforehand if the proc will return a result set or not.
Any help is appreciated.
You should use FieldCount.
From Remarks:
Executing a query that, by its nature, does not return rows (such as a DELETE query), sets FieldCount to 0.
So I believe the answer was staring me in the face in the form of the error I was receiving.
The error message is a NullReferenceException so I've decided instead of checking HasRows to just ensure the result of GetSchemaData() != null.
if (reader.GetSchemaTable() != null)
{
schema = reader.GetSchemaTable();
} else
{
SqlContext.Pipe.Send("Failed to get schema. No rows output.");
reader.Close();
connection.Close();
return;
}
I've tested for my cases and it is working as expected.
I understand there are probably multiple ways to handle this and people can still contribute with an explanation of their method.
Thanks!

Check if update statement was successful

I have the following method that sets an object to a specific status (it sets a column value of a specific row to '4' :
C#
void setObjectToFour(int objectID)
{
using (var conn = new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["Database"].ConnectionString))
using (var command = new SqlCommand("setObjectToFour", conn)
{
CommandType = CommandType.StoredProcedure
})
{
command.Parameters.Add(new SqlParameter("#objectID", SqlDbType.Int)).Value = objectID;
conn.Open();
command.ExecuteNonQuery();
}
}
SQL:
...
AS
BEGIN
Update [DB_OBJECT].[dbo].[object_table]
SET status = 4
WHERE id = #objectID
END
The problem is that the DB_OBJECT DB is not managed by us and is the DB of a piece of software.
The followed problem is that the query from above not always works (and we haven't figured out why) and I were thinking about how we could 'force' or 'check' if the row was updated.
Is it smart to do it as follow?:
1 - Create new C# Method Check and stored procedure getStatus that retrieves the status of the object
2 - I will put both methods from above in a do while until the status is 4.
Is this a smart approach?
ExecuteNonQuery() method returns the number of rows affected.
int recordAffectd = command.ExecuteNonQuery();
if (recordAffectd > 0)
{
// do something here
}
ExecuteNonQuery() does not return data at all: only the number of rows affected by an insert, update, or delete.
try this
if (command.ExecuteNonQuery() != 0)
{
// more code
}

MySQL .Net Connector Issue Executing Routine

I'm having an issue getting my code to execute a MySQL routine.
Keeps popping error:
Procedure or function 'ShortenedURLS' cannot be found in database 'Get'.
Routine
DELIMITER $$
USE `o7thurlshortner`$$
DROP PROCEDURE IF EXISTS `Get.ShortenedURLS`$$
CREATE DEFINER=`root`#`localhost` PROCEDURE `Get.ShortenedURLS`(IN `ID` BIGINT)
NO SQL
SELECT `ShortID`, `ShortCode`, `URL`, `ClickThroughs`
FROM `Shortener`
WHERE `AccountID` = ID$$
DELIMITER ;
Code - Accessing and running the routine
internal DbDataReader GetResults()
{
try
{
// check for parameters
if (AreParams())
{
PrepareParams(_Cmd);
}
// set our connection
_Cmd.Connection = _Conn;
// set the type of query to run
_Cmd.CommandType = _QT;
// set the actual query to run
_Cmd.CommandText = _Qry;
// open the connection
_Cmd.Connection.Open();
// prepare the command with any parameters that may have gotten added
_Cmd.Prepare();
// Execute the SqlDataReader, and set the connection to close once returned
_Rdr = _Cmd.ExecuteReader(CommandBehavior.CloseConnection);
// clear out any parameters
_Cmd.Parameters.Clear();
// return our reader object
return (!_Rdr.HasRows) ? null : _Rdr;
}
catch (DbException SqlEx)
{
_Msg += "Acccess.GetResults SqlException: " + SqlEx.Message;
ErrorReporting.WriteEm.WriteItem(SqlEx, "o7th.Class.Library.Data.MySql.Access.GetResults", _Msg);
return null;
}
catch (Exception ex)
{
_Msg += "Acccess.GetResults Exception: " + ex.Message;
ErrorReporting.WriteEm.WriteItem(ex, "o7th.Class.Library.Data.MySql.Access.GetResults", _Msg);
return null;
}
}
Code - to fire it off
IList<Typing> _T = Wrapper.GetResults<Typing>("Get.ShortenedURLS",
System.Data.CommandType.StoredProcedure,
new string[] { "?ID" },
new object[] { 1 },
new MySqlDbType[] { MySqlDbType.Int32 },
false);
Update
Verified this does work properly once I fireoff a routine without a . in it.
How can I get this to work if my routines do have .'s, I cannot simply re-write existing procedures in a production database tied to a high traffic website...
In order to call you stored procedure you do have to wrap the name of it in backticks since it contains the special character .
However, there is a bug in the mysql connector code that is causing it to escape it again. When you specify
cmd.CommandType = CommandType.StoredProcedure;
The code branches during execute reader as you can see below...
if (statement == null || !statement.IsPrepared)
{
if (CommandType == CommandType.StoredProcedure)
statement = new StoredProcedure(this, sql);
else
statement = new PreparableStatement(this, sql);
}
// stored procs are the only statement type that need do anything during resolve
statement.Resolve(false); // the problem occurs here
part of what Resolve does is fix the procedure name..
//StoredProcedure.cs line 104-112
private string FixProcedureName(string name)
{
string[] parts = name.Split('.');
for (int i = 0; i < parts.Length; i++)
if (!parts[i].StartsWith("`", StringComparison.Ordinal))
parts[i] = String.Format("`{0}`", parts[i]);
if (parts.Length == 1) return parts[0];
return String.Format("{0}.{1}", parts[0], parts[1]);
}
As you can see here, anytime the stored procedure name has a . in it, the name of the stored procedure is broken up into parts and escaped individually, and this is causing your code to not be able to call your stored procedure.
So your only options to fix this are..
1) Open a bug with oracle to fix the provider (assuming there is not one already open)
2) Change the name of your stored procedure to not use a .
3) Download the code for the provider, fix it, recompile
4) Find another connector

Categories

Resources