How to combine multiple SQL Commands into one? - c#

Is it possible to optimize the following queries by merging them inside a single SQLCommand?
SqlCommand cmd = new SqlCommand
{
CommandType = CommandType.Text,
CommandText = "DELETE FROM cbu_naslovi WHERE [ID]='" + CurrentID + "'",
Connection = con
};
SqlCommand cmd1 = new SqlCommand
{
CommandType = CommandType.Text,
CommandText = "DELETE FROM cbu_deli WHERE [IDX]='" + CurrentID + "'",
Connection = con
};
cmd.ExecuteNonQuery();
cmd1.ExecuteNonQuery();
EDIT: Working solution, as suggested by the community answers bellow
SqlCommand cmd = new SqlCommand
{
CommandType = CommandType.Text,
CommandText = "DELETE FROM cbu_naslovi WHERE [ID] = #CurrentID; DELETE FROM cbu_deli WHERE [IDX] = #CurrentID",
Connection = con
};
cmd.Parameters.AddWithValue("#CurrentID", CurrentID);
cmd.ExecuteNonQuery();

Yes, you can just separate them with a semicolon. For example I have code that executes the following in a single call
SET NOCOUNT ON;
DECLARE #decimalDate DECIMAL(12,0);
DECLARE #charDate CHAR(12);
DECLARE #utcDate DATETIMEOFFSET;
DECLARE date_cursor CURSOR FOR SELECT {1} FROM {0} WHERE ISNULL({1},0)!=0;
OPEN date_cursor;
FETCH NEXT FROM date_cursor INTO #decimalDate;
WHILE ##FETCH_STATUS=0
BEGIN
BEGIN TRY SET #charDate=CONVERT(CHAR(12),#decimalDate);
SET #utcDate=SwitchOffset(
CONVERT(DATETIME,'20'
+SUBSTRING(#charDate,1,2)+'-'+SUBSTRING(#charDate,3,2)+'-'
+SUBSTRING(#charDate,5,2)+' '+SUBSTRING(#charDate,7,2)+':'
+SUBSTRING(#charDate,9,2)+':'+SUBSTRING(#charDate,11,2)
,121) AT TIME ZONE '{3}',0);
END
TRY BEGIN CATCH
SET #utcDate=SysUtcDateTime();
END CATCH;
BEGIN
TRY UPDATE {0} SET {2}=#utcDate WHERE CURRENT OF date_cursor;
END TRY
BEGIN CATCH END CATCH;
FETCH NEXT FROM date_cursor INTO #decimalDate;
END;
CLOSE date_cursor;
DEALLOCATE date_cursor;
There are exceptions. For instance the "create procedure" statement must be the first statement of a block. But most DML can be batched like this.

You can write it like this:
SqlCommand cmd = new SqlCommand
{
CommandType = CommandType.Text,
CommandText = $"DELETE FROM cbu_naslovi WHERE [ID]='{CurrentID}';DELETE FROM cbu_deli WHERE [IDX]='{CurrentID}'",
Connection = con
};

if you need to run Non Query operation, you could try to execute bunch of commands by using Server object.
benefit: you could use GO in SQL statement. Command does not allow to use GO.
server.ConnectionContext.ExecuteNonQuery("your SQL statement -- could be 100 statements with hundrends of GO commands", ExecutionTypes.Default)
server variable has type Server

Related

Scalar valued function from SQL Server not returning value when called from C#

I was given this function
CREATE FUNCTION [dbo].[GET_WEBGIS_ISSUE_NUM]
()
RETURNS VARCHAR(50)
AS
BEGIN
DECLARE #v_new_num int, #v_new_issue_num varchar(50);
SET #v_new_num = (SELECT COUNT(*) + 1
FROM [dbo].[WEBGIS_ISSUE]
WHERE [ISSUE_NUM] LIKE CONCAT(FORMAT(GETDATE(), 'yyMM'), '%'));
IF #v_new_num < 10
SET #v_new_issue_num = CONCAT(FORMAT(GETDATE(), 'yyMM'), '00', #v_new_num);
ELSE IF #v_new_num < 100
SET #v_new_issue_num = CONCAT(FORMAT(GETDATE(), 'yyMM'), '00', #v_new_num);
ELSE
SET #v_new_issue_num = CONCAT(FORMAT(GETDATE(), 'yyMM'), #v_new_num);
RETURN #v_new_issue_num
END;
I tried calling it from the following C# code
SqlConnection cnn = new SqlConnection(connectionString);
SqlCommand cmd = new SqlCommand();
cmd.Connection = cnn;
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = "[NEPS].[dbo].[GET_WEBGIS_ISSUE_NUM]";
//add any parameters the stored procedure might require
if (cmd.Connection.State == ConnectionState.Closed) //cmd.Connection.Open();
{
cnn.Open();
var o = cmd.ExecuteScalar();
//blabla
cnn.Close();
}
but when I debug the code, I kept on receiving null.
Notes: the connection is ok, it is connected, when I tried changing the function's name it yields an error and when I checked through the SQL Server it also returns an appropriate return value.
You're treating a scalar function as a stored procedure, which is the wrong type for this type of execution. You need to 'CommandType.Text' with scalar functions.
Other notes on the C# part :
use using blocks with SqlConnection and SqlCommand (let the using clause handles the dispose and close connection parts for you).
the query should be declared as const string
always end the query with a semicolon (even if it's running in the SQL Server without it).
avoid using short names, choose a readable naming for your variables.
Here is the C# code :
const string query = "SELECT [NEPS].[dbo].[GET_WEBGIS_ISSUE_NUM]();";
using(SqlConnection connection = new SqlConnection(connectionString))
using(SqlCommand command = new SqlCommand(query, connection))
{
connection.Open();
var result = command.ExecuteScalar();
// do stuff
}
For the function GET_WEBGIS_ISSUE_NUM perhaps you can avoid the extra IFs with this line :
CREATE FUNCTION [dbo].[GET_WEBGIS_ISSUE_NUM]
()
RETURNS VARCHAR(50)
AS
BEGIN
DECLARE #v_new_num int, #v_new_issue_num varchar(50);
SET #v_new_num = (SELECT COUNT(*) + 1
FROM [dbo].[WEBGIS_ISSUE]
WHERE [ISSUE_NUM] LIKE CONCAT(FORMAT(GETDATE(), 'yyMM'), '%'));
SET #v_new_issue_num, FORMAT(GETDATE(), 'yyMM') + RIGHT('000' + CAST(#v_new_num AS VARCHAR), 4);
RETURN #v_new_issue_num
END;
It is more common to use a SELECT statement to return a scalar function result. When you use EXECUTE (due to CommandType.StoredProcedure), you need to also specify a return parameter and retrieve the result from the parameter after execution:
var result = cmd.Parameters.Add("#result", SqlDbType.VarChar, 50);
result.Direction = ParameterDirection.ReturnValue;
cmd.ExecuteNonQuery(); //ExecuteScalar will work too but the result is null and you still need to use the parameter
var o = result.Value;
As noted in the comments to your question, consider the concurrency implications of this approach. Duplicate values will be returned until the row count changes.

c# ADO set context_info for SQL INSERT TRIGGER

I have to use an existing INSERT trigger which uses CONTEXT_INFO...
How can I pass "context_info" to a c# SqlCommand so that this trigger keeps working correctly?
Usage in SQL-Trigger:
select #ctxt=context_info from master.dbo.sysprocesses where spid = ##spid
set #session=substring(#ctxt,1,4)
set #contextid=substring(#ctxt,5,4)
set #ntduser=substring(#ctxt,9,120)
Tried:
//string strContext = "declare #context_info varbinary(30) set #context_info = cast('abcdefghijklmnopqrstuv1234567890' as varbinary(30)) set context_info #context_info";
///////////////
SqlCommandBuilder cb = new SqlCommandBuilder(da);
da.UpdateCommand = cb.GetUpdateCommand();
//da.UpdateCommand.CommandText= da.UpdateCommand.CommandText.Replace(" WHERE ", strContext+" WHERE ");
//da.UpdateCommand.CommandText = "SET CONTEXT_INFO 0x1 " + da.UpdateCommand.CommandText;
da.Update(dataTable);
da.Dispose();
see commented out code...
In my SQL Trigger "context_info" was always empty
Already read that:
https://social.msdn.microsoft.com/Forums/en-US/4a0ecb28-11cb-45ec-adbd-d72ac65b158a/how-to-pass-net-applications-parameter-to-a-trigger?forum=transactsql
but also does not work.
Isn't there a sample out there where context_info is passed to SqlCommand or SqlConnection or SqlTransaction?
I used something like this (simplified) once to set some information (guid) so that it can be retrieved from inside a trigger ...
string sql = $"DECLARE #session uniqueidentifier; SET #session = '{guid}'; SET CONTEXT_INFO #session";
var cmd = new SqlCommand(sql, connection, transaction);
cmd.ExecuteNonQuery();
Be sure not to close the connection between setting the context_info and invokation of the trigger ...

Stored Procedure doesn't return records when called from C#, but works in SSMS

I have a stored procedure that correctly returns records when I call it from a SSMS query.
Here is the stored procedure:
CREATE PROCEDURE [dbo].[q_CheckRecords]
#ItemIDS AS VARCHAR(40)
AS
BEGIN
SET NOCOUNT ON
SELECT *
FROM q_Warehouse80_OOS_ItemsNeedingNotification
WHERE item_id = #ItemIDS
END
Calling this from a SSMS query like this:
exec [q_CheckOOSWarehouse80ItemsNeedingNotification] 'B30-R10000-B001'
It correctly returns a row, however when I use this C# code to call the stored procedure, I never get any rows returned.
SqlCommand cmd = null;
SqlDataReader myReader = null;
System.Data.SqlClient.SqlConnection conn = null;
conn = new System.Data.SqlClient.SqlConnection("Data Source=" + sSessionServer + ";database=" + sSessionDatabase + "; Integrated Security=SSPI");
String SQL = "[q_CheckOOSWarehouse80ItemsNeedingNotification]";
cmd = new SqlCommand();
cmd.CommandText = SQL;
cmd.CommandType = System.Data.CommandType.StoredProcedure;
cmd.Connection = conn;
cmd.Parameters.Add("#ItemIDS", SqlDbType.VarChar).Value = ItemsToBeChecked;
conn.Open();
myReader = cmd.ExecuteReader();
// check to see if any rows were returned.
if (myReader.HasRows)
{
while (myReader.Read())
{
// code to read fields in returned rows here
}
}
conn.Close();
It appears to be a problem with how C# defines the datatype being passed to the stored procedure, but I haven't found any information online on how to solve this problem.
If I were to changed the stored procedure so it's "hard coded"
#ItemIDS AS VARCHAR(40)
AS
BEGIN
SET NOCOUNT ON
select * from q_Warehouse80_OOS_ItemsNeedingNotification where item_id = 'B30-R10000-B001'
END
then the C# call to it correctly indicates that a row was "found".
Any help would be greatly appreciated.
When you don't specify the length of a varChar sql treats it as length 1.
cmd.Parameters.Add("#ItemIDS", SqlDbType.VarChar).Value = ItemsToBeChecked;
Your variable ItemsToBeChecked will be truncated, and I suspect there is nothing matching in your database with just the first character of that value.
Specify the length of the varchar
cmd.Parameters.Add("#ItemIDS", SqlDbType.VarChar, 40).Value = ItemsToBeChecked;
You can verify this is the case by putting a profiler on sql, and executing your c#. You will see the value passed to the #ItemIDS parameter is only 1 character long.
The issue you are facing is because you are not calling your stored procedure in your C# Code.

Programmatically get Windows Services and write to SQL

I am trying to query services on a remote server and insert them into SQL. I know I am missing something that will actually write this back but I cannot figure out what it is. Everything works until I try to write the data to SQL.
static void Main(string[] args)
{
string username = ConfigurationManager.AppSettings["UserName"].ToString();
string password = ConfigurationManager.AppSettings["Password"].ToString();
string domain = ConfigurationManager.AppSettings["Domain"].ToString();
var logonType = SimpleImpersonation.LogonType.NewCredentials;
using (SimpleImpersonation.Impersonation.LogonUser(domain, username, password, logonType))
{
var connection = ConfigurationManager.ConnectionStrings["Connection"].ConnectionString;
using (SqlConnection conn = new SqlConnection(connection))
{
conn.Open();
using (SqlCommand command = new SqlCommand("SELECT ServerName, ServerIP FROM Server", conn))
{
SqlDataReader reader = command.ExecuteReader();
while (reader.Read())
{
string serverIP = reader["ServerIP"].ToString();
string serverName = reader["ServerName"].ToString();
ServiceController[] services = ServiceController.GetServices(serverIP);
foreach (ServiceController service in services)
{
using (SqlCommand cmd = new SqlCommand("sp_InsertServices", conn))
{
cmd.CommandType = System.Data.CommandType.StoredProcedure;
SqlParameter ID = new SqlParameter();
ID.ParameterName = "#ID";
ID.Direction = System.Data.ParameterDirection.Output;
cmd.Parameters.AddWithValue("#ID", ID);
cmd.Parameters.AddWithValue("#Display", service.DisplayName);
cmd.Parameters.AddWithValue("#Service", service.ServiceName);
cmd.Parameters.AddWithValue("#Status", service.Status);
cmd.Parameters.AddWithValue("#Server", serverName);
}
}
}
}
}
}
}
Here is the Stored Proc
PROCEDURE [dbo].[sp_InsertServices]
#ID int,
#Display nvarchar(50),
#Service nvarchar(50),
#Status nvarchar(50),
#Server nvarchar(50)
AS
BEGIN
SET NOCOUNT ON
INSERT INTO Services
(ID,
DisplayName,
ServiceName,
Status,
Server)
VALUES
(#ID,
#Display,
#Service,
#Status,
#Server)
SET #ID = SCOPE_IDENTITY()
END
EDIT: Sorry some more info. I have added an execute at the end after the param adds and I get this error.
An unhandled exception of type 'System.InvalidOperationException' occurred in System.Data.dll
Additional information: There is already an open DataReader associated with this Command which must be closed first.
Looks like I need to close the first reader but I still use it in the second command. Don't now where I can safely close it.
You never execute your SqlCommand. After you add your parameters, try adding cmd.ExecuteNonQuery();
The 2nd problem is that you are trying to reuse the same SqlConnection. You'll need to create a new SqlConnection since you are using your existing connection to loop through the list of servers.
You've built up the Command object (cmd), now you just need to save it, like cmd.ExecuteNonQuery();
First this you need to change the stored procedure declaration as
ALTER PROCEDURE [dbo].[sp_InsertServices]
#Display nvarchar(50),
#Service nvarchar(50),
#Status nvarchar(50),
#Server nvarchar(50)
AS
BEGIN
SET NOCOUNT ON
INSERT INTO Services
(DisplayName, ServiceName, Status, Server)
VALUES
(#Display,#Service,#Status,#Server)
SELECT SCOPE_IDENTITY()
END
And now change the calling code to
using (SqlCommand cmd = new SqlCommand("sp_InsertServices", conn))
{
cmd.CommandType = System.Data.CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("#Display", service.DisplayName);
cmd.Parameters.AddWithValue("#Service", service.ServiceName);
cmd.Parameters.AddWithValue("#Status", service.Status);
cmd.Parameters.AddWithValue("#Server", serverName);
int serviceID = (int)cmd.ExecuteScalar();
// What you want to do with the ID returned?
}
The ID parameter is not needed if your ID field is an IDENTITY column, it is automatically calculated by the database and you could return it using a simple select. If your stored procedure ends with a SELECT SCOPE_IDENTITY() the result can be retrieved on the C# code using a simple ExecuteScalar without the need of an output parameter.
Finally, if you try to ExecuteXXX an SqlCommand using the same connection in use by a SqlDataReader you get an exception because the connection is busy serving the SqlDataReader and cannot execute the command.
The simple solution is to add this to your connection string
....;MultipleActiveResultSets=True;....
See MSDN for MultipleActiveResultSets

Passing Parameters through stored Procedure

I am new to interfacing DB's with applications and am trying to pull a couple of fields from a database where the parameter I specified should filter out the results. I keep receiving a no parameters or arguments were supplied. Can anyone shed a little insight on this? Thanks.
below is the stored procedure:
ALTER PROC dbo.PassParamUserID
AS
set nocount on
DECLARE #UserID int;
SELECT f_Name, l_Name
FROM tb_User
WHERE tb_User.ID = #UserID;
And here is my code
class StoredProcedureDemo
{
static void Main()
{
StoredProcedureDemo spd = new StoredProcedureDemo();
//run a simple stored procedure that takes a parameter
spd.RunStoredProcParams();
}
public void RunStoredProcParams()
{
SqlConnection conn = null;
SqlDataReader rdr = null;
string ID = "2";
Console.WriteLine("\n the customer full name is:");
try
{
//create a new connection object
conn = new SqlConnection("Data Source=.\\SQLEXPRESS;AttachDbFilename=c:\\Program Files\\Microsoft SQL Server\\MSSQL10.SQLEXPRESS\\MSSQL\\DATA\\UserDB.mdf;Integrated Security=True;Connect Timeout=30;User Instance=True; Integrated Security=SSPI");
conn.Open();
//create command objects identifying the stored procedure
SqlCommand cmd = new SqlCommand("PassParamUserID", conn);
//Set the command object so it know to execute the stored procedure
cmd.CommandType = CommandType.StoredProcedure;
//ADD PARAMETERS TO COMMAND WHICH WILL BE PASSED TO STORED PROCEDURE
cmd.Parameters.Add(new SqlParameter("#UserID", 2));
//execute the command
rdr = cmd.ExecuteReader();
//iterate through results, printing each to console
while (rdr.Read())
{
Console.WriteLine("First Name: {0,25} Last Name: {0,20}", rdr["f_Name"], rdr["l_Name"]);
}
}
You need to modify your SQL stored proc to:
ALTER PROC dbo.PassParamUserID
#UserID int
AS set nocount on
SELECT f_Name, l_Name FROM tb_User WHERE tb_User.ID = #UserID;
At the moment you are just declaring it as a variable within the procedure.
Here are some MSDN articles that may help you going forward:
Creating and Altering Stored procedures
Declaring Local Variables
ALTER PROC dbo.PassParamUserID (#UserID int)
AS
set nocount on
SELECT f_Name, l_Name FROM tb_User WHERE tb_User.ID = #UserID;
If you want to pass a parameter in you need to define it before the AS statement as shown above.

Categories