【问题标题】:Entity Framework Core 3.1 Return value (int) from stored procedureEntity Framework Core 3.1 存储过程的返回值(int)
【发布时间】:2020-01-07 08:30:38
【问题描述】:

这返回-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");
}

【问题讨论】:

  • 考虑为它标记或分享解决方案...

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


【解决方案1】:

ExecuteSqlRawAsync 返回 the number of rows affected 用于插入、更新和删除(-1 用于选择)。

如果您不想更改您的 SP 以引入 output parameter,您可以使用 SqlCommandSqlCommand.ExecuteScalar() 返回The first column of the first row in the result set

using (var cmd = _context.Database.GetDbConnection().CreateCommand()) {
    cmd.CommandText = "[Production].[Select_TicketQuantity]";
    cmd.CommandType = System.Data.CommandType.StoredProcedure;
    if (cmd.Connection.State != System.Data.ConnectionState.Open) cmd.Connection.Open();
    cmd.Parameters.Add(new SqlParameter("Ticket", ticket));
    cmd.Parameters.Add(new SqlParameter("Reference", reference));
    quantity = (int)cmd.ExecuteScalar();
}

【讨论】:

  • 非常感谢您的出色回答。
【解决方案2】:

根据您的情况,您需要创建和使用output parameter

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

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

END   

然后在 C# 中:

int? quantity = 0;
var ticketParam = new SqlParameter("Ticket", ticket);
var referenceParam = new SqlParameter("Reference", reference);
var quantityParam = new SqlParameter("Quantity") { Direction = ParameterDirection.Output };
try
{
    await _context.Database.ExecuteSqlRawAsync("EXEC Production.Select_TicketQuantity @Ticket, @Reference, @Quantity output", new[] { ticketParam, referenceParam, quantityParam });
    quantity = Convert.ToInt32(quantityParam.Value);
}
catch (Exception ex)
{
    _logger.LogError($"{ex}");
    return RedirectToPage("Index");
}

【讨论】:

  • 这个解决方案看起来很有趣,但在转换时表示 Quantidade 上的列无效,并且在 From Openquery 中的 @SQL 处需要一个字符串
  • 你可以把sproc中的文字替换成原来的内容,那是我玩的。
【解决方案3】:

使用ExecuteSqlRaw/ExecuteSqlRawAsync 添加AlbertK 已经编写的内容。

最好的办法是在你的存储过程中使用OUTPUT parameter

想象一个场景,读取来自stored procedureStatusCode是必要的:

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# 代码中,您需要使用您的 stored procedure 中的 output parameter 名称创建 SQLParameter 对象。

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] 传递给OUTPUT arguments,而只需将它们提供给parameters 列表。

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

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

【讨论】:

    【解决方案4】:

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

    确保您使用 Microsoft.Data.SqlClient 包而不是 System.Data.SqlClient 作为 SqlParameter 参数,否则您将收到运行时异常。 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;
        }
    }
    

    【讨论】:

      【解决方案5】:

      另一种不需要您修改 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")});
              
      

      【讨论】:

        【解决方案6】:

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

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

        【讨论】:

          猜你喜欢
          • 2016-03-15
          • 1970-01-01
          • 1970-01-01
          • 2021-09-10
          • 2021-11-08
          • 1970-01-01
          • 2020-12-11
          • 2020-05-21
          • 1970-01-01
          相关资源
          最近更新 更多