Подтвердить что ты не робот

Как я могу использовать 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.

4b9b3361

Ответ 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