I am using Asp.net core and EF core in my application. Basically I want to get multiple result set from a single stored procedure. Tried to search it for last 2 days no such
I had a very similar issue building a new app on an existing DB so I was forced to use the stored procs. I found success using ExecuteScalar() to find single results and ExecuteReader() to populate my lists. Below I will add snippets of my code.
public async Task<IEnumerable<vCompanyLogsheet>> GetCompanyLogsheet(object period)
{
using (var db = new TimeMachineTestContext())
{
DbCommand cmd = db.Database.GetDbConnection().CreateCommand();
cmd.CommandText = "exec @return_value = dbo.[usp_GetCompanyLogSheets] 'June 2017'";
cmd.CommandType = CommandType.Text;
cmd.Parameters.Add(new SqlParameter("@return_value", SqlDbType.Int) { Direction = ParameterDirection.Output });
if (cmd.Connection.State != ConnectionState.Open)
{
cmd.Connection.Open();
}
var data = new List<vCompanyLogsheet>();
DbDataReader reader = await cmd.ExecuteReaderAsync();
if (reader.HasRows)
{
while (reader.Read())
{
data.Add(new vCompanyLogsheet()
{
TimeEntryId = reader.GetInt32(0),
TimeEntryDate = reader.GetString(1),
FullName = reader.GetString(2),
ProjectName = reader.GetString(3),
ProjectCategoryName = reader.GetString(4),
CategoryGroup = reader.GetString(5),
TimeEntryDetail = reader.GetString(6),
NrHours = reader.GetDecimal(7),
});
}
}
else
{
Console.WriteLine("No rows found.");
}
reader.Close();
if (cmd.Connection.State == ConnectionState.Open)
{
cmd.Connection.Close();
}
return data;
}
}
And here is the stored proc:
CREATE PROCEDURE [dbo].[usp_GetCompanyLogSheets]
@Period varchar(50)
AS
SELECT
TimeEntryId,
CONVERT(varchar(50),TimeEntryDate,105) as TimeEntryDate,
FullName,
ProjectName,
ProjectCategoryName,
ISNULL(ProjectCategoryGroup , 'N/A') as CategoryGroup,
TimeEntryDetail,
CONVERT(DECIMAL(6,2), NrHours) as NrHours
FROM
viewTimeEntries
WHERE
Period = @Period
ORDER BY
TimeEntryDate
Executing the stored proc in SQL creates the following code:
DECLARE @return_value int
EXEC @return_value = [dbo].[usp_GetCompanyLogSheets]
@Period = N'June 2017'
SELECT 'Return Value' = @return_value
It seems simple enough, but for some reason return_value is consistently 0. In more basic stored procs returning a single integer I had the same issue. I had to use var myVar = cmd.ExecuteScalar() to get that return value.
I hope this helps, it took me like 2 days to find a working solution. I don't know how efficient it is, but it works and I'll use it until EF Core releases an official solution.
This should work. This time I filled only a DataTable, but you can fill a DataSet with multiples DataTables
using (SqlConnection connection = new SqlConnection(_customerContext.Database.Connection.ConnectionString))
{
SqlCommand cmd = new SqlCommand("dbo.usp_CustomerAll_sel", connection);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add(new SqlParameter("@SomeOutput", SqlDbType.BigInt) { Direction = ParameterDirection.Output, Value = -1 });
if (cmd.Connection.State != ConnectionState.Open)
{
cmd.Connection.Open();
}
connection.Open();
SqlDataAdapter adapter = new SqlDataAdapter(cmd);
DataTable dt = new DataTable();
adapter.Fill(dt);
long SomeOutput = (long)cmd.Parameters["@SomeOutput"].Value;
connection.Close();
}
Since you can't use SqlDataAdapter in .net core, you can use a third party library to archieve the same result, like NReco.Data , actually, the code is pretty similar.
Here's an example of how you can get multiple result sets from a stored procedure, if you are OK with ADO.NET:
Return multiple recordsets from stored proc in C#
You have to ditch the output parameter in this case, though.
In EF Core you can't return ad-hoc types from raw SQL queries yet (they are working on this), so you will need a workaround for this issue. Add this class to your project:
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.EntityFrameworkCore
{
public static class RDFacadeExtensions
{
public static RelationalDataReader ExecuteSqlQuery(this DatabaseFacade databaseFacade, string sql, params object[] parameters)
{
var concurrencyDetector = databaseFacade.GetService<IConcurrencyDetector>();
using (concurrencyDetector.EnterCriticalSection())
{
var rawSqlCommand = databaseFacade
.GetService<IRawSqlCommandBuilder>()
.Build(sql, parameters);
return rawSqlCommand
.RelationalCommand
.ExecuteReader(
databaseFacade.GetService<IRelationalConnection>(),
parameterValues: rawSqlCommand.ParameterValues);
}
}
}
}
Then you can call the method below and get the OUTPUT from you SP, here's a sample:
var _sMsg = new SqlParameter("sMsg", "")
{
Direction = ParameterDirection.Output,
DbType = DbType.String,
Size = 500
};
var sql = "exec sp_foo @sUserId, @sMsg OUTPUT";
using (var dr = _ctx.Database.ExecuteSqlQuery(sql, _sUserID, _sMsg))
{
//here you can retrive your table
while (dr.DbDataReader.Read())
{
var bar = dr.DbDataReader[0].ToString();
}
//here is your OUTPUT
return _sMsg.Value.ToString();
}