Dapperで挿入および挿入されたIDを返すにはどうすればよいですか?


170

データベースへの挿入を実行し、挿入されたIDをDapperで返すにはどうすればよいですか?

私はこのようなものを試しました:

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

しかし、それはうまくいきませんでした。

@Marc Gravell、返信ありがとう。私はあなたの解決策を試しましたが、同じ例外トレースが以下にあります

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

回答:


285

それはありませんサポート(含む入力/出力パラメータRETURNあなたが使用している場合の値)DynamicParametersが、この場合には単純なオプションは、単に次のとおりです。

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

SQL Serverの最新バージョンでは、次のOUTPUT句を使用できることに注意してください。

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

11
@ppiotrowicz hmmm ....くそーSCOPEIDENTITYが返ってきnumericますね おそらく、元のコードを使用しますselect @idか?(これはキャストを追加するだけです)。これが将来のdapperビルドで自動的に機能することを確認するためにメモしておきます。今のところ別のオプションはselect cast(SCOPE_IDENTITY() as int)-もう一度、少し醜いです。これを修正します。
Marc Gravell

2
@MarcGravell:すごい!グレートマーク、いいですね scope_identity戻り値の型がであることを知りませんでしたnumeric(38,0)。+1本当に良い発見。それは本当にありませんが、私だけではないと確信しています。
Robert Koritnik

5
ねえ、この答えは、dapperクエリからID値を取得するための最大のヒットです。これは、オブジェクトにバインドするときに大幅に改善されると述べました。これをどのように行うかについて、編集して更新を与えることができますか?Nov26'12コメントの近くにあるgithubのTestsファイルでリビジョンを確認しましたが、質問に関連するものは何も表示されません:/私の仮定は、Query<foo>値を挿入してから、* where id = SCOPE_IDENTITY()を選択することです。

2
@XerxesこれはCQSに違反していると思われるのはなぜですか?CQSは、SQL操作がグリッドを返すかどうかではありません。これは、純粋で単純なコマンドです。単語を使用しているにもかかわらず、これはCQS用語でのクエリではありませんQuery
Marc Gravell

3
NitpickyですがQuery、返されたコレクションから最初の値を使用して取得するのではなく、ExecuteScalar<T>通常は1つの値しか返されないため、この場合の方が理にかなっています。
Peter Majeed 2015年

52

KB:2019779、「SCOPE_IDENTITY()および@@ IDENTITYを使用すると、誤った値を受け取る場合があります」、OUTPUT句が最も安全なメカニズムです。

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
参考までに、これはSCOPE_IDENTITYを使用するよりも遅くなる可能性があり、SQL Server 2008 R2 Service Pack 1のアップデート#5で修正されました
Michael Silver

2
@MichaelSilver OUTPUTより前にSCOPE_IDENTITYまたは@@ IDENTITYを使用することをお勧めしますか?KB:2019779をしてFIXED
Kiquenet

1
@Kiquenet、修正されていないDBに対してコードを記述している場合、予想どおりに機能することを確認するために、おそらくOUTPUT句を使用します。
マイケルシルバー

1
@これは単一のレコードを挿入するのに最適ですが、コレクションを渡すと次のようになりますAn enumerable sequence of parameters (arrays, lists, etc) is not allowed in this context
MaYaN

43

遅い回答ですが、ここでは、最終的に使用した回答の代替案を示しSCOPE_IDENTITY()ます:OUTPUT INSERTED

挿入されたオブジェクトのIDのみを返します:

挿入された行のすべてまたは一部の属性を取得できます。

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

挿入されたIDのオブジェクトを返す:

あなたが望むならば、あなたは得ることができるPhoneEmail、さらに全体に挿入された列:

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

また、これにより、削除または更新された行のデータを返すことができます。次の理由でトリガーを使用する場合は注意してください。

OUTPUTから返される列には、INSERT、UPDATE、またはDELETEステートメントが完了した後、トリガーが実行される前のデータが反映されます。

INSTEAD OFトリガーの場合、トリガー操作の結果として変更が行われなかった場合でも、INSERT、UPDATE、またはDELETEが実際に発生したかのように、返された結果が生成されます。トリガーの本体内でOUTPUT句を含むステートメントを使用する場合は、トリガーに挿入および削除されたテーブルを参照するためにテーブルエイリアスを使用して、OUTPUTに関連付けられたINSERTEDおよびDELETEDテーブルで列参照が重複しないようにする必要があります。

ドキュメントの詳細:リンク


1
クエリで使用する@Kiquenet TransactionScopeオブジェクト。もっとここで見つけることができます:dapper-tutorial.net/transactionここと:stackoverflow.com/questions/10363933/...
TadijaBagarić

ここでは、「QuerySingle <int>」の代わりに「ExecuteScalarAsync <int>」を使用できますか?
Ebleme

6

取得しているInvalidCastExceptionは、SCOPE_IDENTITYがDecimal(38,0)であることが原因です。

次のようにキャストすることで、intとして返すことができます。

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

SQL 2000を使っているのかどうかはわかりませんが、SQL 2000を機能させるためにこれを実行する必要がありました。

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
<code> select cast(SCOPE_IDENTITY()as int)</ code>を試してみてください。2000でも動作するはずです。
David Aleu

試しましたselect cast(SCOPE_IDENTITY() as int)
Kiquenet

1

Dapper.Contrib.Extensionsを使いやすくする優れたライブラリがあります。これを組み込んだ後は、次のように書くことができます。

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

0

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とは何ですか?
Kiquenet

@Kirquenet、私は少し前に取り組んだプロジェクトでDapper、Dapper.SimpleCRUD、Dapper.SimpleCRUD.ModelGenerator、Dapper.SimpleLoad、およびDapper.SimpleSaveを使用しました。nuGetインポートを介して追加しました。それらをT4テンプレートと組み合わせて、サイトのすべてのDAOを足場しました。github.com/Paymentsense/Dapper.SimpleSave github.com/Paymentsense/Dapper.SimpleLoad
Lodlaiden
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.