Как выполнить вставку и вернуть вставленную личность с помощью Dapper?

170

Как выполнить вставку в базу данных и вернуть вставленную идентификацию с помощью 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
ppiotrowicz
источник

Ответы:

287

Он поддерживает параметры ввода / вывода (включая 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 хммм .... чертовски SCOPEIDENTITY собирается вернуться numeric, а? Возможно, использовать свой оригинальный код и select @id? (это просто добавляет актерский состав). Я сделаю примечание, чтобы убедиться, что это работает автоматически в будущих сборках dapper. Еще один вариант на данный момент select cast(SCOPE_IDENTITY() as int)- опять же немного некрасиво. Я исправлю это.
Марк Гравелл
2
@MarcGravell: Ух ты! Отличный Марк, это хорошо! Я не понял, что scope_identityвозвращаемый тип есть numeric(38,0). +1 действительно хорошая находка. Никогда, хотя на самом деле, и я уверен, что я не единственный.
Роберт Коритник
5
Эй, этот ответ - хит номер один для получения значения идентичности из более щадящего запроса. Вы упомянули, что это значительно улучшается при привязке к объекту; Вы можете отредактировать и дать обновленную информацию о том, как бы вы сделали это сейчас? Я проверил ревизии в файле Tests на github рядом с вашим комментарием от 26 ноября 2012 года, но не вижу ничего, связанного с вопросом: / Я предполагаю, Query<foo>что вставляет значения, а затем выбирает *, где id = SCOPE_IDENTITY ().
2
@Xerxes, что заставляет вас думать, что это нарушает CQS? CQS не о том, возвращает ли операция SQL сетку. Это команда, чистая и простая. Это не запрос в терминах CQS, несмотря на использование слова Query.
Марк Гравелл
3
Nitpicky, но вместо того, чтобы использовать Queryи получать первое значение из возвращенной коллекции, я думаю, ExecuteScalar<T>в этом случае имеет больше смысла, поскольку обычно возвращается не более одного значения.
Питер Маджид
53

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();
jww
источник
14
К вашему сведению, это может быть медленнее, чем при использовании SCOPE_IDENTITY и было исправлено в обновлении № 5 до SQL Server 2008 R2 с пакетом обновления 1.
Майкл Сильвер,
2
@MichaelSilver вы рекомендуете использовать SCOPE_IDENTITY или @@ IDENTITY , прежде чем ВЫХОД ? КБ: 2019779 было FIXED ?
Kiquenet
1
@Kiquenet, если бы я писал код для БД, которая не была исправлена, я бы, вероятно, использовал предложение OUTPUT только для того, чтобы убедиться, что он работает должным образом.
Майкл Сильвер
1
@this работает отлично подходит для вставки одной записи , но если я прохожу в коллекции я получаюAn enumerable sequence of parameters (arrays, lists, etc) is not allowed in this context
МАЙЯ
44

Поздний ответ, но здесь является альтернативой к SCOPE_IDENTITY()ответам , которые мы заканчивали с помощью: OUTPUT вставленного

Возврат только идентификатора вставленного объекта:

Это позволяет вам получить все или некоторые атрибуты вставленной строки:

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

Вернуть вставленный объект с идентификатором:

Если вы хотите, вы можете получить Phoneи Emailдаже целый вставленный ряд:

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, псевдонимы таблиц должны использоваться для ссылки на вставленные и удаленные таблицы триггера, чтобы избежать дублирования ссылок на столбцы с таблицами INSERTED и DELETED, связанными с OUTPUT.

Подробнее об этом в документации: ссылка

Тадия багарич
источник
1
@Kiquenet TransactionScope объект для использования с запросом. Больше можно найти здесь: dapper-tutorial.net/transaction и здесь: stackoverflow.com/questions/10363933/…
Тадия Багарич
Можем ли мы использовать здесь «ExecuteScalarAsync <int>» вместо «QuerySingle <int>»?
Ebleme
6

Исключение InvalidCastException, которое вы получаете, связано с тем, что SCOPE_IDENTITY является десятичным числом (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();
bpruitt Годдарда
источник
4

Не уверен, что это потому, что я работаю против 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();
mytydev
источник
2
Попробуйте использовать <code> select cast (SCOPE_IDENTITY () as int) </ code>, и это должно работать и в 2000 году.
Дэвид Алеу
ты пробовал 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));
        }
    }
Lodlaiden
источник
Что такое Dapper.SimpleSave?
Кикенет
@Kirquenet, я использовал Dapper, Dapper.SimpleCRUD, Dapper.SimpleCRUD.ModelGenerator, Dapper.SimpleLoad и Dapper.SimpleSave в проекте, над которым я работал некоторое время назад. Я добавил их через импорт NuGet. Я объединил их с шаблоном T4, чтобы подготовить все DAO для моего сайта. github.com/Paymentsense/Dapper.SimpleSave github.com/Paymentsense/Dapper.SimpleLoad
Лодлейден,