实体框架核心3.1存储过程的返回值(int)

时间:2020-01-07 08:30:38

标签: c# sql-server entity-framework-core-3.1

这将返回-1,如何从存储过程中获取实际的返回值?

这是我的存储过程

ALTER PROCEDURE [Production].[Select_TicketQuantity]
    @Ticket NVARCHAR(25),
    @Reference NVARCHAR(20)
AS
BEGIN

    declare @SQL nvarchar (4000)
    SET @SQL = 'select QARTCOL as Quantidade from D805DATPOR.GCARCCR1 where NCOLGIA = ' + @Ticket + ' AND NARTCOM = ''' + @Reference + ''''
    SET @SQL = N'select CONVERT(int,Quantidade) as Quantidade from OpenQuery(MACPAC, ''' + REPLACE(@SQL, '''', '''''') + ''')'
    PRINT @SQL
    EXEC (@SQL)

END   

C#代码

int? quantity= 0;
try
{
    quantity= await _context.Database.ExecuteSqlRawAsync("EXEC Production.Select_TicketQuantity @p0, @p1", parameters: new[] { ticket, reference});
}
catch (Exception ex)
{
    _logger.LogError($"{ex}");
    return RedirectToPage("Index");
}

4 个答案:

答案 0 :(得分:2)

使用ExecuteSqlRaw / ExecuteSqlRawAsyncAlbertK添加到已写的内容中。

最好的选择是在您的存储过程中使用OUTPUT parameter

想象一个场景,其中有必要从stored procedure中读取 StatusCode

CREATE PROCEDURE dbo.spIfUserExists (
    @Username VARCHAR(120),
    @StatusCode INT OUTPUT,
) AS
BEGIN
    IF EXISTS (SELECT * FROM dbo.[Users] WHERE Username=@Username)
        SET @StatusCode = 1;
    ELSE
        SET @StatusCode = 0;
END

除了向INT分配一个整数(output parameter)之外,您实际上什么都不会返回。

在C#代码中,您需要使用SQLParameter中的output parameter名称创建stored procedure对象。

SqlParameter StatusCode = new SqlParameter
{
    ParameterName = "@StatusCode",
    SqlDbType = System.Data.SqlDbType.Int,
    //Direction of this parameter is output.
    Direction = System.Data.ParameterDirection.Output
 };

最后,使用SP所需的参数执行ExecuteSqlRawAsync

@p0,@p1..@pn表示存储过程的位置参数。

string Username = "MyRandomUsername";

using (var ctx = new UsersDbContext())
{
    await ctx.Database.ExecuteSqlRawAsync(
      "spIfUserExists @p0, @StatusCode OUT",
      parameters: new object[] {
        Username,
        StatusCode,
      });
}

//StatusCode now contains the status code from the [spIfUserExists].
Console.WriteLine(Convert.ToInt32(StatusCode.Value));

输出参数不需要像普通参数那样的位置参数,例如Username因此,您不需要将@p[n]传递给{ {1}},但仅将其提供给OUTPUT arguments列表。

如果要使用parameters,则编码没有区别。您只需要删除ExecuteSqlRaw关键字即可同步运行await

您的存储过程中可以有多个具有不同数据类型的ExecuteSqlRaw。您只需要遵循相同的模式即可。

答案 1 :(得分:0)

要扩展@AlbertK的答案,可以向DatabaseFacade类型添加扩展名,以获取返回的不同类型的标量值。

确保为SqlParameter参数使用Microsoft.Data.SqlClient包而不是System.Data.SqlClient,否则将收到运行时异常。 https://github.com/dotnet/efcore/issues/16812

public static class DatabaseFacadeExtensions
{
    public static async Task<TResult> ExecuteScalarAsync<TResult>(
        this DatabaseFacade database, 
        String commandText, 
        CommandType commandType, 
        params SqlParameter[] parameters)
    {
        TResult result;
        using (var cmd = database.GetDbConnection().CreateCommand())
        {
            cmd.CommandText = commandText;
            cmd.CommandType = commandType;
            if (cmd.Connection.State != System.Data.ConnectionState.Open) cmd.Connection.Open();
            cmd.Parameters.AddRange(parameters);
            result = (TResult)(await cmd.ExecuteScalarAsync());
        }
        return result;
    }
}

答案 2 :(得分:0)

另一种不需要您修改 SP 的方法:使用 DbSet 的 FromSqlRaw 扩展方法以获得 IQueryable 中的结果。

public static IQueryable<TResult> RunSp<TResult>(this DbSet<TResult> dbSet, string storedProcedure, DbParameter[] parameters = null) where TResult : class
    {
        var paramNames = string.Empty;
        if (parameters != null)
        {
            paramNames = string.Join(",", parameters.Select(a => a.ParameterName));
        }
        return dbSet.FromSqlRaw($"EXEC {storedProcedure} {paramNames}", parameters ?? new object[]{} );
    }

您需要在您的上下文中定义一个 DbSet,并使用您想要获得的结果类型:

[Keyless]
    public class TestSpRecord
    {
        public int Field1 { get; set; }
        public string Field2 { get; set; }
    }

用法如下:

var resultList = testCtx.TestSpRecords.RunSp("EchoParams", new DbParameter[] {  new SqlParameter("@Param1",10),new SqlParameter("@Param2","text")});
        

答案 3 :(得分:0)

我知道这真的很旧,但我有一些存储过程使用以下作为过程中的最后一行。

select SCOPE_IDENTITY()

话虽如此,我借用了 EF Core 源代码片段并进行了调整以允许 ExecuteScalar。 https://github.com/dotnet/efcore/blob/main/src/EFCore.Relational/Extensions/RelationalDatabaseFacadeExtensions.cs

    public static TResult ExecuteScalar<TResult>(this DbContext dbContext, string sql, params object[] parameters)
    {            
        var facadeDependencies = (IRelationalDatabaseFacadeDependencies)((IDatabaseFacadeDependenciesAccessor)dbContext.Database).Dependencies;

        var rawSqlCommand = facadeDependencies.RawSqlCommandBuilder.Build(sql, parameters);
            
        var logger = facadeDependencies.CommandLogger;

        TResult result = (TResult)rawSqlCommand.RelationalCommand
                            .ExecuteScalar(
                                new RelationalCommandParameterObject(
                                    facadeDependencies.RelationalConnection,
                                    rawSqlCommand.ParameterValues,
                                    null,
                                    dbContext,
                                    logger, true));

        return result;
    }