Entity Framework Core 3.1 Возвращаемое значение (int) из хранимой процедуры

это возвращает -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");
}

person Jackal    schedule 07.01.2020    source источник
comment
Подумайте о том, чтобы отметить или поделиться решением для него ...   -  person Diego Venâncio    schedule 04.04.2020


Ответы (5)


ExecuteSqlRawAsync возвращает the number of rows affected для вставок, обновлений и удалений (-1 для выборок).

Если вы не хотите изменять свой SP, чтобы ввести output parameter, вы можете использовать SqlCommand. SqlCommand.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();
}
person AlbertK    schedule 07.01.2020

Вам необходимо создать и использовать выходной параметр, в вашем случае:

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");
}
person Ian Kemp    schedule 07.01.2020
comment
это решение выглядит интересным, но говорит о недопустимом столбце в Quantidade при преобразовании и ожидает строку в @SQL в From Openquery - person Jackal; 07.01.2020
comment
Вы можете заменить текст в sproc исходным содержимым, это я немного поиграл. - person Ian Kemp; 08.01.2020

Чтобы добавить к тому, что AlbertK уже написал, но с использованием _1 _ / _ 2_.

Лучше всего использовать OUTPUT parameter в вашей хранимой процедуре.

Представьте себе сценарий, в котором необходимо прочитать StatusCode из stored procedure:

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

Наконец, выполните ExecuteSqlRawAsync с необходимыми параметрами для вашего SP.

Где @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 с разными типами данных. Вам нужно только следовать той же схеме.

person Volkan Güven    schedule 28.09.2020

Чтобы расширить ответ @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;
    }
}
person BusyBee    schedule 07.06.2020

Альтернативный способ, который не потребует от вас изменения SP: используйте метод расширения FromSqlRaw для DbSet, чтобы получить результаты в 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")});
        
person ilker    schedule 05.03.2021