[英]Entity Framework Core 3.1 Return value (int) from stored procedure
这返回-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");
}
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();
}
您需要创建并使用输出参数,在您的情况下:
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");
}
添加AlbertK已经编写的内容,但使用ExecuteSqlRaw
/ ExecuteSqlRawAsync
。
最好的办法是在您的存储过程中使用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# 代码中,您需要使用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
。 您只需要遵循相同的模式。
要扩展@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;
}
}
不需要您修改 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")});
我知道这真的很旧,但我有一些存储过程使用以下作为 proc 中的最后一行。
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;
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.