如何获取使用 EF Core 从存储过程中插入的记录的 id 值

Posted

技术标签:

【中文标题】如何获取使用 EF Core 从存储过程中插入的记录的 id 值【英文标题】:How do you get the id value of record inserted from a stored procedure using EF Core 【发布时间】:2021-10-06 08:06:48 【问题描述】:

我正在使用 EF Core 5,但无法取回我插入表中的记录的 userId。我尝试了不同的方法来做到这一点,但我没有运气。我得到的只是一个-1,我知道它是针对受影响的行。我似乎无法检索声明为存储过程的 OUTPUT 参数的 UserID。

存储过程:

ALTER PROCEDURE [dbo].[User.Save]
    (@Return_Code  INT OUTPUT,
     @Error_Description_Code NVARCHAR(50) OUTPUT,
     @idCallerSite INT = 0, --default if not specified
     @idCaller     INT = 0,
     @idUser       INT OUTPUT, 
     @firstName     NVARCHAR(50),
     @middleName    NVARCHAR(50),
     @lastName      NVARCHAR(50),
     @preferredName NVARCHAR(50),
     @username      NVARCHAR(512),
     @password      NVARCHAR(512),
     @isActive      BIT,
     @isSalesperson BIT,
     @userPreferences NVARCHAR(512),
     @phoneNumber     NVARCHAR(25),
     @phoneFax        NVARCHAR(25),
     @email           NVARCHAR(255),
     @photo           NVARCHAR(255),
     @lastEditedBy    INT)
AS
BEGIN
    SET NOCOUNT ON

    DECLARE @idPermission INT  -- define the permission required to perform this function
    SET @idPermission = 141
    
    /* 
    validate uniqueness
    */
    IF (SELECT COUNT(1)
        FROM Users
        WHERE SiteID = @idCallerSite
          AND (username = @username)
          AND (@idUser IS NULL OR @idUser <> UserID)) > 0
    BEGIN
        SET @Return_Code = 2
        SET @Error_Description_Code = 'UserSave_UserNotUnique'
        RETURN 1 
    END

    /*
    insert/update the user
    */

    IF (@idUser = 0 OR @idUser IS NULL)
    BEGIN
        -- insert the new user
        INSERT INTO Users (SiteID, firstName, middleName, lastName,
                           preferredName, username,
                           HashedPassword, isActive, IsSalesperson,
                           UserPreferences, PhoneNumber, FaxNumber,
                           EmailAddress, Photo, LastEditedBy)
        VALUES (@idCallerSite, @firstName, @middleName, @lastName,
                @preferredName, @username, 
                dbo.GetHashedString('SHA1', @password), @isActive, @isSalesperson,
                @userPreferences, @phoneNumber, @phoneFax,
                @email, @photo, @lastEditedBy)

        /*
        get the new user's id
        */

        SET @idUser = SCOPE_IDENTITY()
        SELECT @idUser

        -- return successfully
        SET @Return_Code = 0 --(0 is 'success')
        SET @Error_Description_Code = ''
    END

控制器调用

var retCode = new SqlParameter  ParameterName = "@Return_Code", Value = null, SqlDbType = (SqlDbType.Int), Direction = System.Data.ParameterDirection.Output ;

var errorDesc = new SqlParameter  ParameterName = "@Error_Description_Code", Value = null, SqlDbType = SqlDbType.NVarChar, Size = 50, Direction = System.Data.ParameterDirection.Output ;
var idCallerSite = new SqlParameter  ParameterName = "@idCallerSite", Value = 1, SqlDbType = (SqlDbType.Int), Direction = System.Data.ParameterDirection.Input ;
var idCaller = new SqlParameter  ParameterName = "@idCaller", Value = 0, SqlDbType = (SqlDbType.Int), Direction = System.Data.ParameterDirection.Input ;
var idUser = new SqlParameter  ParameterName = "@idUser", Value = user.UserID, SqlDbType = (SqlDbType.Int), Direction = System.Data.ParameterDirection.Output ;
var fName = new SqlParameter  ParameterName = "@firstName", Value = user.FirstName.ToString(), SqlDbType = SqlDbType.NVarChar, Size = 50, Direction = System.Data.ParameterDirection.Input ;
var mName = new SqlParameter  ParameterName = "@middleName", Value = user.MiddleName.ToString(), SqlDbType = SqlDbType.NVarChar, Size = 50, Direction = System.Data.ParameterDirection.Input ;
var lName = new SqlParameter  ParameterName = "@lastName", Value = user.LastName.ToString(), SqlDbType = SqlDbType.NVarChar, Size = 50, Direction = System.Data.ParameterDirection.Input ;
var pName = new SqlParameter  ParameterName = "@preferredName", Value = user.PreferredName.ToString(), SqlDbType = SqlDbType.NVarChar, Size = 50, Direction = System.Data.ParameterDirection.Input ;
var userName = new SqlParameter  ParameterName = "@username", Value = user.UserName.ToString(), SqlDbType = SqlDbType.NVarChar, Size = 512, Direction = System.Data.ParameterDirection.Input ;
var password = new SqlParameter  ParameterName = "@password", Value = user.HashedPassword.ToString(), SqlDbType = SqlDbType.NVarChar, Size = 512, Direction = System.Data.ParameterDirection.Input ;
var isActive = new SqlParameter  ParameterName = "@isActive", Value = user.IsActive, SqlDbType = (SqlDbType.Bit), Direction = System.Data.ParameterDirection.Input ;
var isSalesperson = new SqlParameter  ParameterName = "@isSalesperson", Value = user.IsSalesperson, SqlDbType = (SqlDbType.Bit), Direction = System.Data.ParameterDirection.Input ;
var userPref = new SqlParameter  ParameterName = "@userPreferences", Value = user.UserPreferences.ToString(), SqlDbType = SqlDbType.NVarChar, Size = 512, Direction = System.Data.ParameterDirection.Input ;
var phone = new SqlParameter  ParameterName = "@phoneNumber", Value = user.PhoneNumber.ToString(), SqlDbType = SqlDbType.NVarChar, Size = 25, Direction = System.Data.ParameterDirection.Input ;
var fax = new SqlParameter  ParameterName = "@phoneFax", Value = user.FaxNumber.ToString(), SqlDbType = SqlDbType.NVarChar, Size = 25, Direction = System.Data.ParameterDirection.Input ;
var email = new SqlParameter  ParameterName = "@email", Value = user.EmailAddress.ToString(), SqlDbType = SqlDbType.NVarChar, Size = 255, Direction = System.Data.ParameterDirection.Input ;
var photo = new SqlParameter  ParameterName = "@photo", Value = user.Photo.ToString(), SqlDbType = SqlDbType.NVarChar, Size = 255, Direction = System.Data.ParameterDirection.Input ;
var lastEditedBy = new SqlParameter  ParameterName = "@lastEditedBy", Value = user.LastEditedBy, SqlDbType = (SqlDbType.Int), Direction = System.Data.ParameterDirection.Input ;

var _user = await _db.Database.ExecuteSqlRawAsync("EXEC [dbo].[User.Save] @Return_Code, @Error_Description_Code, @idCallerSite, @idCaller, @idUser OUT, @firstName, @middleName, @lastName, @preferredName, @username, @password, @isActive, @isSalesperson, @userPreferences, @phoneNumber, @phoneFax, @email, @photo, @lastEditedBy", retCode, errorDesc, idCallerSite, idCaller, idUser, fName, mName, lName, pName, userName, password, isActive, isSalesperson, userPref, phone, fax, email, photo, lastEditedBy);

return Ok(_user);

我使用.FromSql 得到以下错误。

var _user = _db.Users.FromSqlRaw("EXEC [dbo].[User.Save] @Return_Code, @Error_Description_Code, @idCallerSite, @idCaller, @idUser OUT, @firstName, @middleName, @lastName, @preferredName, @username, @password, @isActive, @isSalesperson, @userPreferences, @phoneNumber, @phoneFax, @email, @photo, @lastEditedBy", retCode, errorDesc, idCallerSite, idCaller, idUser, fName, mName, lName, pName, userName, password, isActive, isSalesperson, userPref, phone, fax, email, photo, lastEditedBy);

System.InvalidOperationException:“FromSql”操作的结果中不存在所需的列“UserID”。

在 Microsoft.EntityFrameworkCore.Query.Internal.FromSqlQueryingEnumerable1.BuildIndexMap(IReadOnlyList1 columnNames, DbDataReader dataReader) 在 Microsoft.EntityFrameworkCore.Query.Internal.FromSqlQueryingEnumerable1.AsyncEnumerator.InitializeReaderAsync(DbContext _, Boolean result, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func4 操作,Func4 verifySucceeded, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.Query.Internal.FromSqlQueryingEnumerable1.AsyncEnumerator.MoveNextAsync() 在 Microsoft.AspNetCore.Mvc.Infrastructure.AsyncEnumerableReader.ReadInternal[T](对象值) 在 Microsoft.AspNetCore.Mvc.Infrastructure.AsyncEnumerableReader.ReadInternal[T](对象值) 在 Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor.ExecuteAsyncEnumerable(ActionContext 上下文,ObjectResult 结果,对象 asyncEnumerable,Func`2 阅读器) 在 Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|29_0[TFilter,TFilterAsync](ResourceInvoker 调用程序,Task lastTask,State next,Scope 范围,Object state,Boolean isCompleted) 在 Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResultExecutedContextSealed 上下文) 在 Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext[TFilter,TFilterAsync](State& next, Scope& scope, Object& state, Boolean& isCompleted) 在 Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|27_0(ResourceInvoker 调用程序,任务 lastTask,下一个状态,作用域范围,对象状态,布尔 isCompleted) 在 Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|19_0(ResourceInvoker 调用程序,任务 lastTask,下一个状态,作用域范围,对象状态,布尔 isCompleted) 在 Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|17_0(ResourceInvoker 调用程序,任务任务,IDisposable 范围) 在 Microsoft.AspNetCore.Routing.EndpointMiddleware.g__AwaitRequestTask|6_0(端点端点、任务 requestTask、ILogger 记录器) 在 Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext 上下文) 在 Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext) 在 Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider) 在 Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext 上下文)

标题:

Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Connection: keep-alive
Content-Length: 301
Content-Type: application/json
Cookie: fblo_329420117573382=y
Host: localhost:62374
Referer: http://localhost:62374/swagger/index.html
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36
sec-ch-ua: " Not;A Brand";v="99", "Google Chrome";v="91", "Chromium";v="91"
sec-ch-ua-mobile: ?0
Origin: http://localhost:62374
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty

【问题讨论】:

改用FromSql()会有什么结果? 我收到以下错误 System.InvalidOperationException:“FromSql”操作的结果中不存在所需的列“UserID”。 请edit 您的问题包括您尝试使用FromSql() 以及您收到的完整错误消息。看起来您想将结果保存在具有属性/列UserID 的对象中,但是您有一个SELECT @idUser 语句,它将只返回一个整数值(并且您无论如何都想使用输出参数)。跨度> 【参考方案1】:

实体框架要求至少存在主键,所以你需要这样做

SET @idUser = SCOPE_IDENTITY();
SELECT UserID = @idUser;

但是,这只会为您提供一个字段,而不会为您提供任何其他列。要获取其他列,您只需 OUTPUT 一切(无需连接)

INSERT INTO Users (SiteID, firstName,
.........
OUTPUT inserted.ID, inserted.SiteID, inserted.firstName
.........
        VALUES (@idCallerSite, @firstName
.........

请注意,用于调用存储过程的语法并不理想,因为它依赖于参数位置而不是名称。改为使用EXEC [dbo].[User.Save] @Return_Code = @Return_Code OUTPUT, @Error_Description_Code = @Error_Description_Code, @idCallerSite = @idCallerSite ...,您还需要在输出参数上指定OUTPUT,如图所示。

【讨论】:

【参考方案2】:

只能使用输出参数idUser来获取记录id

_db.Database.ExecuteSqlRaw(....);

var id=0;

if (idUser.Value != DBNull.Value)
     
        id = (int) idUser.Value;
     
return Ok(id);

并修复存储过程。删除 SELECT @idUser,只留下

 SET @idUser = SCOPE_IDENTITY()

如果 sp 包含 SELECT Database.ExecuteSqlRaw 总是返回 -1;

【讨论】:

【参考方案3】:

我通过对所有字段执行 select 语句解决了这个问题,然后从结果集中提取了 UserID。感谢@Progman 的帮助。是你的建议帮助了我。

【讨论】:

以上是关于如何获取使用 EF Core 从存储过程中插入的记录的 id 值的主要内容,如果未能解决你的问题,请参考以下文章

EF Core 调用具有多个连接的存储过程并映射相关数据

如何通过 FromSqlRaw 在 EF Core 3.0 中调用存储过程

如何使用 EF Core 5 执行存储过程 ORACLE?

插入嵌套对象 EF Core 5

无法使用 EF Core 或 LinqToDb EF Core Tools 或 ADO.NET 运行任何存储过程创建脚本

如何使用 EntityFramework 调用存储过程?