233

How do I perform an insert to database and return inserted identity with Dapper?

I've tried something like this:

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

But it did't work.

@Marc Gravell thanks, for reply. I've tried your solution but, still same exception trace is below

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
Charles Burns
  • 10,310
  • 7
  • 64
  • 81
ppiotrowicz
  • 4,464
  • 3
  • 32
  • 46

9 Answers9

389

It does support input/output parameters (including RETURN value) if you use DynamicParameters, but in this case the simpler option is simply:

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

Note that on more recent versions of SQL Server (2005+) you can use the OUTPUT clause:

var id = connection.QuerySingle<int>( @"
INSERT INTO [MyTable] ([Stuff])
OUTPUT INSERTED.Id
VALUES (@Stuff);", new { Stuff = mystuff});
James Skemp
  • 8,018
  • 9
  • 64
  • 107
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • Oh, one more thing. I'm not using .NET 4.0 and dynamic features – ppiotrowicz Nov 25 '11 at 14:22
  • 15
    @ppiotrowicz hmmm.... darn SCOPEIDENTITY is going to return `numeric`, eh? Perhaps use your original code and `select @id` ? (this just adds a cast). I will make a note to make sure this works automatically in future dapper builds. Another option for now is `select cast(SCOPE_IDENTITY() as int)` - again, a bit ugly. I will fix this. – Marc Gravell Nov 25 '11 at 14:26
  • 2
    @MarcGravell: Wow! Great Marc, that's a good one! I didn't realize that `scope_identity` return type is `numeric(38,0)`. +1 a really good find. Never though of it really and I'm sure I'm not the only one. – Robert Koritnik Nov 26 '11 at 13:59
  • @MarcGravell just wondering if you had tried making the automatic conversion happen. I ran into this recently. The TSQL cast works, but it would be nice to not have to do that. Thanks! – Ronnie Overby Nov 26 '12 at 20:20
  • 1
    @Ronnie yes; in a recent build I **hugely** improved this for when binding *to an object*, i.e. `Query(sql, ...)` - however, the simpler case of `Query(sql, ...)` needs a little bit of love to bring it up to the same standard. There is a work-item logged to do that. – Marc Gravell Nov 26 '12 at 21:16
  • @MarcGravell: I am having the same problem with calling return from a stored procedure. The primary key field is a bigint and I even set a local variable of type bigint to the SCOPE_IDENTITY() variable before returning the variable. When I call execute and pass a long generic parameter, it fails with the "Specified cast is not valid". I have the following question, pertaining to this issue: http://stackoverflow.com/questions/17197018/dapper-getting-specified-cast-is-not-valid-for-returnvalue-parameter-value – user1790300 Jun 20 '13 at 18:30
  • 6
    Hey, this answer is the number one hit for getting an identity value back from a dapper query. You mentioned that this is hugely improved when binding to an object; can you edit and give an update as to how you'd do this now? I checked revisions in the Tests file on github near your Nov26'12 comment but don't see anything related to the question :/ My assumption is to `Query` that inserts values then selects * where id = SCOPE_IDENTITY(). –  Sep 08 '13 at 17:41
  • @MarcGravell this method obviously breaks the CQS principle, do you think that the principle could be neglected in this instance? – Farhad-Taran Mar 03 '15 at 12:58
  • 4
    @Xerxes what makes you think this violates CQS? CQS isn't about whether a SQL operation returns a grid. This is a command, pure and simple. This is not a query in CQS terms, despite using the word `Query`. – Marc Gravell Mar 03 '15 at 13:41
  • @MarcGravell but if its a command then shouldn't the return type be void? Id imagine that the method wrapping this will return the id. – Farhad-Taran Mar 03 '15 at 14:01
  • 1
    @Xerxes the method wrapping this can return the id or not, as you feel free; however, *knowing the row id* is often a necessary feature in insert commands, especially if that command is part of a larger group. CQS is **not** about "does this method return void"; if you focus on that, you're missing the main aims – Marc Gravell Mar 03 '15 at 14:24
  • 5
    Nitpicky, but rather than use `Query` and get the first value from the returned collection, I think `ExecuteScalar` makes more sense in this case since at most one value is normally returned. – Peter Majeed Jun 18 '15 at 09:06
  • 2
    `@@IDENTITY and SCOPE_IDENTITY()` are ***evil*** ? better use `OUTPUT Inserted.Id` ? – Kiquenet May 30 '18 at 09:55
  • HOW-TO Dapper and OUTPUT clause ? https://www.sqlservercentral.com/articles/the-output-clause-for-insert-and-delete-statements – Kiquenet Jun 04 '20 at 21:29
  • @Kiquenet note that the answer here does mention the `output` clause. Is there a question I can help with there? Dapper and `output` work just fine together. The linked article is not Dapper related, but yes: the queries there can be used alongside Dapper via any if the Query methods – Marc Gravell Jun 04 '20 at 23:13
67

A late answer, but here is an alternative to the SCOPE_IDENTITY() answers that we ended up using: OUTPUT INSERTED

Return only ID of inserted object:

It allows you to get all or some attributes of the inserted row:

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

Return inserted object with ID:

If you wanted you could get Phone and Email or even the whole inserted row:

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

Also, with this you can return data of deleted or updated rows. Just be careful if you are using triggers because (from link mentioned before):

Columns returned from OUTPUT reflect the data as it is after the INSERT, UPDATE, or DELETE statement has completed but before triggers are executed.

For INSTEAD OF triggers, the returned results are generated as if the INSERT, UPDATE, or DELETE had actually occurred, even if no modifications take place as the result of the trigger operation. If a statement that includes an OUTPUT clause is used inside the body of a trigger, table aliases must be used to reference the trigger inserted and deleted tables to avoid duplicating column references with the INSERTED and DELETED tables associated with OUTPUT.

More on it in the docs: link

Tadija Bagarić
  • 2,495
  • 2
  • 31
  • 48
  • 1
    @Kiquenet TransactionScope object to use with the query. More can be found here: http://dapper-tutorial.net/transaction and here: https://stackoverflow.com/questions/10363933/transaction-with-dapper-dot-net – Tadija Bagarić May 29 '18 at 16:46
  • 1
    Can we use 'ExecuteScalarAsync' here instead of 'QuerySingle'? – Ebleme Mar 23 '19 at 23:30
58

KB:2019779,"You may receive incorrect values when using SCOPE_IDENTITY() and @@IDENTITY", The OUTPUT clause is the safest mechanism:

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
  • 629
  • 5
  • 4
  • 15
    FYI, this may be slower than using SCOPE_IDENTITY and was fixed in update #5 to SQL Server 2008 R2 Service Pack 1. – Michael Silver Nov 26 '14 at 05:55
  • 2
    @MichaelSilver do you recommend use ***SCOPE_IDENTITY*** or ***@@IDENTITY*** before than ***OUTPUT*** ? _KB:2019779_ was **FIXED** ? – Kiquenet Feb 15 '17 at 12:48
  • 1
    @Kiquenet, if I were writing the code against a DB that was not fixed, I would probably use the OUTPUT clause just to be sure it works as expected. – Michael Silver Feb 21 '17 at 06:44
  • 1
    @this works great for inserting a single record but if I pass in a collection I get `An enumerable sequence of parameters (arrays, lists, etc) is not allowed in this context` – MaYaN Mar 15 '17 at 11:19
7

The InvalidCastException you are getting is due to SCOPE_IDENTITY being a Decimal(38,0).

You can return it as an int by casting it as follows:

string sql = @"
INSERT INTO [MyTable] ([Stuff]) VALUES (@Stuff);
SELECT CAST(SCOPE_IDENTITY() AS INT)";

int id = connection.Query<int>(sql, new { Stuff = mystuff}).Single();
Community
  • 1
  • 1
bpruitt-goddard
  • 3,174
  • 2
  • 28
  • 34
4

Not sure if it was because I'm working against SQL 2000 or not but I had to do this to get it to work.

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
  • 89
  • 1
  • 3
4

I was using .net core 3.1 with postgres 12.3. Building on the answer from Tadija Bagarić I ended up with:

using (var connection = new NpgsqlConnection(AppConfig.CommentFilesConnection))
        {

            string insertUserSql = @"INSERT INTO mytable(comment_id,filename,content)
                    VALUES( @commentId, @filename, @content) returning id;";

            int newUserId = connection.QuerySingle<int>(
                                            insertUserSql,
                                            new
                                            {
                                                commentId = 1,
                                                filename = "foobar!",
                                                content = "content"
                                            }
                                            );

          


        }

where AppConfig is my own class which simply gets a string set for my connection details. This is set within the Startup.cs ConfigureServices method.

richardprocter
  • 125
  • 1
  • 2
3

There is a great library to make your life easier Dapper.Contrib.Extensions. After including this you can just write:

public int Add(Transaction transaction)
{
        using (IDbConnection db = Connection)
        {
                return (int)db.Insert(transaction);
        }
}
Wings
  • 502
  • 4
  • 14
1

I see answer for sql server, well here it is for MySql using a transaction


    Dim sql As String = "INSERT INTO Empleado (nombres, apepaterno, apematerno, direccion, colonia, cp, municipio, estado, tel, cel, correo, idrol, relojchecadorid, relojchecadorid2, `activo`,`extras`,`rfc`,`nss`,`curp`,`imagen`,sueldoXHra, IMSSCotiza, thumb) VALUES (@nombres, @apepaterno, @apematerno, @direccion, @colonia, @cp, @municipio, @estado, @tel, @cel, @correo, @idrol, @relojchecadorid, @relojchecadorid2, @activo, @extras, @rfc, @nss, @curp, @imagen,@sueldoXHra,@IMSSCotiza, @thumb)"
    
            Using connection As IDbConnection = New MySqlConnection(getConnectionString())
                connection.Open()
                Using transaction = connection.BeginTransaction
                    Dim res = connection.Execute(sql, New With {reg.nombres, reg.apepaterno, reg.apematerno, reg.direccion, reg.colonia, reg.cp, reg.municipio, reg.estado, reg.tel, reg.cel, reg.correo, reg.idrol, reg.relojchecadorid, reg.relojchecadorid2, reg.activo, reg.extras, reg.rfc, reg.nss, reg.curp, reg.imagen, reg.thumb, reg.sueldoXHra, reg.IMSSCotiza}, commandTimeout:=180, transaction:=transaction)
                    lastInsertedId = connection.ExecuteScalar("SELECT LAST_INSERT_ID();", transaction:=transaction)
                    If res > 0 Then 
transaction.Commit()
return true
end if
                    
                End Using
            End Using
0

If you're using 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
  • 361
  • 1
  • 10
  • What is Dapper.SimpleSave ? – Kiquenet Jun 03 '20 at 20:46
  • @Kirquenet, I used Dapper, Dapper.SimpleCRUD, Dapper.SimpleCRUD.ModelGenerator, Dapper.SimpleLoad, and Dapper.SimpleSave in a project I worked on awhile ago. I added them via nuGet imports. I combined them with a T4 template to scaffold all the DAO for my site. https://github.com/Paymentsense/Dapper.SimpleSave https://github.com/Paymentsense/Dapper.SimpleLoad – Lodlaiden Jun 06 '20 at 21:04