Bagaimana cara saya melakukan penyisipan dan mengembalikan identitas yang disisipkan dengan Dapper?

170

Bagaimana cara saya melakukan penyisipan ke basis data dan mengembalikan identitas yang disisipkan dengan Dapper?

Saya sudah mencoba sesuatu seperti ini:

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

Tapi itu tidak berhasil.

@Marc Gravell terima kasih, untuk balasan. Saya sudah mencoba solusi Anda tetapi, jejak pengecualian masih sama di bawah ini

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
sumber

Jawaban:

286

Itu mendukung parameter input / output (termasuk RETURNnilai) jika Anda gunakan DynamicParameters, tetapi dalam hal ini opsi yang lebih sederhana adalah:

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

Perhatikan bahwa pada versi SQL Server yang lebih baru Anda dapat menggunakan OUTPUTklausa:

var id = connection.QuerySingle<int>( @"
INSERT INTO [MyTable] ([Stuff])
OUTPUT INSERTED.Id
VALUES (@Stuff);", new { Stuff = mystuff});
Marc Gravell
sumber
11
@ppiotrowicz hmmm .... Sialan SCOPEIDENTITY akan kembali numeric, eh? Mungkin menggunakan kode asli Anda dan select @id? (ini hanya menambah gips). Saya akan membuat catatan untuk memastikan ini berfungsi secara otomatis di build necis masa depan. Pilihan lain untuk saat ini adalah select cast(SCOPE_IDENTITY() as int)- sekali lagi, agak jelek. Saya akan memperbaikinya.
Marc Gravell
2
@MarcGravell: Wow! Great Marc, itu bagus! Saya tidak menyadari bahwa scope_identitytipe pengembalian adalah numeric(38,0). +1 temuan yang sangat bagus. Tidak pernah benar-benar dan saya yakin saya bukan satu-satunya.
Robert Koritnik
5
Hai, jawaban ini adalah nomor satu hit untuk mendapatkan nilai identitas kembali dari permintaan necis. Anda menyebutkan bahwa ini sangat ditingkatkan saat mengikat ke objek; dapatkah Anda mengedit dan memberikan pembaruan tentang bagaimana Anda akan melakukan ini sekarang? Saya memeriksa revisi dalam file Tes di github dekat komentar Nov26'12 Anda tetapi tidak melihat apa pun yang terkait dengan pertanyaan: / Asumsi saya adalah Query<foo>bahwa nilai sisipan kemudian memilih * di mana id = SCOPE_IDENTITY ().
2
@Xerxes apa yang membuat Anda berpikir ini melanggar CQS? CQS bukan tentang apakah operasi SQL mengembalikan grid. Ini adalah perintah, murni dan sederhana. Ini bukan permintaan dalam istilah CQS, meskipun menggunakan kata Query.
Marc Gravell
3
Nitpicky, tetapi daripada menggunakan Querydan mendapatkan nilai pertama dari koleksi yang dikembalikan, saya pikir ExecuteScalar<T>lebih masuk akal dalam kasus ini karena paling banyak satu nilai biasanya dikembalikan.
Peter Majeed
53

KB: 2019779 , "Anda mungkin menerima nilai yang salah saat menggunakan SCOPE_IDENTITY () dan @@ IDENTITY", klausa OUTPUT adalah mekanisme paling aman:

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
sumber
14
FYI, ini mungkin lebih lambat daripada menggunakan SCOPE_IDENTITY dan diperbaiki dalam pembaruan # 5 untuk SQL Server 2008 R2 Paket Layanan 1.
Michael Silver
2
@MichaelSilver, apakah Anda merekomendasikan untuk menggunakan SCOPE_IDENTITY atau @@ IDENTITY sebelum OUTPUT ? KB: 2019779 itu TETAP ?
Kiquenet
1
@ Kiquenet, jika saya menulis kode terhadap DB yang tidak diperbaiki, saya mungkin akan menggunakan klausa OUTPUT hanya untuk memastikan itu berfungsi seperti yang diharapkan.
Michael Silver
1
@ ini bekerja sangat baik untuk memasukkan satu catatan tetapi jika saya lulus dalam koleksi saya dapatkanAn enumerable sequence of parameters (arrays, lists, etc) is not allowed in this context
MaYaN
44

Jawaban yang terlambat, tetapi di sini ada alternatif untuk SCOPE_IDENTITY()jawaban yang akhirnya kami gunakan: OUTPUT INSERTED

Hanya kembalikan ID objek yang disisipkan:

Ini memungkinkan Anda untuk mendapatkan semua atau beberapa atribut dari baris yang dimasukkan:

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

Kembalikan objek yang dimasukkan dengan ID:

Jika mau, Anda bisa mendapatkan Phonedan Emailatau bahkan seluruh baris yang disisipkan:

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

Juga, dengan ini Anda dapat mengembalikan data dari baris yang dihapus atau diperbarui . Berhati-hatilah jika Anda menggunakan pemicu karena:

Kolom yang dikembalikan dari OUTPUT mencerminkan data sebagaimana adanya setelah pernyataan INSERT, UPDATE, atau DELETE selesai tetapi sebelum pemicu dieksekusi.

Untuk BUKAN pemicu, hasil yang dikembalikan dihasilkan seolah-olah INSERT, UPDATE, atau DELETE benar-benar terjadi, bahkan jika tidak ada modifikasi yang terjadi sebagai hasil dari operasi pemicu. Jika pernyataan yang menyertakan klausa OUTPUT digunakan di dalam tubuh pemicu, alias tabel harus digunakan untuk mereferensikan pemicu yang dimasukkan dan tabel yang dihapus untuk menghindari duplikasi referensi kolom dengan tabel INSERTED dan DELETED yang terkait dengan OUTPUT.

Lebih lanjut tentang itu di docs: link

Tadija Bagarić
sumber
1
@Kiquenet TransactionScope objek untuk digunakan dengan permintaan. Lebih banyak dapat ditemukan di sini: dapper-tutorial.net/transaction dan di sini: stackoverflow.com/questions/10363933/…
Tadija Bagarić
Bisakah kita menggunakan 'ExecuteScalarAsync <int>' di sini alih-alih 'QuerySingle <int>'?
Ebleme
6

InvalidCastException yang Anda dapatkan adalah karena SCOPE_IDENTITY menjadi Desimal (38,0) .

Anda dapat mengembalikannya sebagai int dengan melemparkannya sebagai berikut:

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

Tidak yakin apakah itu karena saya bekerja melawan SQL 2000 atau tidak, tetapi saya harus melakukan ini untuk membuatnya berfungsi.

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
sumber
2
Coba <code> pilih pemeran (SCOPE_IDENTITY () sebagai int) </code> dan seharusnya berfungsi pada 2000 juga.
David Aleu
Apakah kamu sudah mencoba select cast(SCOPE_IDENTITY() as int)?
Kiquenet
1

Ada perpustakaan yang bagus untuk membuat hidup Anda lebih mudah Dapper.Contrib.Extensions. Setelah memasukkan ini, Anda cukup menulis:

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

Jika Anda menggunakan 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
sumber
Apa itu Dapper.SimpleSave?
Kiquenet
@ Kirquenet, saya menggunakan Dapper, Dapper.SimpleCRUD, Dapper.SimpleCRUD.ModelGenerator, Dapper.SimpleLoad, dan Dapper.SimpleSave dalam proyek yang saya kerjakan beberapa waktu lalu. Saya menambahkannya melalui impor nuGet. Saya menggabungkannya dengan templat T4 untuk merancah semua DAO untuk situs saya. github.com/Paymentsense/Dapper.SimpleSave github.com/Paymentsense/Dapper.SimpleLoad
Lodlaiden