Як виконати вставку та повернути вставлену особу з 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

Відповіді:


286

Він підтримує параметри вводу / виводу (включаючи 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 .... чорт СКАПЕЙДЕНТНІСТЬ повернеться numeric, так? Можливо, використовуєте свій оригінальний код і select @id? (це просто додає акторський склад). Я зроблю замітку, щоб переконатися, що це працює автоматично в майбутніх збірниках Dapper. Ще один варіант на даний момент select cast(SCOPE_IDENTITY() as int)- знову ж таки, трохи некрасиво. Я це виправлю.
Марк Гравелл

2
@MarcGravell: Нічого собі! Чудовий Марк, це добре! Я не розумів, що такий scope_identityтип повернення numeric(38,0). +1 дійсно гарна знахідка. Ніколи цього не було насправді, і я впевнений, що я не єдиний.
Роберт Коритник

5
Гей, ця відповідь - хіт номер один для повернення значення ідентичності із запиту "Dapper". Ви згадали, що це значно покращується під час прив’язки до об'єкта; чи можете ви відредагувати та надати оновлення щодо того, як би ви це зробили зараз? Я перевірив зміни у файлі тестів на github поблизу вашого коментаря Nov26'12, але не бачу нічого, пов’язаного з питанням: / Моє припущення полягає в Query<foo>тому, що він вставляє значення, а потім вибирає * where 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();

14
FYI, це може бути повільніше, ніж використання SCOPE_IDENTITY, і було зафіксовано в оновленнях №5 до пакета оновлень 1 для SQL Server 2008 R2 1.
Майкл Сілвер

2
@MichaelSilver чи рекомендуєте ви використовувати SCOPE_IDENTITY або @@ IDENTITY раніше, ніж OUTPUT ? КБ: 2019779 було виправлено ?
Кікенет

1
@Kiquenet, якби я писав код проти БД, який не був виправлений, я б, ймовірно, використовував пункт OUTPUT просто для того, щоб переконатися, що він працює, як очікувалося.
Майкл Сілвер

1
@ це чудово підходить для вставки одного запису, але якщо я 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 повернуті результати генеруються так, як ніби насправді відбулося ВСТАВКА, ОНОВЛЕННЯ або ВИДАЛЕННЯ, навіть якщо в результаті дії тригера не відбулося жодних модифікацій. Якщо оператор, що включає в себе пункт OUTPUT, використовується в тілі тригера, псевдоніми таблиць повинні використовуватися для посилання на тригери вставлених та видалених таблиць, щоб уникнути дублювання посилань стовпців із таблицями INSERTED та DELETED, пов'язаними з OUTPUT.

Детальніше про це у документах: посилання


1
@Kiquenet TransactionScope об’єкт для використання із запитом. Більше можна знайти тут: dapper-tutorial.net/transaction і тут: stackoverflow.com/questions/10363933 / ...
Tadija Bagarić

Чи можемо ми тут використовувати «ExecuteScalarAsync <int>» замість «QuerySingle <int>»?
Еблеме

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

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

2
Спробуйте <code> select cast (SCOPE_IDENTITY () як int) </code>, і він також повинен працювати у 2000 році.
Девід Алеу

ти пробував select cast(SCOPE_IDENTITY() as int)?
Кікенет

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?
Кікенет

@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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.