Come eseguo un inserimento e restituisco l'identità inserita con Dapper?


170

Come eseguo un inserimento nel database e restituisco l'identità inserita con Dapper?

Ho provato qualcosa del genere:

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

Ma non ha funzionato.

@Marc Gravell grazie, per la risposta. Ho provato la tua soluzione, ma la stessa traccia delle eccezioni è sotto

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

Risposte:


287

Lo fa di supporto parametri di input / output (tra cui RETURNil valore) se si utilizza DynamicParameters, ma in questo caso l'opzione più semplice è semplicemente:

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

Si noti che nelle versioni più recenti di SQL Server è possibile utilizzare la OUTPUTclausola:

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

11
@ppiotrowicz hmmm .... maledetto SCOPEIDENTITY sta per tornare numeric, eh? Forse usi il tuo codice originale e select @id? (questo aggiunge solo un cast). Farò una nota per assicurarmi che funzioni automaticamente nelle future versioni dapper. Un'altra opzione per ora è select cast(SCOPE_IDENTITY() as int)- ancora una volta, un po 'brutta. Risolverò questo.
Marc Gravell

2
@MarcGravell: Wow! Grande Marc, questo è buono! Non mi rendevo conto che il scope_identitytipo di ritorno è numeric(38,0). +1 una scoperta davvero buona. Non ci ho mai pensato davvero e sono sicuro di non essere l'unico.
Robert Koritnik,

5
Ehi, questa risposta è l'hit numero uno per ottenere un valore di identità da una query dapper. Hai detto che questo è enormemente migliorato quando si lega a un oggetto; puoi modificare e dare un aggiornamento su come lo faresti ora? Ho controllato le revisioni nel file Test su github vicino al tuo commento del Nov26'12 ma non vedo nulla di correlato alla domanda: / La mia ipotesi è diQuery<foo> che inserisca valori e selezioni * dove id = SCOPE_IDENTITY ().

2
@Xerxes cosa ti fa pensare che questo violi CQS? CQS non riguarda se un'operazione SQL restituisce una griglia. Questo è un comando, puro e semplice. Questa non è una query in termini CQS, nonostante usi la parola Query.
Marc Gravell

3
Nitpicky, ma piuttosto che usare Querye ottenere il primo valore dalla raccolta restituita, penso ExecuteScalar<T>abbia più senso in questo caso poiché al massimo viene normalmente restituito un valore.
Peter Majeed,

53

KB: 2019779 , "È possibile che vengano visualizzati valori errati quando si utilizza SCOPE_IDENTITY () e @@ IDENTITY", la clausola OUTPUT è il meccanismo più sicuro:

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
Cordiali saluti, questo potrebbe essere più lento rispetto all'utilizzo di SCOPE_IDENTITY ed è stato corretto nell'aggiornamento n. 5 a SQL Server 2008 R2 Service Pack 1.
Michael Silver,

2
@MichaelSilver mi consiglia di utilizzare SCOPE_IDENTITY o @@ IDENTITY prima di OUTPUT ? KB: 2019779 è stato FISSO ?
Kiquenet,

1
@Kiquenet, se stavo scrivendo il codice su un DB che non è stato corretto, probabilmente utilizzerei la clausola OUTPUT solo per essere sicuro che funzioni come previsto.
Michael Silver,

1
@questo funziona benissimo per l'inserimento di un singolo disco ma se passo in una raccolta ottengoAn enumerable sequence of parameters (arrays, lists, etc) is not allowed in this context
MaYaN

44

Una risposta tardiva, ma ecco un'alternativa alle SCOPE_IDENTITY()risposte che abbiamo finito per usare: OUTPUT INSERTED

Restituisce solo l'ID dell'oggetto inserito:

Ti permette di ottenere tutti o alcuni attributi della riga inserita:

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

Restituisce l'oggetto inserito con ID:

Se lo desideri, puoi ottenere Phonee / Emailo l'intera riga inserita:

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

Inoltre, con questo è possibile restituire i dati delle righe eliminate o aggiornate . Fai solo attenzione se stai usando i trigger perché (dal link menzionato prima):

Le colonne restituite da OUTPUT riflettono i dati così come sono dopo il completamento dell'istruzione INSERT, UPDATE o DELETE, ma prima dell'esecuzione dei trigger.

Per i trigger INSTEAD OF, i risultati restituiti vengono generati come se INSERT, UPDATE o DELETE si fossero effettivamente verificati, anche se non sono state apportate modifiche a seguito dell'operazione del trigger. Se un'istruzione che include una clausola OUTPUT viene utilizzata all'interno del corpo di un trigger, è necessario utilizzare gli alias di tabella per fare riferimento alle tabelle inserite ed eliminate dal trigger per evitare di duplicare i riferimenti di colonna con le tabelle INSERTED e DELETED associate a OUTPUT.

Maggiori informazioni nei documenti: link


1
Oggetto @Kiquenet TransactionScope da utilizzare con la query. Maggiori informazioni possono essere trovate qui: dapper-tutorial.net/transaction e qui: stackoverflow.com/questions/10363933/...
Tadija Bagarić

Possiamo usare 'ExecuteScalarAsync <int>' qui invece di 'QuerySingle <int>'?
Ebleme,

6

Il InvalidCastException che stai ricevendo è dovuto al fatto che SCOPE_IDENTITY è un decimale (38,0) .

Puoi restituirlo come int lanciandolo come segue:

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

Non sono sicuro se è perché sto lavorando su SQL 2000 oppure no, ma ho dovuto farlo per farlo funzionare.

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
Prova il <code> select cast (SCOPE_IDENTITY () come int) </code> e dovrebbe funzionare anche nel 2000.
David Aleu,

hai provato select cast(SCOPE_IDENTITY() as int)?
Kiquenet,

1

C'è una grande libreria per semplificarti la vita Dapper.Contrib.Extensions. Dopo aver incluso questo puoi semplicemente scrivere:

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

0

Se stai usando 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));
        }
    }

Che cos'è Dapper.SimpleSave?
Kiquenet,

@Kirquenet, ho usato Dapper, Dapper.SimpleCRUD, Dapper.SimpleCRUD.ModelGenerator, Dapper.SimpleLoad e Dapper.SimpleSave in un progetto su cui ho lavorato qualche tempo fa. Li ho aggiunti tramite le importazioni nuGet. Li ho combinati con un modello T4 per impilare tutto il DAO per il mio sito. github.com/Paymentsense/Dapper.SimpleSave github.com/Paymentsense/Dapper.SimpleLoad
Lodlaiden
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.