Làm cách nào để thực hiện chèn và trả lại danh tính được chèn bằng Dapper?


170

Làm cách nào để thực hiện thao tác chèn vào cơ sở dữ liệu và trả về danh tính được chèn bằng Dapper?

Tôi đã thử một cái gì đó như thế này:

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

Nhưng nó không hoạt động.

@Marc Gravell cảm ơn, đã trả lời. Tôi đã thử giải pháp của bạn, nhưng, vẫn có dấu vết ngoại lệ tương tự bên dưới

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

Câu trả lời:


285

Nó không hỗ trợ các tham số đầu vào / đầu ra (bao gồm RETURNgiá trị) nếu bạn sử dụng DynamicParameters, nhưng trong trường hợp này, tùy chọn đơn giản hơn chỉ đơn giản là:

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

Lưu ý rằng trên các phiên bản SQL Server gần đây hơn, bạn có thể sử dụng OUTPUTmệnh đề:

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

11
@ppiotrowicz hmmm .... SCIANIDENTITY sẽ quay trở lại numeric, hả? Có lẽ sử dụng mã gốc của bạn và select @id? (điều này chỉ cần thêm một diễn viên). Tôi sẽ ghi chú để đảm bảo rằng nó hoạt động tự động trong các bản dựng dapper trong tương lai. Một lựa chọn khác cho bây giờ là select cast(SCOPE_IDENTITY() as int)- một lần nữa, một chút xấu xí. Tôi sẽ sửa nó.
Marc Gravell

2
@MarcGravell: Chà! Marc tuyệt vời, đó là một trong những tốt! Tôi đã không nhận ra rằng scope_identityloại trở lại là numeric(38,0). +1 một tìm kiếm thực sự tốt. Mặc dù vậy, tôi chắc chắn rằng mình không phải là người duy nhất.
Robert Koritnik

5
Này, câu trả lời này là điểm nhấn số một để lấy lại giá trị nhận dạng từ truy vấn dapper. Bạn đã đề cập rằng điều này được cải thiện rất nhiều khi liên kết với một đối tượng; bạn có thể chỉnh sửa và cập nhật thông tin về cách bạn thực hiện việc này bây giờ không? Tôi đã kiểm tra các sửa đổi trong tệp Kiểm tra trên github gần bình luận ngày 26 tháng 11 của bạn nhưng không thấy bất cứ điều gì liên quan đến câu hỏi: / Giả định của tôi là Query<foo>chèn các giá trị sau đó chọn * trong đó id = SCOPE_IDENTITY ().

2
@Xerxes điều gì khiến bạn nghĩ điều này vi phạm CQS? CQS không phải là về việc một hoạt động SQL có trả về lưới hay không. Đây là một lệnh, tinh khiết và đơn giản. Đây không phải là một truy vấn trong thuật ngữ CQS, mặc dù sử dụng từ này Query.
Marc Gravell

3
Nitpicky, nhưng thay vì sử dụng Queryvà nhận giá trị đầu tiên từ bộ sưu tập được trả về, tôi nghĩ ExecuteScalar<T>có ý nghĩa hơn trong trường hợp này vì nhiều nhất một giá trị thường được trả về.
Peter Majeed

52

KB: 2019779 , "Bạn có thể nhận các giá trị không chính xác khi sử dụng SCOPE_IDENTITY () và @@ IDENTITY", mệnh đề OUTPUT là cơ chế an toàn nhất:

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
FYI, điều này có thể chậm hơn so với việc sử dụng SCOPE_IDENTITY và đã được sửa trong bản cập nhật # 5 lên Gói dịch vụ SQL Server 2008 R2 1.
Michael Silver

2
@MichaelSilver bạn có khuyên bạn nên sử dụng SCOPE_IDENTITY hoặc @@ IDENTITY trước hơn OUTPUT không? KB: 2019779 đã CỐ ĐỊNH ?
Kiquenet

1
@Kiquenet, nếu tôi đang viết mã dựa trên DB không được sửa, tôi có thể sẽ sử dụng mệnh đề OUTPUT chỉ để đảm bảo nó hoạt động như mong đợi.
Michael Silver

1
@this hoạt động tuyệt vời để chèn một bản ghi duy nhất nhưng nếu tôi vượt qua trong một bộ sưu tập tôi nhận đượcAn enumerable sequence of parameters (arrays, lists, etc) is not allowed in this context
MaYaN

43

Một câu trả lời muộn, nhưng đây là một câu trả lời thay thế cho SCOPE_IDENTITY()câu trả lời mà chúng tôi đã kết thúc bằng cách sử dụng: OUTPUT INSERTED

Chỉ trả về ID của đối tượng được chèn:

Nó cho phép bạn có được tất cả hoặc một số thuộc tính của hàng được chèn:

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

Trả về đối tượng được chèn bằng ID:

Nếu bạn muốn, bạn có thể lấy PhoneEmailthậm chí toàn bộ hàng được chèn:

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

Ngoài ra, với điều này, bạn có thể trả về dữ liệu của các hàng bị xóa hoặc cập nhật . Chỉ cần cẩn thận nếu bạn đang sử dụng kích hoạt bởi vì:

Các cột được trả về từ OUTPUT phản ánh dữ liệu như sau khi câu lệnh INSERT, UPDATE hoặc DELETE đã hoàn thành nhưng trước khi kích hoạt được thực thi.

Đối với các trình kích hoạt INSTEAD OF, các kết quả được trả về được tạo ra như thể đã xảy ra XÁC NHẬN, CẬP NHẬT hoặc XÓA, ngay cả khi không có sửa đổi nào xảy ra do kết quả của hoạt động kích hoạt. Nếu một câu lệnh bao gồm mệnh đề OUTPUT được sử dụng bên trong phần thân của trình kích hoạt, thì các bí danh bảng phải được sử dụng để tham chiếu các bảng được chèn và xóa của trình kích hoạt để tránh trùng lặp các tham chiếu cột với các bảng CHỈ và XÓA được liên kết với OUTPUT.

Thêm về nó trong các tài liệu: liên kết


1
@Kiquenet đối tượng TransactionScope để sử dụng với truy vấn. Có thể tìm thấy nhiều hơn ở đây: dapper-tutorial.net/transaction và tại đây: stackoverflow.com/questions/10363933/ chủ
Tadija Bagarić

Chúng tôi có thể sử dụng 'ExecuteScalarAsync <int>' ở đây thay vì 'QuerySingle <int>' không?
Ebleme

6

UnlimitedCastException mà bạn nhận được là do SCOPE_IDENTITY là số thập phân (38,0) .

Bạn có thể trả về nó như một int bằng cách sử dụng nó như sau:

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

Không chắc có phải vì tôi đang làm việc với SQL 2000 hay không nhưng tôi phải làm điều này để khiến nó hoạt động.

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
Hãy thử <code> chọn cast (SCOPE_IDENTITY () là int) </ code> và nó cũng sẽ hoạt động vào năm 2000.
David Aleu

bạn đã thử select cast(SCOPE_IDENTITY() as int)chưa
Kiquenet

1

Có một thư viện tuyệt vời để làm cho cuộc sống của bạn dễ dàng hơn Dapper.Contrib.Extensions. Sau khi bao gồm điều này, bạn chỉ có thể viết:

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

0

Nếu bạn đang sử dụng 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));
        }
    }

Dapper.SimpleSave là gì?
Kiquenet

@Kirquenet, tôi đã sử dụng Dapper, Dapper.SimpleCRUD, Dapper.SimpleCRUD.ModelGenerator, Dapper.SimpleLoad và Dapper.SimpleSave trong một dự án tôi đã làm việc cách đây một thời gian. Tôi đã thêm chúng thông qua nhập khẩu nuGet. Tôi đã kết hợp chúng với một mẫu T4 để tạo ra tất cả DAO cho trang web của tôi. github.com/Paymentsense/Dapper.SimpleSave github.com/Paymentsense/Dapper.SimpleLoad
Lodlaiden
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.