Comment effectuer une insertion et renvoyer une identité insérée avec Dapper?

170

Comment effectuer une insertion dans la base de données et renvoyer l'identité insérée avec Dapper?

J'ai essayé quelque chose comme ça:

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

Mais ça n'a pas marché.

@Marc Gravell merci, pour la réponse. J'ai essayé votre solution mais, toujours la même trace d'exception est ci-dessous

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
la source

Réponses:

287

Il prend en charge les paramètres d'entrée / sortie (y compris la RETURNvaleur) si vous utilisez DynamicParameters, mais dans ce cas, l'option la plus simple est simplement:

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

Notez que sur les versions plus récentes de SQL Server, vous pouvez utiliser la OUTPUTclause:

var id = connection.QuerySingle<int>( @"
INSERT INTO [MyTable] ([Stuff])
OUTPUT INSERTED.Id
VALUES (@Stuff);", new { Stuff = mystuff});
Marc Gravell
la source
11
@ppiotrowicz hmmm .... sacrément SCOPEIDENTITY va revenir numeric, hein? Peut-être utiliser votre code d'origine et select @id? (cela ajoute juste un casting). Je vais faire une note pour m'assurer que cela fonctionne automatiquement dans les futures versions dapper. Une autre option pour l'instant est select cast(SCOPE_IDENTITY() as int)- encore une fois, un peu moche. Je vais réparer ça.
Marc Gravell
2
@MarcGravell: Wow! Super Marc, c'est un bon! Je n'avais pas réalisé que le scope_identitytype de retour était numeric(38,0). +1 une très bonne trouvaille. Jamais vraiment pensé et je suis sûr que je ne suis pas le seul.
Robert Koritnik
5
Hé, cette réponse est le numéro un pour récupérer une valeur d'identité à partir d'une requête pimpante. Vous avez mentionné que cela est considérablement amélioré lors de la liaison à un objet; pouvez-vous modifier et donner une mise à jour sur la façon dont vous feriez cela maintenant? J'ai vérifié les révisions dans le fichier Tests sur github près de votre commentaire Nov26'12 mais je ne vois rien de lié à la question: / Mon hypothèse est Query<foo>que les valeurs insèrent puis sélectionne * où id = SCOPE_IDENTITY ().
2
@Xerxes qu'est-ce qui vous fait penser que cela enfreint le CQS? CQS ne consiste pas à savoir si une opération SQL renvoie une grille. C'est une commande pure et simple. Ce n'est pas une requête en termes CQS, malgré l'utilisation du mot Query.
Marc Gravell
3
Nitpicky, mais plutôt que d'utiliser Queryet d'obtenir la première valeur de la collection retournée, je pense que ExecuteScalar<T>cela a plus de sens dans ce cas, car au plus une valeur est normalement retournée.
Peter Majeed
53

KB: 2019779 , "Vous pouvez recevoir des valeurs incorrectes lors de l'utilisation de SCOPE_IDENTITY () et @@ IDENTITY", la clause OUTPUT est le mécanisme le plus sûr:

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
la source
14
Pour info, cela peut être plus lent que l'utilisation de SCOPE_IDENTITY et a été corrigé dans la mise à jour n ° 5 du Service Pack 1 de SQL Server 2008 R2
Michael Silver
2
@MichaelSilver recommandez-vous d'utiliser SCOPE_IDENTITY ou @@ IDENTITY avant OUTPUT ? KB: 2019779 a été FIXES ?
Kiquenet
1
@Kiquenet, si j'écrivais le code sur une base de données qui n'a pas été corrigée, j'utiliserais probablement la clause OUTPUT juste pour être sûr qu'elle fonctionne comme prévu.
Michael Silver
1
@this fonctionne très bien pour insérer un seul enregistrement, mais si je passe dans une collection, je reçoisAn enumerable sequence of parameters (arrays, lists, etc) is not allowed in this context
MaYaN
44

Une réponse tardive, mais voici une alternative aux SCOPE_IDENTITY()réponses que nous avons fini par utiliser: OUTPUT INSERTED

Renvoie uniquement l'ID de l'objet inséré:

Il vous permet d'obtenir tout ou partie des attributs de la ligne insérée:

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

Renvoyer l'objet inséré avec l'ID:

Si vous le souhaitez, vous pouvez obtenir Phoneet Emailou même toute la ligne insérée:

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

De plus, vous pouvez renvoyer les données des lignes supprimées ou mises à jour . Faites juste attention si vous utilisez des déclencheurs car (à partir du lien mentionné précédemment):

Les colonnes renvoyées par OUTPUT reflètent les données telles qu'elles sont après la fin de l'instruction INSERT, UPDATE ou DELETE, mais avant l'exécution des déclencheurs.

Pour les déclencheurs INSTEAD OF, les résultats renvoyés sont générés comme si INSERT, UPDATE ou DELETE s'était réellement produit, même si aucune modification n'a eu lieu à la suite de l'opération de déclenchement. Si une instruction qui inclut une clause OUTPUT est utilisée dans le corps d'un déclencheur, les alias de table doivent être utilisés pour référencer les tables insérées et supprimées du déclencheur afin d'éviter la duplication des références de colonne avec les tables INSERTED et DELETED associées à OUTPUT.

Plus d'informations dans la documentation: lien

Tadija Bagarić
la source
1
Objet @Kiquenet TransactionScope à utiliser avec la requête. Plus d'informations peuvent être trouvées ici: dapper-tutorial.net/transaction et ici: stackoverflow.com/questions/10363933/…
Tadija Bagarić
Pouvons-nous utiliser «ExecuteScalarAsync <int>» ici au lieu de «QuerySingle <int>»?
Ebleme
6

L'exception InvalidCastException que vous obtenez est due au fait que SCOPE_IDENTITY est un décimal (38,0) .

Vous pouvez le renvoyer en tant qu'int en le castant comme suit:

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
la source
4

Je ne sais pas si c'était parce que je travaille contre SQL 2000 ou non, mais je devais le faire pour le faire fonctionner.

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
la source
2
Essayez le <code> select cast (SCOPE_IDENTITY () as int) </code> et cela devrait également fonctionner en 2000.
David Aleu
avez-vous essayé select cast(SCOPE_IDENTITY() as int)?
Kiquenet
1

Il existe une excellente bibliothèque pour vous simplifier la vie Dapper.Contrib.Extensions. Après avoir inclus cela, vous pouvez simplement écrire:

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

Si vous utilisez 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
la source
Qu'est-ce que Dapper.SimpleSave?
Kiquenet le
@Kirquenet, j'ai utilisé Dapper, Dapper.SimpleCRUD, Dapper.SimpleCRUD.ModelGenerator, Dapper.SimpleLoad et Dapper.SimpleSave dans un projet sur lequel j'ai travaillé il y a un moment. Je les ai ajoutés via les importations nuGet. Je les ai combinés avec un modèle T4 pour échafauder tous les DAO de mon site. github.com/Paymentsense/Dapper.SimpleSave github.com/Paymentsense/Dapper.SimpleLoad
Lodlaiden