MVC C# Count Records Based on Condition - c#

I am trying to count and display the number of logs conducted by various users from the Admin section.
Each user has a UserID and through this, I would like to count and display data from a specific table.
I have made of a stored procedure and passed it to the Controller via a ViewBag but it keeps returning 1 as the total for all values.
the procedure is Below
CREATE proc [Usp_GetCallCountByUserID]
#UserID int output
AS
BEGIN
SELECT COUNT (*) FROM Customer Where UserID= #UserID
GROUP BY UserID
set #UserID =##ROWCOUNT
END
GO
Controller:
public ActionResult Index()
{
con.Open();
SqlCommand comm = new SqlCommand("Usp_GetCallCountByUserID", con);
comm.CommandType = CommandType.StoredProcedure;
comm.Parameters.Add("#UserID", SqlDbType.Int).Direction =
ParameterDirection.Output;
SqlDataReader reader;
reader = comm.ExecuteReader();
reader.Close();
ViewBag.CountLog = comm.Parameters["#UserID"].Value.ToString();
con.Close();
var callCustomers = db.Registrations;
return View(callCustomers.ToList());
}

Edit: answer edited after clarification
If I'm right you would do something like this:
CREATE proc [Usp_GetCallCountByUserID]
#UserId int
#TotalCount int output
AS
BEGIN
SELECT #TotalCount = COUNT(*) FROM Customer WHERE UserId = #UserId
END
Please modify calling c# code accordingly. You do not need the GROUP BY clause, if grouping by the same field you are filtering on.
Consideration: IMO a stored procedure is overkill for such a simple task, and does not give any sensible performance improvements, expecially if your application and DB server are always up (both will optimize the query execution, if repeated). When queries are so simple, I'd rather execute a CommandType.Text DbCommand:
conn.Open();
using (var cmd = conn.CreateCommand())
{
cmd.CommandType = CommandType.Text;
cmd.CommandText = "SELECT Count(*) FROM Customer where UserId = #UserId";
cmd.Parameters.Add("#UserId", SqlDbType.Int).Value = currentUserId;
using (var reader = cmd.ExecuteReader())
{
reader.Read(); // Advance one record
ViewBag.CountLog = reader.GetInt32(0);
}
}
Finally, since you seem to be using some sort of ORM (Entity Framework?), why not just:
db.Customers.Count(c => c.UserId == currentUserId);
That is pretty much equivalent to the code above.

First of all your stored procedure does exactly as expected, since ##ROWCOUNT returns the number of rows in the result.
You have to write your result in another variable like shown below.
For your Stored Procedure
DECLARE #Result INT
SELECT #Result = COUNT (*) FROM Customer Where UserID= #UserID
GROUP BY UserID
And then Read this value ;)
And Changes for your Code
reader.Close();
ViewBag.CountLog = comm.Parameters["#Result"].Value.ToString();
con.Close();

Don't use global variable ##ROWCOUNT
you have to save the value of COUNT(*) in your request, look code below,
CREATE proc [Usp_GetCallCountByUserID]
#UserID int,
#RowCount int output
AS
BEGIN
SELECT #RowCount = COUNT (*) FROM Customer Where UserID= #UserID
GROUP BY UserID
END
GO

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 return output varchar value from SQL stored procedure

I am trying to get a varchar value from SQL to use in function in C# here is how i tried to create Stored Procedure to return ,i appreciate any help.
#result
as an output value
CREATE PROCEDURE [dbo].[RolValidation]
#UserID int,
#result varchar(50) output
AS
BEGIN
Select #result=role from LogUser where UserID=#UserID
end
And here is the function in C#
if (sqlCon.State == ConnectionState.Closed)
sqlCon.Open();
SqlCommand sqlcmd = new SqlCommand("RolValidation", sqlCon);
sqlcmd.CommandType = CommandType.StoredProcedure;
sqlcmd.Parameters.AddWithValue("#UrunID", SqlDbType.Int);
sqlcmd.Parameters.Add("#result", SqlDbType.NVarChar,50).Direction = ParameterDirection.Output;
string rol = Convert.ToString(sqlcmd.Parameters["#UrunID"].Value);
sqlcmd.ExecuteNonQuery();
returnValue = rol;
It looks like that the code from your question is not the real code that you are working on so there might be other reasons why you don't get your value back.
However, judging from what I see if you modify the SP a little:
CREATE PROCEDURE [dbo].[RolValidation]
#UserID INT;
AS
BEGIN
SET NOCOUNT ON;
DECLARE #result varchar(50);
SET #result = Select role from LogUser where UserID=#UserID;
RETURN #result;
END
And in your C# code:
if (sqlCon.State == ConnectionState.Closed)
{
sqlCon.Open();
}
SqlCommand sqlcmd = new SqlCommand("RolValidation", sqlCon);
sqlcmd.CommandType = CommandType.StoredProcedure;
sqlcmd.Parameters.AddWithValue("#UserID", SqlDbType.Int);
sqlcmd.Parameters["#UserID"].Value = userID;
var rol = cmd.Parameters.Add("#result", SqlDbType.NVarChar, 50);
rol.Direction = ParameterDirection.ReturnValue
sqlcmd.ExecuteNonQuery();
returnValue = rol.Value;
Basically there were two main issues with your original code
1 - you should SET the value and then "RETURN" it. You can set it inline the SELECT query as you have done, but I kinda like this explicit approach a bit more
2 - you were trying to get the value before executing the query:
string rol = Convert.ToString(sqlcmd.Parameters["#UrunID"].Value);
sqlcmd.ExecuteNonQuery();
which is wrong, it should be the other way around - first execute the query then try to get the .Value
Other than that I'm not aware of the entire code but it's a good practice to wrap the connection and the command in using statements. If you don't have a good reason not to do that I suggest to change it.
P.S
Now I see that you are not passing the UserID value, at least in the code from the original question. So make sure to add this too, in my answer it's this row:
sqlcmd.Parameters["#UserID"].Value = userID;

Stored procedure doesn't return an int value

MySql Procedure Code:
CREATE DEFINER=`root`#`localhost` PROCEDURE `USP_CreateCliente`(IN nome_cliente VARCHAR(45))
BEGIN
Select 20;
INSERT INTO clienti ( nome_cliente )
VALUES ( nome_cliente );
Select id_cliente from clienti;
END
C# code in the controller page:
ClienteInfo CI = new ClienteInfo();
DboUser objdbo = new DboUser();
int id_cliente = 0;
CI.nome_cliente = txtNomeCliente.Text;
id_cliente = objdbo.CreateClienteInfo(CI);
DboUser class:
public int CreateClienteInfo(ClienteInfo CI)
{
int result;
MySqlConnection conn = new MySqlConnection();
DbConnection db = new DbConnection();
conn = db.ConnessioneDb();
MySqlCommand cmd = new MySqlCommand(Costanti.StoredProcedures.USP_CreateCliente, conn);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("#nome_cliente", CI.nome_cliente);
result = cmd.ExecuteNonQuery();
conn.Close();
return result;
}
I want my C# code to retrieve the id of my customer after inserting it into the database, so I can save it to the session and retrieve it again later in a page that will print a document with the customer's informations.
My id_cliente returns 0, do you notice any syntax error?
Did I do something wrong?
I'm 90% sure it's a problem dealing with the stored precedure tough, cause the customer is inserted correctly
Change this line
result = cmd.ExecuteNonQuery();
to
result = Convert.ToInt32(cmd.ExecuteScalar());
but you should also change your stored procedure because it doesn't return the last id generated for you by the AUTO_INCREMENT column id_cliente
CREATE DEFINER=`root`#`localhost` PROCEDURE `USP_CreateCliente`(IN nome_cliente VARCHAR(45))
BEGIN
INSERT INTO clienti ( nome_cliente ) VALUES ( nome_cliente );
Select LAST_INSERT_ID();
END
In MySql, to get the generated auto_increment value, you could use LAST_INSERT_ID(), next, your C# code don't need to use ExecuteNonQuery, which returns just the number of rows that you have changed, added or deleted, but you use ExecuteScalar which returns the first column of the first row of the last SELECT command executed by your stored procedure. (It is the SELECT LAST_INSERT_ID())
Also, to complete the answer, you don't really need a stored procedure for this kind of simple work. The advantages should be minimal while the problems related to a different piece of software to maintain are self evident.
Your C# code could be (removed the usage of your DbConnection class because it is not clear what it does)
public int CreateClienteInfo(ClienteInfo CI)
{
int result;
string cmdText = #"INSERT INTO clienti ( nome_cliente ) VALUES ( nome_cliente );
Select LAST_INSERT_ID();";
using(MySqlConnection conn = new MySqlConnection(....connectionstring .....))
using(MySqlCommand cmd = new MySqlCommand(cmdText, conn);
{
conn.Open()
cmd.Parameters.AddWithValue("#nome_cliente", CI.nome_cliente);
result = Convert.ToInt32(cmd.ExecuteScalar())
return result;
}
}
Here you use the possibility to pass batch commands to your MySql engine, meaning two commandtexts with the same MySqlCommand separating them with the semicolon

Insert 10000 records from C# into SQL Server

I have a C# console program
Selects 10000 records from Input table (Keyid varchar(4), Address varchar(100), Name varchar(100)).
for each record from Input table, it calls an API that returns data (if person works in that address, status is OK else NOT OK, also returns address type-place of interest or residential, commercial etc.) that needs to be saved in one main table and a detail table.
Main table:
1001|JOE STILTON| 2 MAIN ST, SALEM,PA| OK|4/15/2014
Detail table:
1001|PHARMACY
1001|COMMERCIAL
i.e Joe works in a pharmacy which is also a commercial bldg.
Right now, I call the API. then I call a method,
private static void insertTable(string keyid, DateTime updDate, string name, string address,string status)
{
Int32 rowsAffected = 0;
string connectionString = GetConnectionString();
SqlConnection connection = new SqlConnection(connectionString);
connection.Open();
SqlCommand cmd = new SqlCommand("google.usp_InsertCompanyAddrComponents", connection);
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandTimeout = 90;
cmd.Parameters.Add("#keyid", SqlDbType.VarChar);
cmd.Parameters["#keyid"].Value = keyid;
cmd.Parameters.Add( new SqlParameter("#dateverified", updDate));
cmd.Parameters.Add("#name", SqlDbType.VarChar);
cmd.Parameters["#name"].Value = name;
cmd.Parameters.Add("#address", SqlDbType.VarChar);
cmd.Parameters["#address"].Value = address;
cmd.Parameters.Add( new SqlParameter("#status", status));
try
{
rowsAffected = cmd.ExecuteNonQuery();
}
catch (Exception ep)
{
Console.WriteLine(ep.Message);
}
connection.Close();
}
Then, I call another similar method that inserts into detail table.
Since I have to do this for 10,000 records at a time, there is lot of I/O. how can I change to do batch insert? all 10000 insert at a time?
Thanks
R
You can also take a look into table types in SQL Server. You can pass two table types in stored procedure and do required operations directly over there.
Here is my sample stored proc
CREATE PROCEDURE [dbo].[usp_AssociateTags]
#Tags AS UDT_Tag READONLY
AS
SET XACT_ABORT ON
BEGIN TRAN
--Insert into Tag Master
INSERT INTO dbo.TagMaster
(
Name
,IsActive
)
VALUES ( '', -- Name - varchar(50)
1 -- IsActive - bit
)
DECLARE #TagId AS INT
SET #TagId=SCOPE_IDENTITY()
INSERT INTO dbo.TagCollection
( TagNumber, TagId )
SELECT TagNumber, #TagId FROM #Tags t
WHERE NOT EXISTS(SELECT * FROM dbo.TagCollection WHERE TagNumber = t.TagNumber)
COMMIT TRAN
SET XACT_ABORT OFF
Script to test this stored procedure
--DECLARE #hello as UDT_Tag
--INSERT INTO #hello VALUES('vaibhav')
--INSERT INTO #hello VALUES('Shantanu')
--INSERT INTO #hello VALUES('Sam')
--INSERT INTO #hello VALUES('Aakash')
--EXEC usp_AssociateTags #hello
--SELECT * FROM dbo.TagCollection
C# code to consume this procedure
SqlParameter Tags = new SqlParameter { ParameterName = "#Tags"
, Value = entity.Tags.ToDataTable()
, Direction = ParameterDirection.Input
, SqlDbType = SqlDbType.Structured, TypeName="UDT_Tag" };
SqlHelper.ExecuteNonQuery(tran, CommandType.StoredProcedure
, "usp_AssociateTags", Tags);
CodeProject

How to insert a record and return the newly created ID using a single SqlCommand?

I'm using an SqlCommand object to insert a record into a table with an autogenerated primary key. How can I write the command text so that I get the newly created ID when I use the ExecuteScalar() method?
INSERT INTO YourTable(val1, val2, val3 ...)
VALUES(#val1, #val2, #val3...);
SELECT SCOPE_IDENTITY();
Don't forget the semicolons at the end of each statement.
Add the following line to the end of the Sql Query...
SELECT SCOPE_IDENTITY()
And then use the ExecuteScalar method on the SqlCommand object...
var rowCount = command.ExecuteScalar()
insert into Yourtable()
values()
SELECT SCOPE_IDENTITY()
I just ran a test and verified that the semi-colons are optional using SQL Server 2005 SP2, and .Net 3.5
Add an output parameter to the command object and then set the value to the new ID in the stored procedure.
Stored Procedure:
#ID AS INT OUTPUT
[Insert Command]
SET #ID = SCOPE_IDENTITY()
.NET:
cmd.CommandText = "stored_procedure";
SqlParameter pID = new SqlParameter("ID", DBType.Int32, 4);
pID.Direction = ParameterDirection.Output;
cmd.ExecuteScalar();
int id = Convert.ToInt32(cmd.Parameters["ID"].Value.ToString());
Don't use ##IDENTITY, however simple it may seem. It can return incorrect values.
SELECT SCOPE_IDENTITY()
appears to be the obvious choice.
Although I like Dave Markle's answer, ( and I see you did too, since you marked it as your answer ), that method can fail if you have triggers on your database, that audit CUD operations, and your audit table has an IDENTITY column. It would return the value of the Audit table's identity, not the table you just inserted into, since the audit table actualy happen after.
In that case, a more generic method can be used that will work in both cases, regardless of any auditing. Its a bit more wordy, but you get what you pay for.
example:
#"DECLARE #tmp AS TABLE ( id int )
INSERT INTO case
(
caseID,
partID,
serialNumber,
hardware,
software,
firmware
)
OUTPUT Inserted.ID into #tmp
VALUES
(
#caseID,
#partItemID,
#serialNumber,
#hardware,
#software,
#firmware
)
Select ID from #tmp" )
Immediately after your insert stmt, use
SELECT CAST(scope_identity() AS bigint) ---- incase you have a return result as int64
This will return the column created id/identity.
If your id is a Guid, then I found this solution to be best:
INSERT INTO YourTable (val1, val2, val3)
OUTPUT inserted.id
VALUES (#val1, #val2, #val3)
Thanks #Scott Ivey
Full demo:
internal static Guid InsertNote(Note note)
{
Guid id;
using (
var connection =
new SqlConnection(ConfigurationManager.ConnectionStrings["dbconn"].ConnectionString))
{
connection.Open();
using (
var command =
new SqlCommand(
"INSERT INTO Notes ([Title],[Text]) " +
"OUTPUT inserted.id " +
$"VALUES ('{title}','{text}');", connection))
{
command.CommandType = CommandType.Text;
var reader = command.ExecuteReader();
reader.Read();
id = reader.GetGuid(reader.GetOrdinal("id"));
}
connection.Close();
}
return id;
}
I would recommend using a Stored Procedure, but this is for unit testing our repository.
Straight out of the Whirlpool:
If you're using MS SQL you can use "SELECT ##IDENTITY as Value" after your insert to get the last ID generated
and:
##IDENTITY and SCOPE_IDENTITY return the last identity value generated in any table in the current session. However, SCOPE_IDENTITY returns the value only within the current scope; ##IDENTITY is not limited to a specific scope.
Edit: As pointed out in the comments, you should always use SCOPE_IDENTITY, not ##IDENTITY.

Categories