Wie führe ich eine Einfügung durch und gebe die eingefügte Identität mit Dapper zurück?

170

Wie führe ich eine Einfügung in die Datenbank durch und gebe die eingefügte Identität mit Dapper zurück?

Ich habe so etwas versucht:

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

Aber es hat nicht funktioniert.

@ Marc Gravell danke für die Antwort. Ich habe Ihre Lösung ausprobiert, aber die gleiche Ausnahmespur ist unten aufgeführt

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
quelle

Antworten:

285

Es unterstützt zwar Eingabe- / Ausgabeparameter (einschließlich RETURNWert), wenn Sie diese verwenden DynamicParameters. In diesem Fall ist die einfachere Option jedoch einfach:

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

Beachten Sie, dass Sie in neueren Versionen von SQL Server die folgende OUTPUTKlausel verwenden können:

var id = connection.QuerySingle<int>( @"
INSERT INTO [MyTable] ([Stuff])
OUTPUT INSERTED.Id
VALUES (@Stuff);", new { Stuff = mystuff});
Marc Gravell
quelle
11
@ppiotrowicz hmmm .... verdammt SCOPEIDENTITY wird zurückkehren numeric, was? Vielleicht verwenden Sie Ihren Originalcode und select @id? (Dies fügt nur eine Besetzung hinzu). Ich werde eine Notiz machen, um sicherzustellen, dass dies in zukünftigen adretten Builds automatisch funktioniert. Eine andere Option für den Moment ist select cast(SCOPE_IDENTITY() as int)- wieder ein bisschen hässlich. Ich werde das beheben.
Marc Gravell
2
@MarcGravell: Wow! Großartiger Marc, das ist gut! Ich wusste nicht, dass es sich um einen scope_identityRückgabetyp handelt numeric(38,0). +1 ein wirklich guter Fund. Niemals wirklich und ich bin sicher, dass ich nicht der einzige bin.
Robert Koritnik
5
Hey, diese Antwort ist der erste Treffer, um einen Identitätswert aus einer adretten Abfrage zurückzugewinnen. Sie haben erwähnt, dass dies beim Binden an ein Objekt erheblich verbessert wird. Können Sie bearbeiten und ein Update geben, wie Sie dies jetzt tun würden? Ich habe die Revisionen in der Testdatei auf github in der Nähe Ihres Nov26'12-Kommentars überprüft, sehe jedoch nichts im Zusammenhang mit der Frage: / Ich Query<foo>gehe davon aus, dass Werte eingefügt werden und dann * ausgewählt wird, wobei id = SCOPE_IDENTITY ().
2
@Xerxes, warum denkst du, dass dies gegen CQS verstößt? Bei CQS geht es nicht darum, ob eine SQL-Operation ein Raster zurückgibt. Dies ist ein Befehl, schlicht und einfach. Dies ist keine CQS-Abfrage, obwohl das Wort verwendet wird Query.
Marc Gravell
3
Nitpicky, aber anstatt Queryden ersten Wert aus der zurückgegebenen Sammlung zu verwenden und abzurufen, ist ExecuteScalar<T>dies in diesem Fall meiner Meinung nach sinnvoller, da normalerweise höchstens ein Wert zurückgegeben wird.
Peter Majeed
52

KB: 2019779 , "Bei Verwendung von SCOPE_IDENTITY () und @@ IDENTITY erhalten Sie möglicherweise falsche Werte." Die OUTPUT-Klausel ist der sicherste Mechanismus:

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
quelle
14
Zu Ihrer Information, dies ist möglicherweise langsamer als die Verwendung von SCOPE_IDENTITY und wurde in Update Nr. 5 auf SQL Server 2008 R2 Service Pack 1 behoben.
Michael Silver
2
@MichaelSilver empfehlen Sie die Verwendung von SCOPE_IDENTITY oder @@ IDENTITY vor OUTPUT ? KB: 2019779 wurde behoben ?
Kiquenet
1
@Kiquenet, wenn ich den Code für eine Datenbank schreiben würde, die nicht repariert wurde, würde ich wahrscheinlich die OUTPUT-Klausel verwenden, um sicherzugehen, dass sie wie erwartet funktioniert.
Michael Silver
1
@dies funktioniert gut zum Einfügen eines einzelnen Datensatzes, aber wenn ich eine Sammlung übergebe, bekomme ichAn enumerable sequence of parameters (arrays, lists, etc) is not allowed in this context
MaYaN
43

Eine späte Antwort, aber hier ist eine Alternative zu den SCOPE_IDENTITY()Antworten, die wir letztendlich verwendet haben: OUTPUT INSERTED

Nur ID des eingefügten Objekts zurückgeben:

Sie können alle oder einige Attribute der eingefügten Zeile abrufen:

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

Eingefügtes Objekt mit ID zurückgeben:

Wenn Sie möchten, können Sie Phoneund Emailoder sogar die gesamte eingefügte Zeile erhalten:

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

Damit können Sie auch Daten von gelöschten oder aktualisierten Zeilen zurückgeben. Seien Sie vorsichtig, wenn Sie Trigger verwenden, weil:

Von OUTPUT zurückgegebene Spalten geben die Daten wieder, wie sie sind, nachdem die INSERT-, UPDATE- oder DELETE-Anweisung abgeschlossen wurde, aber bevor Trigger ausgeführt werden.

Bei INSTEAD OF-Triggern werden die zurückgegebenen Ergebnisse so generiert, als ob INSERT, UPDATE oder DELETE tatsächlich aufgetreten wären, auch wenn infolge der Triggeroperation keine Änderungen vorgenommen wurden. Wenn eine Anweisung, die eine OUTPUT-Klausel enthält, im Hauptteil eines Triggers verwendet wird, müssen Tabellenaliasnamen verwendet werden, um auf die vom Trigger eingefügten und gelöschten Tabellen zu verweisen, um zu vermeiden, dass Spaltenreferenzen mit den mit OUTPUT verknüpften Tabellen INSERTED und DELETED dupliziert werden.

Mehr dazu in der Dokumentation: Link

Tadija Bagarić
quelle
1
@ Kiquenet TransactionScope-Objekt, das mit der Abfrage verwendet werden soll. Weitere finden Sie hier: dapper-tutorial.net/transaction und hier: stackoverflow.com/questions/10363933/…
Tadija Bagarić
Können wir hier 'ExecuteScalarAsync <int>' anstelle von 'QuerySingle <int>' verwenden?
Ebleme
6

Die InvalidCastException, die Sie erhalten, ist darauf zurückzuführen, dass SCOPE_IDENTITY eine Dezimalzahl (38,0) ist .

Sie können es als int zurückgeben, indem Sie es wie folgt umwandeln:

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
quelle
4

Ich bin mir nicht sicher, ob es daran liegt, dass ich gegen SQL 2000 arbeite oder nicht, aber ich musste dies tun, damit es funktioniert.

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
quelle
2
Probieren Sie die <code> select cast (SCOPE_IDENTITY () als int) </ code> aus und sie sollte auch im Jahr 2000 funktionieren.
David Aleu
haben Sie versucht select cast(SCOPE_IDENTITY() as int)?
Kiquenet
1

Es gibt eine großartige Bibliothek, die Ihnen das Leben erleichtert Dapper.Contrib.Extensions. Nachdem Sie dies aufgenommen haben, können Sie einfach schreiben:

public int Add(Transaction transaction)
{
        using (IDbConnection db = Connection)
        {
                return (int)db.Insert(transaction);
        }
}
Flügel
quelle
0

Wenn Sie Dapper.SimpleSave verwenden:

 //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
quelle
Was ist Dapper.SimpleSave?
Kiquenet
@Kirquenet, ich habe Dapper, Dapper.SimpleCRUD, Dapper.SimpleCRUD.ModelGenerator, Dapper.SimpleLoad und Dapper.SimpleSave in einem Projekt verwendet, an dem ich vor einiger Zeit gearbeitet habe. Ich habe sie über NuGet-Importe hinzugefügt. Ich habe sie mit einer T4-Vorlage kombiniert, um das gesamte DAO für meine Site zu erstellen. github.com/Paymentsense/Dapper.SimpleSave github.com/Paymentsense/Dapper.SimpleLoad
Lodlaiden