如何使用 Dapper 执行插入并返回插入的标识?

Posted

技术标签:

【中文标题】如何使用 Dapper 执行插入并返回插入的标识?【英文标题】:How do I perform an insert and return inserted identity with Dapper? 【发布时间】:2012-01-06 09:34:13 【问题描述】:

如何使用 Dapper 执行插入数据库并返回插入的身份?

我尝试过这样的事情:

string sql = "DECLARE @ID int; " +
             "INSERT INTO [MyTable] ([Stuff]) VALUES (@Stuff); " +
             "SELECT @ID = SCOPE_IDENTITY()";

var id = connection.Query<int>(sql, new  Stuff = mystuff).First();

但它没有用。

@Marc Gravell 感谢您的回复。 我已经尝试了您的解决方案,但是下面仍然是相同的异常跟踪

System.InvalidCastException: Specified cast is not valid

at Dapper.SqlMapper.<QueryInternal>d__a`1.MoveNext() in (snip)\Dapper\SqlMapper.cs:line 610
at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
at Dapper.SqlMapper.Query[T](IDbConnection cnn, String sql, Object param, IDbTransaction transaction, Boolean buffered, Nullable`1 commandTimeout, Nullable`1 commandType) in (snip)\Dapper\SqlMapper.cs:line 538
at Dapper.SqlMapper.Query[T](IDbConnection cnn, String sql, Object param) in (snip)\Dapper\SqlMapper.cs:line 456

【问题讨论】:

【参考方案1】:

如果您使用DynamicParameters,它确实支持输入/输出参数(包括RETURN 值),但在这种情况下,更简单的选项很简单:

var id = connection.QuerySingle<int>( @"
INSERT INTO [MyTable] ([Stuff]) VALUES (@Stuff);
SELECT CAST(SCOPE_IDENTITY() as int)", new  Stuff = mystuff);

请注意,在较新版本的 SQL Server (2005+) 上,您可以使用 OUTPUT 子句:

var id = connection.QuerySingle<int>( @"
INSERT INTO [MyTable] ([Stuff])
OUTPUT INSERTED.Id
VALUES (@Stuff);", new  Stuff = mystuff);

【讨论】:

@ppiotrowicz 嗯....该死的 SCOPEIDENTITY 将返回numeric,是吗?也许使用您的原始代码和 select @id ? (这只是增加了演员表)。我会做一个说明,以确保它在未来的 dapper 构建中自动工作。现在的另一个选择是select cast(SCOPE_IDENTITY() as int) - 再次,有点难看。我会解决这个问题。 @MarcGravell:哇!伟大的马克,这是一个很好的!我没有意识到scope_identity 返回类型是numeric(38,0)。 +1 一个非常好的发现。从来没有想过真的,我敢肯定我不是唯一一个。 嘿,这个答案是从 dapper 查询中获取身份值的第一选择。您提到绑定到对象时,这得到了极大的改进;您可以编辑并提供有关您现在如何执行此操作的更新吗?我在 12 年 12 月 26 日评论附近的 github 上的测试文件中检查了修订,但没有看到与该问题相关的任何内容:/ 我的假设是 Query&lt;foo&gt; 插入值然后选择 * where id = SCOPE_IDENTITY()。 @Xerxes 是什么让您认为这违反了 CQS? CQS 与 SQL 操作是否返回网格无关。这是一个命令,纯粹而简单。这不是 CQS 术语中的查询,尽管使用了 Query 这个词。 Nitpicky,但不是使用Query 并从返回的集合中获取第一个值,我认为ExecuteScalar&lt;T&gt; 在这种情况下更有意义,因为通常最多返回一个值。【参考方案2】:

KB:2019779,"使用 SCOPE_IDENTITY() 和 @@IDENTITY 时可能会收到不正确的值", OUTPUT 子句是最安全的机制:

string sql = @"
DECLARE @InsertedRows AS TABLE (Id int);
INSERT INTO [MyTable] ([Stuff]) OUTPUT Inserted.Id INTO @InsertedRows
VALUES (@Stuff);
SELECT Id FROM @InsertedRows";

var id = connection.Query<int>(sql, new  Stuff = mystuff).Single();

【讨论】:

仅供参考,这可能比使用 SCOPE_IDENTITY 慢,并且已在 SQL Server 2008 R2 Service Pack 1 的更新 #5 中修复。 @MichaelSilver 你推荐在 SCOPE_IDENTITY@@IDENTITY 之前使用 @@IDENTITY b>输出 ? KB:2019779 是否已修复 @Kiquenet,如果我正在针对未修复的数据库编写代码,我可能会使用 OUTPUT 子句以确保它按预期工作。 @this 非常适合插入单条记录,但如果我传入一个集合,我会得到 An enumerable sequence of parameters (arrays, lists, etc) is not allowed in this context【参考方案3】:

一个较晚的答案,但这是我们最终使用的SCOPE_IDENTITY() 答案的替代方法:OUTPUT INSERTED

只返回插入对象的 ID:

它允许您获取插入行的全部或部分属性:

string insertUserSql = @"INSERT INTO dbo.[User](Username, Phone, Email)
                        OUTPUT INSERTED.[Id]
                        VALUES(@Username, @Phone, @Email);";

int newUserId = conn.QuerySingle<int>(
                                insertUserSql,
                                new
                                
                                    Username = "lorem ipsum",
                                    Phone = "555-123",
                                    Email = "lorem ipsum"
                                ,
                                tran);

返回插入的具有 ID 的对象:

如果你愿意,你可以得到 PhoneEmail 甚至整个插入的行:

string insertUserSql = @"INSERT INTO dbo.[User](Username, Phone, Email)
                        OUTPUT INSERTED.*
                        VALUES(@Username, @Phone, @Email);";

User newUser = conn.QuerySingle<User>(
                                insertUserSql,
                                new
                                
                                    Username = "lorem ipsum",
                                    Phone = "555-123",
                                    Email = "lorem ipsum"
                                ,
                                tran);

此外,您还可以返回已删除更新行的数据。如果您使用触发器,请小心,因为(来自前面提到的链接):

从 OUTPUT 返回的列反映了在 INSERT、UPDATE 或 DELETE 语句已完成但在触发器之前 被执行。

对于 INSTEAD OF 触发器,生成的返回结果就像 实际发生了 INSERT、UPDATE 或 DELETE,即使没有 修改发生作为触发操作的结果。如果一个 包含 OUTPUT 子句的语句用于 触发器,必须使用表别名来引用插入的触发器 并删除表以避免与 与 OUTPUT 关联的 INSERTED 和 DELETED 表。

更多信息请参阅文档:link

【讨论】:

@Kiquenet TransactionScope 对象用于查询。更多可以在这里找到:dapper-tutorial.net/transaction 和这里:***.com/questions/10363933/… 我们可以在这里使用 'ExecuteScalarAsync' 而不是 'QuerySingle' 吗?【参考方案4】:

您收到的 InvalidCastException 是由于 SCOPE_IDENTITY 是 Decimal(38,0)。

您可以通过将其转换为 int 来返回它,如下所示:

string sql = @"
INSERT INTO [MyTable] ([Stuff]) VALUES (@Stuff);
SELECT CAST(SCOPE_IDENTITY() AS INT)";

int id = connection.Query<int>(sql, new  Stuff = mystuff).Single();

【讨论】:

【参考方案5】:

不确定是不是因为我正在使用 SQL 2000,但我必须这样做才能使其正常工作。

string sql = "DECLARE @ID int; " +
             "INSERT INTO [MyTable] ([Stuff]) VALUES (@Stuff); " +
             "SET @ID = SCOPE_IDENTITY(); " +
             "SELECT @ID";

var id = connection.Query<int>(sql, new  Stuff = mystuff).Single();

【讨论】:

试试 select cast(SCOPE_IDENTITY() as int) 它应该在 2000 年也可以工作。 你试过select cast(SCOPE_IDENTITY() as int)吗?【参考方案6】:

有一个很棒的库可以让你的生活更轻松 Dapper.Contrib.Extensions。包含此内容后,您可以编写:

public int Add(Transaction transaction)

        using (IDbConnection db = Connection)
        
                return (int)db.Insert(transaction);
        

【讨论】:

如果您输入了交易内容,这实际上可能很有用。【参考方案7】:

我看到了 sql server 的答案,这里是使用事务的 mysql


    Dim sql As String = "INSERT INTO Empleado (nombres, apepaterno, apematerno, direccion, colonia, cp, municipio, estado, tel, cel, correo, idrol, relojchecadorid, relojchecadorid2, `activo`,`extras`,`rfc`,`nss`,`curp`,`imagen`,sueldoXHra, IMSSCotiza, thumb) VALUES (@nombres, @apepaterno, @apematerno, @direccion, @colonia, @cp, @municipio, @estado, @tel, @cel, @correo, @idrol, @relojchecadorid, @relojchecadorid2, @activo, @extras, @rfc, @nss, @curp, @imagen,@sueldoXHra,@IMSSCotiza, @thumb)"
    
            Using connection As IDbConnection = New MySqlConnection(getConnectionString())
                connection.Open()
                Using transaction = connection.BeginTransaction
                    Dim res = connection.Execute(sql, New With reg.nombres, reg.apepaterno, reg.apematerno, reg.direccion, reg.colonia, reg.cp, reg.municipio, reg.estado, reg.tel, reg.cel, reg.correo, reg.idrol, reg.relojchecadorid, reg.relojchecadorid2, reg.activo, reg.extras, reg.rfc, reg.nss, reg.curp, reg.imagen, reg.thumb, reg.sueldoXHra, reg.IMSSCotiza, commandTimeout:=180, transaction:=transaction)
                    lastInsertedId = connection.ExecuteScalar("SELECT LAST_INSERT_ID();", transaction:=transaction)
                    If res > 0 Then 
transaction.Commit()
return true
end if
                    
                End Using
            End Using

【讨论】:

【参考方案8】:

如果您使用的是 Dapper.SimpleSave:

 //no safety checks
 public static int Create<T>(object param)
    
        using (SqlConnection conn = new SqlConnection(GetConnectionString()))
        
            conn.Open();
            conn.Create<T>((T)param);
            return (int) (((T)param).GetType().GetProperties().Where(
                    x => x.CustomAttributes.Where(
                        y=>y.AttributeType.GetType() == typeof(Dapper.SimpleSave.PrimaryKeyAttribute).GetType()).Count()==1).First().GetValue(param));
        
    

【讨论】:

什么是 Dapper.SimpleSave ? @Kirquenet,我在不久前从事的一个项目中使用了 Dapper、Dapper.SimpleCRUD、Dapper.SimpleCRUD.ModelGenerator、Dapper.SimpleLoad 和 Dapper.SimpleSave。我通过 nuGet 导入添加了它们。我将它们与 T4 模板结合起来,为我的站点搭建了所有 DAO。 github.com/Paymentsense/Dapper.SimpleSavegithub.com/Paymentsense/Dapper.SimpleLoad【参考方案9】:

我使用 .net core 3.1 和 postgres 12.3。基于 Tadija Bagarić 的回答,我最终得到:

using (var connection = new NpgsqlConnection(AppConfig.CommentFilesConnection))
        

            string insertUserSql = @"INSERT INTO mytable(comment_id,filename,content)
                    VALUES( @commentId, @filename, @content) returning id;";

            int newUserId = connection.QuerySingle<int>(
                                            insertUserSql,
                                            new
                                            
                                                commentId = 1,
                                                filename = "foobar!",
                                                content = "content"
                                            
                                            );

          


        

其中 AppConfig 是我自己的类,它只是为我的连接详细信息设置一个字符串。这是在 Startup.cs ConfigureServices 方法中设置的。

【讨论】:

以上是关于如何使用 Dapper 执行插入并返回插入的标识?的主要内容,如果未能解决你的问题,请参考以下文章

在控制器中为 INSERT 获取标识值(执行插入命令并在 Sql 中返回插入的 Id)

当主键列不是标识列时如何插入数据?

如何使用 Dapper.NET 将 C# 列表插入数据库

C# Web API 使用 Dapper 使用存储过程插入数据 - 无法获取插入的记录 ID

Dapper.SimpleCRUD 插入/更新/获取失败并显示消息“实体必须具有至少一个 [Key] 属性”

如何在 Dapper.NET 中使用事务?