Как удалить несколько строк в Entity Framework Core?

Мне нужно удалить несколько строк из базы данных с помощью Entity Framework Core.

Этот код НЕ работает:

foreach (var item in items)
{
    myCollection.Remove(item);
}

потому что я получаю сообщение об ошибке "InvalidOperationException: коллекция была изменена, операция перечисления не может выполняться" после первого объекта. Другими словами, .Remove удаляет только один объект.

Ядро Entity Framework не имеет .RemoveRange, поэтому я понятия не имею, как выполнить эту операцию.

Чтобы сохранить максимальную совместимость с различными поставщиками баз данных, я бы предпочел НЕ называть context.Database.ExecuteSqlCommand("удалить из physical_table where..."). Есть ли подходящее решение? Благодарю!

Ответ 1

потому что я получаю сообщение об ошибке "InvalidOperationException: коллекция была изменена, операция перечисления не может выполняться" после первого объекта. Другими словами,.Remove удаляет только один объект.

Это не имеет никакого отношения к EF Core, и, да, .Remove() удаляет только один объект. Тем не менее, вы пытаетесь изменить коллекцию, через которую выполняете итерацию. Есть способы сделать это, но это не очень хороший путь.

Ядро Entity Framework не имеет.RemoveRange, поэтому я понятия не имею, как выполнить эту операцию.

Есть определенно, по крайней мере, несколько простых способов удалить несколько записей в EF Core. И у EF Core есть метод RemoveRange() - это метод в DbSet<TEntity>, см. Здесь в документах API (как указано в комментарии выше).

Пара вариантов:

  1. Если myCollection имеет тип, принадлежащий DbSet<TEntity>, простой вызов, такой как этот, будет делать трюк:

    _dbContext.MyEntities.RemoveRange(myCollection);
    _dbContext.SaveChanges();
    
  2. Если myCollection на самом деле является навигационным свойством у сущности, которую вы запросили, вы можете вызвать .Clear() в коллекции, а не итерации и вызова .Remove().

    var myParentEntity = _dbContext.MyParentEntities
                             .Include(x => x.MyChildrenEntities)
                             .Single(x => x.Id == id);
    myParentEntity.MyChildrenEntities.Clear();
    _dbContext.SaveChanges();
    

Как также было отмечено выше, в вашем вопросе отсутствует много контекста - должен быть опубликован более полный код. Я просто беру пару ударов в темноте, чтобы поднять вас и запустить с EF Core!

Ответ 2

Если вы хотите удалить много элементов (прочитанных сотен или более) на каком-то произвольном фильтре, наиболее эффективным способом будет так называемое "массовое удаление". EFCore.BulkExtensions позволяет это. Посмотрите пример ниже:

var toRemoveModels = DataAccess.ModelRepository.All
    .Where(m => m.Name.StartsWith("Added model"))
    .ToList();
DataAccess.ModelRepository.BulkDelete(toRemoveModels);

где фактическая реализация в контексте базы данных так же проста, как:

public void BulkDelete<TModel>(IList<TModel> entities) where TModel: class
{
    this.BulkDelete(entities, bulkConfig: null);
}

Это создаст кучу запросов, но будет по-прежнему более эффективным, чем выдача множества операторов DELETE:

SELECT [m].[Id], [m].[MakeId], [m].[Name], [m].[PriceInEur]
FROM [Model] AS [m]
WHERE [m].[Name] LIKE N'Added model' + N'%' AND (LEFT([m].[Name], LEN(N'Added model')) = N'Added model')
go
SELECT columnproperty(object_id('dbo.[Model]'),'Id','IsIdentity');
go
SELECT TOP 0 T.[Id] INTO dbo.[ModelTemp208f3efb] FROM dbo.[Model] AS T LEFT JOIN dbo.[Model] AS Source ON 1 = 0;
go
select @@trancount; SET FMTONLY ON select * from dbo.[ModelTemp208f3efb] SET FMTONLY OFF exec ..sp_tablecollations_100 N'[dbo].[ModelTemp208f3efb]'
go
insert bulk dbo.[ModelTemp208f3efb] ([Id] Int)
go
MERGE dbo.[Model] WITH (HOLDLOCK) AS T USING dbo.[ModelTemp208f3efb] AS S ON T.[Id] = S.[Id] WHEN MATCHED THEN DELETE;
go
DROP TABLE dbo.[ModelTemp208f3efb]
go

Примечание. Более эффективным способом выполнения "массового" удаления было бы предоставление IQueryable котором указывается способ получения элементов и генерируется DELETE аналогичный следующему:

DELETE FROM SomeTable
WHERE Id IN (SELECT Id FROM SomeTable WHERE ...)

Это происходит быстрее, потому что не требуется загружать объекты EF, а также создавать временную таблицу и MERGE против нее.

Я использовал библиотеку для Entity Framework 6, но не смог найти некоммерческую для EF Core.