Как я могу использовать Entity Framework для выполнения MERGE, когда я не знаю, существует ли запись?
В this SO answer о Entity Framework и MERGE, пример того, как его кодировать, таков:
public void SaveOrUpdate(MyEntity entity)
{
if (entity.Id == 0)
{
context.MyEntities.AddObject(entity);
}
else
{
context.MyEntities.Attach(entity);
context.ObjectStateManager.ChangeObjectState(entity, EntityState.Modified);
}
}
Это предполагает, что вы уже знаете, существует ли сущность, которую вы хотите восстановить, или нет; в этом случае вы проверяете entity.Id
. Но что, если вы не знаете, существует ли предмет или нет? Например, в моем случае я импортирую записи из поставщика в свою базу данных, и данная запись может быть или не быть уже импортирована. Я хочу обновить запись, если она существует, иначе добавьте ее. Но идентификатор поставщика уже установлен в обоих случаях.
Я не вижу никакого способа сделать это, если я просто не спрошу базу данных, если запись уже существует, что наносит ущерб всей цели MERGE.
Ответы
Ответ 1
Если вам нужна команда UPSERT с атомной базой без хранимой процедуры, и вас не беспокоит обновляемый контекст, стоит упомянуть, что вы можете также заключить встроенный оператор MERGE
в вызов ExecuteSqlCommand
:
public void SaveOrUpdate(MyEntity entity)
{
var sql = @"MERGE INTO MyEntity
USING
(
SELECT @id as Id
@myField AS MyField
) AS entity
ON MyEntity.Id = entity.Id
WHEN MATCHED THEN
UPDATE
SET Id = @id
MyField = @myField
WHEN NOT MATCHED THEN
INSERT (Id, MyField)
VALUES (@Id, @myField);"
object[] parameters = {
new SqlParameter("@id", entity.Id),
new SqlParameter("@myField", entity.myField)
};
context.Database.ExecuteSqlCommand(sql, parameters);
}
Это не так, потому что он работает вне абстракции EF над объектами, но это позволит вам использовать команду MERGE
.
Ответ 2
Я использую AddOrUpdate в этой ситуации. Однако я считаю, что он сначала запрашивает базу данных, чтобы принять решение о вставке или обновлении.
context.MyEntities.AddOrUpdate(e => e.Id, entity);
Update:
Я просмотрел файлы журнала отладки. Сначала он запускается:
SELECT TOP (2) ... WHERE 1 = [Extent1].[Id]
Затем он запускается либо:
INSERT [dbo].[TestTable](...) VALUES (...)
SELECT [Id]
FROM [dbo].[TestTable]
WHERE @@ROWCOUNT > 0 AND [Id] = scope_identity()
ИЛИ
UPDATE [dbo].[TestTable]
SET ...
WHERE ([Id] = @2)
Обновление 2:
Здесь интересный метод расширения использует MERGE:
https://gist.github.com/ondravondra/4001192
Ответ 3
AddOrUpdate - хорошее решение, но не масштабируемое. Для того чтобы проверить, существует ли уже существующая сущность, и один раунд туда и обратно, требуется одна обратная связь с базой данных. Таким образом, если вы сохраните 1000 объектов, будет выполняться обратная связь базы данных 2000.
Отказ от ответственности: Я являюсь владельцем проекта Расширения платформы Entity
Эта библиотека позволяет выполнять операцию слияния в Entity Framework одновременно с резким повышением производительности. Для сохранения 1000 объектов потребуется только 1 обратная связь по базе данных.
// Easy to use
context.BulkMerge(customers)
// Easy to customize
context.BulkMerge(customers, operation => {
operation.ColumnPrimaryKeyExpression =
customer => customer.Code;
});
Ответ 4
Единственный способ, которым вы можете изменить INSERT или UPDATE sql, которые создает Entity Framework, - это когда вы настраиваете свою модель для использования хранимых процедур.
Затем вы можете изменить sql, сгенерированный в Up up, для создания процедур вставки и обновления для использования вашего MERGE sql, а не INSERT и UPDATE
CREATE PROCEDURE [dbo].[Blog_Insert]
@Name nvarchar(max),
@Url nvarchar(max)
AS
BEGIN
-- Your Merge Sql goes here
--And you need to use MERGE OUTPUT to get the primary key
--instead of SCOPE_IDENTITY()
--SELECT SCOPE_IDENTITY() AS BlogId
END