使用EF Core获取存储过程的输出参数值?

时间:2017-04-25 21:25:48

标签: c# entity-framework asp.net-core entity-framework-core

我在我的应用程序中使用Asp.net核心和EF核心。基本上我想从单个存储过程中获取多个结果集。试图搜索它最近2天没有这样的运气。试图弄清楚解决它的工作..

这是我的存储过程:

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO

ALTER PROCEDURE [dbo].[usp_CustomerAll_sel]
    @SomeOutput int OUT
AS
BEGIN
    SET NOCOUNT ON;

    SELECT * 
    FROM [dbo].[Customer]

    SELECT @SomeOutput = @@rowcount + 25 --This number 25 is a variable from a complex query but always an integer
END

我在该表中有2条记录,所以基本上它应该返回一个客户类型表,输出参数应该返回27 ..

现在从我的.net代码到目前为止我尝试过了

[HttpGet]
public async Task<Tuple<IEnumerable<Customer>, int>> GetAllCustomer()
{
    var votesParam = new SqlParameter
            {
                ParameterName = "SomeOutput",
                Value = -1,
                Direction = ParameterDirection.Output
            };

    var y = await _customerContext.Customers.FromSql("usp_CustomerAll_sel @SomeOutput out", votesParam).ToArrayAsync();

    return new Tuple<IEnumerable<Customer>, int>(y, (int)votesParam.Value);
}

上面的一个返回列表,但我没有从DB .(int)votesParam.Value得到输出参数的值显示为空

现在,如果我使用ExecuteNonQueryAsync,那么我将获得输出参数,而不是实际数据

private async Task ExecuteStoredProc()
{
    DbCommand cmd = _customerContext.Database.GetDbConnection().CreateCommand();

    cmd.CommandText = "dbo.usp_CustomerAll_sel";
    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();
    }

    await cmd.ExecuteNonQueryAsync();

    long SomeOutput = (long)cmd.Parameters["@SomeOutput"].Value;
}

有没有办法同时获得结果集和输出参数并作为元组返回?

当我输入硬编码值时,它看起来像

[HttpGet]
public async Task<Tuple<IEnumerable<Customer>, int>> GetAllCustomer()
{
    var votesParam = new SqlParameter
    {
        ParameterName = "SomeOutput",
        Value = -1,
        Direction = ParameterDirection.Output
    };

    var y = await _customerContext.Customers.FromSql("usp_CustomerAll_sel @SomeOutput out", votesParam).ToArrayAsync();
    return new Tuple<IEnumerable<Customer>, int>(y, **25**);
}

结果如

{"item1":[{"customerId":1,"customerName":"Cus1"},{"customerId":2,"customerName":"Cus2"}],"item2":27}

基本上这就是我要找的......有什么帮助吗?

4 个答案:

答案 0 :(得分:1)

这应该有效。这次我只填充了一个DataTable,但你可以使用多个DataTables填充一个DataSet

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();
                }

由于您无法在.net核心中使用SqlDataAdapter,因此您可以使用第三方库来实现相同的结果,例如NReco.Data 实际上,代码非常相似。

答案 1 :(得分:1)

在EF Core中,您无法从原始SQL查询中返回特殊类型(他们正在处理此问题),因此首先,您需要针对此问题进行锻炼,将此类添加到您的项目中:

 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);
            }
        }
    }
}

然后你可以调用下面的方法并从你的SP获得OUTPUT,这是一个示例:

            var _sMsg = new SqlParameter("sMsg", "")
            {
                Direction = ParameterDirection.Output,
                DbType = DbType.String,
                Size = 500
            };

            var sql = "exec sp_foo @sUserId, @sMsg OUTPUT";

            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();

答案 2 :(得分:0)

以下是如何使用ADO.NET从存储过程中获取多个结果集的示例:

Return multiple recordsets from stored proc in C#

但是,在这种情况下你必须抛弃输出参数。

答案 3 :(得分:0)

我在现有数据库上构建新应用程序时遇到了非常相似的问题,因此我被迫使用存储的proc。 我发现使用ExecuteScalar()查找单个结果并使用ExecuteReader()填充列表成功。 在下面,我将添加代码片段。

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;
        }
    }

这是存储的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

在SQL中执行存储的proc会创建以下代码:

    DECLARE @return_value int

EXEC    @return_value = [dbo].[usp_GetCompanyLogSheets]
        @Period = N'June 2017'

SELECT  'Return Value' = @return_value

这似乎很简单,但是由于某种原因return_value始终为0。在返回单个整数的更基本的存储过程中,我遇到了同样的问题。我必须使用var myVar = cmd.ExecuteScalar()来获得该返回值。

我希望这会有所帮助,我花了2天的时间找到了可行的解决方案。我不知道它的效率如何,但是它会起作用,直到EF Core发布正式解决方案之前,我都会使用它。