Como faço para inserir e devolver a identidade inserida com o Dapper?

170

Como executo uma inserção no banco de dados e retorno a identidade inserida com o Dapper?

Eu tentei algo como isto:

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

Mas não deu certo.

@ Marc Gravell obrigado, pela resposta. Eu tentei sua solução, mas o mesmo rastreamento de exceção ainda está abaixo

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
ppiotrowicz
fonte

Respostas:

286

Ele suporta parâmetros de entrada / saída (incluindo RETURNvalor) se você usar DynamicParameters, mas neste caso a opção mais simples é simplesmente:

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

Observe que nas versões mais recentes do SQL Server você pode usar a OUTPUTcláusula:

var id = connection.QuerySingle<int>( @"
INSERT INTO [MyTable] ([Stuff])
OUTPUT INSERTED.Id
VALUES (@Stuff);", new { Stuff = mystuff});
Marc Gravell
fonte
11
@ppiotrowicz hmmm .... danado SCOPEIDENTITY vai voltar numeric, não é? Talvez use seu código original e select @id? (isso apenas adiciona um elenco). Farei uma anotação para garantir que isso funcione automaticamente em futuras compilações dapper. Outra opção por enquanto é select cast(SCOPE_IDENTITY() as int)- novamente, um pouco feia. Eu vou consertar isso.
Marc Gravell
2
@MarcGravell: Uau! Ótimo Marc, é bom! Eu não sabia que esse scope_identitytipo de retorno é numeric(38,0). Marcar com +1 uma descoberta muito boa. Nunca pensei nisso realmente e tenho certeza de que não sou o único.
Robert Koritnik
5
Ei, esta resposta é o hit número um para recuperar um valor de identidade de uma consulta dapper. Você mencionou que isso é bastante aprimorado ao vincular a um objeto; você pode editar e atualizar como você faria isso agora? Eu verifiquei as revisões no arquivo Tests no github, perto do seu comentário em Nov26'12, mas não vejo nada relacionado à pergunta: / Minha suposição é Query<foo>que insira valores e selecione * where id = SCOPE_IDENTITY ().
2
@Xerxes, o que faz você pensar que isso viola o CQS? O CQS não se trata de uma operação SQL retornar uma grade. Este é um comando, puro e simples. Esta não é uma consulta em termos de CQS, apesar de usar a palavra Query.
Marc Gravell
3
Nitpicky, mas em vez de usar Querye obter o primeiro valor da coleção retornada, acho que ExecuteScalar<T>faz mais sentido nesse caso, pois no máximo um valor é normalmente retornado.
precisa
53

KB: 2019779 , "Você pode receber valores incorretos ao usar SCOPE_IDENTITY () e @@ IDENTITY". A cláusula OUTPUT é o mecanismo mais 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();
jww
fonte
14
FYI, este pode ser mais lenta do que usar SCOPE_IDENTITY e foi corrigido na atualização # 5 para SQL Server 2008 R2 Service Pack 1.
Michael Silver
2
@MichaelSilver, você recomenda o uso de SCOPE_IDENTITY ou @@ IDENTITY antes de OUTPUT ? KB: 2019779 foi CORRIGIDO ?
Kiquenet
1
@ Kiquenet, se eu estivesse escrevendo o código em um banco de dados que não foi corrigido, provavelmente usaria a cláusula OUTPUT apenas para garantir que funcione conforme o esperado.
Michael Silver
1
@ isso funciona muito bem para a inserção de um único registro, mas se eu passar em uma coleção, recebo #An enumerable sequence of parameters (arrays, lists, etc) is not allowed in this context
May
44

Uma resposta tardia, mas aqui está uma alternativa às SCOPE_IDENTITY()respostas que acabamos usando: OUTPUT INSERTED

Retorne apenas o ID do objeto inserido:

Permite obter todos ou alguns atributos da linha inserida:

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

Retornar objeto inserido com o ID:

Se você quisesse, poderia obter Phonee / Emailou até toda a linha inserida:

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

Além disso, com isso, você pode retornar dados de linhas excluídas ou atualizadas . Apenas tome cuidado se você estiver usando gatilhos, porque:

As colunas retornadas de OUTPUT refletem os dados como estão após a conclusão da instrução INSERT, UPDATE ou DELETE, mas antes da execução dos acionadores.

Para acionadores INSTEAD OF, os resultados retornados são gerados como se INSERT, UPDATE ou DELETE realmente tivessem ocorrido, mesmo se nenhuma modificação ocorrer como resultado da operação do acionador. Se uma instrução que inclui uma cláusula OUTPUT for usada dentro do corpo de um acionador, os aliases da tabela deverão ser usados ​​para referenciar as tabelas inseridas e excluídas do acionador, para evitar duplicar as referências de coluna nas tabelas INSERTED e DELETED associadas a OUTPUT.

Mais informações nos documentos: link

Tadija Bagarić
fonte
1
Objeto @Kiquenet TransactionScope a ser usado com a consulta. Mais pode ser encontrado aqui: dapper-tutorial.net/transaction e aqui: stackoverflow.com/questions/10363933/…
Tadija Bagarić
Podemos usar 'ExecuteScalarAsync <int>' aqui em vez de 'QuerySingle <int>'?
Ebleme
6

A InvalidCastException que você está recebendo deve-se ao fato de SCOPE_IDENTITY ser um decimal (38,0) .

Você pode devolvê-lo como um int, lançando-o da seguinte maneira:

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

int id = connection.Query<int>(sql, new { Stuff = mystuff}).Single();
bpruitt-goddard
fonte
4

Não tenho certeza se foi porque estou trabalhando com o SQL 2000 ou não, mas tive que fazer isso para fazê-lo funcionar.

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();
mytydev
fonte
2
Experimente o <code> select cast (SCOPE_IDENTITY () como int) </code> e deve funcionar em 2000 também.
David Aleu
você tentou select cast(SCOPE_IDENTITY() as int)?
30717
1

Existe uma ótima biblioteca para facilitar sua vida Dapper.Contrib.Extensions. Depois de incluir isso, você pode simplesmente escrever:

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

Se você estiver usando o 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));
        }
    }
Lodlaiden
fonte
O que é o Dapper.SimpleSave?
Kiquenet 03/06
@Kirquenet, usei Dapper, Dapper.SimpleCRUD, Dapper.SimpleCRUD.ModelGenerator, Dapper.SimpleLoad e Dapper.SimpleSave em um projeto em que trabalhei há algum tempo. Eu os adicionei via importações nuGet. Combinei-os com um modelo T4 para montar todo o DAO do meu site. github.com/Paymentsense/Dapper.SimpleSave github.com/Paymentsense/Dapper.SimpleLoad
Lodlaiden