¿Cómo realizo una inserción y devuelvo la identidad insertada con Dapper?


170

¿Cómo realizo una inserción en la base de datos y devuelvo la identidad insertada con Dapper?

He intentado algo como esto:

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();

Pero no funcionó.

@Marc Gravell gracias, por la respuesta. He intentado su solución pero, a continuación, sigue el mismo rastro de excepción.

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

Respuestas:


287

Es compatible con los parámetros de entrada / salida (incluido el RETURNvalor) si lo usa DynamicParameters, pero en este caso la opción más simple es simplemente:

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

Tenga en cuenta que en las versiones más recientes de SQL Server puede usar la OUTPUTcláusula:

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

11
@ppiotrowicz hmmm ... maldición SCOPEIDENTITY va a volver numeric, ¿eh? Tal vez use su código original y select @id? (esto solo agrega un reparto). Haré una nota para asegurarme de que esto funcione automáticamente en futuras compilaciones aptas. Otra opción por ahora es select cast(SCOPE_IDENTITY() as int), de nuevo, un poco feo. Arreglaré esto.
Marc Gravell

2
@MarcGravell: ¡Guau! ¡Genial Marc, esa es buena! No me di cuenta de que el scope_identitytipo de retorno es numeric(38,0). +1 un muy buen hallazgo. Nunca lo pensé realmente y estoy seguro de que no soy el único.
Robert Koritnik el

55
Oye, esta respuesta es el éxito número uno para recuperar un valor de identidad de una consulta rápida. Usted mencionó que esto mejora enormemente cuando se vincula a un objeto; ¿puedes editar y dar una actualización de cómo lo harías ahora? Verifiqué las revisiones en el archivo de Pruebas en github cerca de su comentario del 26 de noviembre de 2012, pero no veo nada relacionado con la pregunta: / Supongo Query<foo>que inserta valores y luego selecciona * donde id = SCOPE_IDENTITY ().

2
@Xerxes, ¿qué te hace pensar que esto viola CQS? CQS no se trata de si una operación SQL devuelve una cuadrícula. Este es un comando, puro y simple. Esto no es una consulta en términos de CQS, a pesar de usar la palabra Query.
Marc Gravell

3
Nitpicky, pero en lugar de usar Queryy obtener el primer valor de la colección devuelta, creo que ExecuteScalar<T>tiene más sentido en este caso, ya que a lo sumo normalmente se devuelve un valor.
Peter Majeed

53

KB: 2019779 , "Puede recibir valores incorrectos al usar SCOPE_IDENTITY () y @@ IDENTITY", la cláusula OUTPUT es el mecanismo más seguro:

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();

14
Para su información, esto puede ser más lento que usar SCOPE_IDENTITY y se corrigió en la actualización # 5 del SQL Server 2008 R2 Service Pack 1.
Michael Silver

2
@MichaelSilver ¿recomienda usar SCOPE_IDENTITY o @@ IDENTITY antes que OUTPUT ? KB: 2019779 fue FIJO ?
Kiquenet

1
@Kiquenet, si escribiera el código en una base de datos que no se corrigió, probablemente usaría la cláusula OUTPUT solo para asegurarme de que funciona como se esperaba.
Michael Silver

1
@esto funciona muy bien para insertar un solo registro, pero si paso en una colección obtengoAn enumerable sequence of parameters (arrays, lists, etc) is not allowed in this context
MaYaN

44

Una respuesta tardía, pero aquí hay una alternativa a las SCOPE_IDENTITY()respuestas que terminamos usando: SALIDA INSERTADA

Devolver solo ID del objeto insertado:

Le permite obtener todos o algunos atributos de la fila insertada:

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

Devolver objeto insertado con ID:

Si quisieras, podrías obtener Phonee Emailincluso toda la fila insertada:

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

Además, con esto puede devolver datos de filas eliminadas o actualizadas . Solo tenga cuidado si está utilizando disparadores porque (del enlace mencionado anteriormente):

Las columnas devueltas desde OUTPUT reflejan los datos tal como están después de que se haya completado la instrucción INSERT, UPDATE o DELETE pero antes de que se ejecuten los desencadenantes.

Para los desencadenadores INSTEAD OF, los resultados devueltos se generan como si realmente hubiera ocurrido INSERTAR, ACTUALIZAR o ELIMINAR, incluso si no se producen modificaciones como resultado de la operación del desencadenante. Si se usa una declaración que incluye una cláusula OUTPUT dentro del cuerpo de un desencadenador, se deben usar alias de tabla para hacer referencia a las tablas insertadas y eliminadas del disparador para evitar duplicar las referencias de columna con las tablas INSERTED y DELETED asociadas con OUTPUT.

Más sobre esto en los documentos: enlace


1
Objeto @Kiquenet TransactionScope para usar con la consulta. Se puede encontrar más aquí: dapper-tutorial.net/transaction y aquí: stackoverflow.com/questions/10363933/…
Tadija Bagarić

¿Podemos usar 'ExecuteScalarAsync <int>' aquí en lugar de 'QuerySingle <int>'?
Ebleme

6

La InvalidCastException que está recibiendo se debe a que SCOPE_IDENTITY es un decimal (38,0) .

Puede devolverlo como int lanzándolo de la siguiente manera:

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

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

4

No estoy seguro de si fue porque estoy trabajando con SQL 2000 o no, pero tuve que hacer esto para que funcione.

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();

2
Pruebe el <code> select cast (SCOPE_IDENTITY () as int) </code> y también debería funcionar en 2000.
David Aleu

lo intentaste select cast(SCOPE_IDENTITY() as int)?
Kiquenet

1

Hay una gran biblioteca para hacerte la vida más fácil Dapper.Contrib.Extensions. Después de incluir esto, simplemente puede escribir:

public int Add(Transaction transaction)
{
        using (IDbConnection db = Connection)
        {
                return (int)db.Insert(transaction);
        }
}

0

Si está utilizando 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));
        }
    }

¿Qué es Dapper.SimpleSave?
Kiquenet

@Kirquenet, usé Dapper, Dapper.SimpleCRUD, Dapper.SimpleCRUD.ModelGenerator, Dapper.SimpleLoad y Dapper.SimpleSave en un proyecto en el que trabajé hace un tiempo. Los agregué a través de nuGet importaciones. Los combiné con una plantilla T4 para andamiar todo el DAO de mi sitio. github.com/Paymentsense/Dapper.SimpleSave github.com/Paymentsense/Dapper.SimpleLoad
Lodlaiden
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.