Programmatically get Windows Services and write to SQL - c#

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

Related

How should I get the values from the select query of the stored procedure in c#

I want the date and the name from the select query which if I run as normal query I get the results but i when I try to get the results in C# all I get is count=0. Can anyone tell me what wrong am I doing?
Here is the C# code
private List<CertificationSummary> GetLastAccessData (string taskOwner)
{
List<CertificationSummary> lastAccessedResult = new List<CertificationSummary>();
string connectionString = SqlPlusHelper.GetConnectionStringByName("MetricRepositoryDefault");
using (SqlConnection connection = new SqlConnection(connectionString))
{
SqlParameter[] sqlParams = new SqlParameter[1];
sqlParams[0] = new SqlParameter("#taskOwner", SqlDbType.NVarChar);
sqlParams[0].Value = taskOwner;
connection.Open();
SqlCommand cmd = connection.CreateCommand();
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = "GetLastAccessedCertificationData";
cmd.Parameters.AddRange(sqlParams);
cmd.ExecuteNonQuery();
}
return lastAccessedResult;
}
And here is the stored procedure:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[GetLastAccessedCertificationData]
(#taskOwner nvarchar(255))
AS
BEGIN
DECLARE #name nvarchar(100)
DECLARE #lastAccessedDate [datetime]
SELECT #name = Name
FROM CertificationReviewCycles
INNER JOIN UserReviewCycleAccess ON CertificationReviewCycles.CertificationReviewCycleID = UserReviewCycleAccess.LastAccessedReviewCycleID
WHERE USERID = #taskOwner
SELECT #lastAccessedDate = LastAccessedDate
FROM UserReviewCycleAccess
WHERE UserID = #taskOwner
CREATE TABLE #tempTable
(
name [nvarchar](255) NULL,
[LastAccessedDate] [datetime] NULL,
)
INSERT INTO #tempTable VALUES (#name, #lastAccessedDate)
SELECT TOP(1) name, LastAccessedDate
FROM #tempTable
END
GO
You are returning lastAccessedResult which is has just been set to new List<CertificationSummary>(). This list has no items, so it has a count of 0.
Use ExecuteReader instead of ExecuteNonQuery and you can then read the data returned and store them into your lastAccessedResult list.
Read here for more info.
ExecuteNonQuery will not return results, and should only be used when you don't expect rows back. This is common for UPDATE statements.
Since you're interested in reading the rows returned by the stored procedure, use ExecuteReader, e.g var reader = cmd.ExecuteReader();
See here for more:
https://learn.microsoft.com/en-us/dotnet/api/system.data.sqlclient.sqldatareader?view=dotnet-plat-ext-3.1
You're using ExecuteNonQuery, which discards any grids from the query. You need to use ExecuteReader to consume grids, but it is a lot of mess and ceremony - the API is verbose. Frankly, I'd recommend a tool like "Dapper" (freely available on NuGet), then this becomes just
private List<CertificationSummary> GetLastAccessData (string taskOwner)
{
string connectionString = SqlPlusHelper.GetConnectionStringByName("MetricRepositoryDefault");
using var connection = new SqlConnection(connectionString);
return connection.Query<CertificationSummary>(
"GetLastAccessedCertificationData",
new { taskOwner }, // <== parameters
commandType: CommandType.StoredProcedure).AsList();
}

How to combine multiple SQL Commands into one?

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

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.

How to solve Procedure or function add_route has too many arguments specified error?

I have created a stored procedure in ms sql server 2016 as follows
create proc add_route
(#from varchar(45),
#to varchar(45),
#cost decimal,
#res int out)
as
begin
if(not exists(select Route_id from Route_Master where From_location=#from and To_location=#to))
begin
insert into Route_Master(From_location,To_location,Cost) values(#from,#to,#cost);
set #res=1;
return #res
end
end
And I'm using the following code using c# to receive the output returned by the stored procedure.
public int addnewroute(string from, string to, decimal cost)
{
using (SqlConnection con = new SqlConnection(connectionString))
{
con.Open();
SqlCommand cd = new SqlCommand("add_route", con);
cd.CommandType = System.Data.CommandType.StoredProcedure;
cd.Parameters.AddWithValue("#from ",from);
cd.Parameters.AddWithValue("#to",to);
cd.Parameters.AddWithValue("#cost",cost);
cd.Parameters.Add(new SqlParameter("#res", SqlDbType.Int));
cd.Parameters["#res"].Direction= ParameterDirection.Output;
cd.ExecuteNonQuery();
return (int)cd.Parameters["#res"].Value;
}
}
But I'm getting the error as ": Procedure or function add_route has too many arguments specified". But I'm passing equal number of arguments to the stored procedure. How do I clear this error?

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