Как удалить несколько строк в Entity Framework (без foreach)


305

Я удаляю несколько элементов из таблицы, используя Entity Framework. Нет внешнего ключа / родительского объекта, поэтому я не могу справиться с этим с помощью OnDeleteCascade.

Прямо сейчас я делаю это:

var widgets = context.Widgets
    .Where(w => w.WidgetId == widgetId);

foreach (Widget widget in widgets)
{
    context.Widgets.DeleteObject(widget);
}
context.SaveChanges();

Это работает, но foreach вызывает меня. Я использую EF4, но я не хочу выполнять SQL. Я просто хочу убедиться, что я ничего не пропустил - это так хорошо, как получается, верно? Я могу абстрагировать его с помощью метода расширения или помощника, но где-то мы все еще будем делать foreach, верно?


1
Вы можете вернуться к принятому ответу.
— Эрик Дж.

1
Если вы хотите оставаться в курсе, возможно, вы захотите проверить мой ответ здесь stackoverflow.com/a/35033286/274589
— Ади

Ответы:


49

Если вы не хотите выполнять SQL напрямую, вызов DeleteObject в цикле - это лучшее, что вы можете сделать сегодня.

Однако вы можете выполнить SQL и сделать его полностью универсальным с помощью метода расширения, используя подход, который я описал здесь .

Хотя этот ответ был для 3,5. Для 4.0 я бы, вероятно, использовал новый API-интерфейс ExecuteStoreCommand, вместо того, чтобы переходить к StoreConnection.


ExecuteStoreCommand - неправильный способ. DeleAllSubmit работает в linq to sql, но не в рамках сущностей. Я хочу такой же вариант в рамках сущности.
— Hiral

653

EntityFramework 6 сделал это немного проще с .RemoveRange().

Пример:

db.People.RemoveRange(db.People.Where(x => x.State == "CA"));
db.SaveChanges();

31
Это именно то, что нам нужно ... За исключением случаев, когда я использую его в достаточно большом диапазоне, я получаю исключение нехватки памяти! Я думал, что весь смысл RemoveRange состоял в том, чтобы передать обработку в базу данных, но, очевидно, нет.
— Самер Адра

это WAAAYYYY быстрее, чем установка состояния Deleted для каждой сущности!
— Джертер

54
Конечно, этот ответ проще, но с точки зрения производительности он может быть не очень хорошим. Зачем? то, что он делает, точно так же, как удаление его в цикле foreach, сначала он выбирает все строки, а затем удаляет по одной, только выигрыш для сохранения: «DetectChanges будет вызываться один раз перед удалением любых объектов и больше не будет вызываться» rest то же самое, попробуйте использовать инструмент, чтобы увидеть сгенерированный sql.
— Аншул Нигам

6
Для достаточно большого диапазона попробуйте что-то вроде .Take (10000) и циклическое выполнение до RemoveRange (...). Count () == 0.
— Eric J.

20
Проблема заключается в том, что входной параметр RemoveRange является IEnumerable, поэтому для выполнения удаления он перечисляет все сущности и запускает 1 запрос DELETE для каждой сущности.
— Буби

74

это так хорошо, как получается, верно? Я могу абстрагировать его с помощью метода расширения или помощника, но где-то мы все еще будем делать foreach, верно?

Ну, да, кроме того, что вы можете сделать это в два ряда:

context.Widgets.Where(w => w.WidgetId == widgetId)
               .ToList().ForEach(context.Widgets.DeleteObject);
context.SaveChanges();

76
Вы делаете ToList (), который побеждает цель. Чем это отличается от оригинального решения?
— Лахра

3
У меня проблемы, так как у меня есть только метод Remove в объекте контекста.
— Пн

2
Это определенно не подходящее решение, когда ожидается миллион строк (или даже несколько сотен). Однако, если мы точно знаем, что будет только несколько строк, это решение будет опрятным и работает отлично. Да, это потребовало бы нескольких обращений к БД, но, на мой взгляд, потерянная абстракция, связанная с вызовом SQL, напрямую перевешивает преимущества.
— Йогстер

Entity Framework, как следует из названия, лучше всего работает с данными на уровне объекта. Операции с большими объемами данных лучше всего обрабатываются старыми добрыми хранимыми процессами. По производительности они являются лучшими вариантами и будут превосходить любую логику EF, требующую цикла.
— Paceman

72
using (var context = new DatabaseEntities())
{
    context.ExecuteStoreCommand("DELETE FROM YOURTABLE WHERE CustomerID = {0}", customerId);
}

Но как вы можете сделать это с помощью списка идентификаторов? Это решение не очень хорошо обрабатывает «списки».
— JesseNewman19

11
@ JesseNewman19 Если у вас уже есть список идентификаторов, используйте a WHERE IN ({0}), и тогда второй аргумент должен быть String.Join(",", idList).
— Лэнгдон

@Langdon, который не будет работать, потому что он отправит команду в sql следующим образом: WHERE IN ("1, 2, 3"). Затем база данных выдает ошибку, потому что вы передали ей строку вместо списка целых чисел.
— JesseNewman19

Я хочу создать такое заявление с помощью LINQ. Самым близким, что я нашел, была библиотека. EntityFramework.Extended
— Jaider

Если вы используете String.Join, вам может потребоваться использовать string.Formatи передать уже сформированную строку SQL в команду. Пока в вашем списке только целые числа, нет риска инъекционной атаки. Проверьте этот вопрос: как я могу передать массив команде execute store?
— Андрей

50

Я знаю, что уже поздно, но если кому-то нужно простое решение, круто то, что вы также можете добавить к нему предложение where:

public static void DeleteWhere<T>(this DbContext db, Expression<Func<T, bool>> filter) where T : class
{
    string selectSql = db.Set<T>().Where(filter).ToString();
    string fromWhere = selectSql.Substring(selectSql.IndexOf("FROM"));
    string deleteSql = "DELETE [Extent1] " + fromWhere;
    db.Database.ExecuteSqlCommand(deleteSql);
}

Примечание: только что протестировано с MSSQL2008.

Обновить:

Приведенное выше решение не будет работать, когда EF генерирует SQL-оператор с параметрами , поэтому вот обновление для EF5 :

public static void DeleteWhere<T>(this DbContext db, Expression<Func<T, bool>> filter) where T : class
{
    var query = db.Set<T>().Where(filter);

    string selectSql = query.ToString();
    string deleteSql = "DELETE [Extent1] " + selectSql.Substring(selectSql.IndexOf("FROM"));

    var internalQuery = query.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance).Where(field => field.Name == "_internalQuery").Select(field => field.GetValue(query)).First();
    var objectQuery = internalQuery.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance).Where(field => field.Name == "_objectQuery").Select(field => field.GetValue(internalQuery)).First() as ObjectQuery;
    var parameters = objectQuery.Parameters.Select(p => new SqlParameter(p.Name, p.Value)).ToArray();

    db.Database.ExecuteSqlCommand(deleteSql, parameters);
}

Это требует немного размышлений, но работает хорошо.


Что такое DbContext? Я полагаю, ваш автоматически сгенерированный контекст сущности? У меня нет метода с именем Set <T>.
— Рабби-

@Stealth: Да, это ваш контекст данных EF, я использую сначала код, но автоматически сгенерированный контекст должен быть таким же. Извините за неверно введенное утверждение, это должно быть Set <T> () (моя компания ограничивает доступ в Интернет, я не мог вставить код, пришлось печатать вручную, так что ...), коды обновлены :)
— Thanh Nguyen

3
Это единственный ответ, который на самом деле отвечает на вопрос! Любой другой ответ удаляет каждый отдельный элемент по одному, невероятно.
— Rocklan

Это похоже на самый правильный ответ. Это позволяет удалять очень общим способом, и это правильно выгружает работу в базу данных, а не в C #.
— JesseNewman19

1
Для всех менее технических программистов я хотел бы немного подробнее рассказать о том, как реализовать это превосходное и универсальное решение, потому что оно сэкономило бы мне несколько минут времени! Продолжение в следующем комментарии ...
— jdnew18

30

Для тех, кто использует EF5, может быть использована следующая библиотека расширений: https://github.com/loresoft/EntityFramework.Extended

context.Widgets.Delete(w => w.WidgetId == widgetId);

3
Имеет проблемы с производительностью на больших таблицах, не может быть использовано в моей ситуации.
— Томас

@ Томас, какой вид исполнения вы заметили? Насколько серьезной была проблема и насколько большой была таблица? Кто-нибудь еще может это подтвердить?
— Анестис Кивраноглу

Это действительно быстро по сравнению с альтернативами там
— Jaider

Я не вижу Delete()функции в моих сущностях в EF6.
— dotNET

context.Widgets.Where(w => w.WidgetId == widgetId).Delete();это новый способ с EntityFramework.Extended
— Питер Керр

11

Тем не менее, кажется сумасшедшим, что приходится что-то вытаскивать с сервера только для того, чтобы удалить его, но, по крайней мере, вернуть только идентификаторы намного проще, чем сносить полные сущности:

var ids = from w in context.Widgets where w.WidgetId == widgetId select w.Id;
context.Widgets.RemoveRange(from id in ids.AsEnumerable() select new Widget { Id = id });

Будьте осторожны - это может не Widgetпройти проверку сущности Entity Framework, поскольку ваши объекты- заглушки имеют только инициализированное Idсвойство. Способ обойти это использовать context.Configuration.ValidateOnSaveEnabled = false(по крайней мере, в EF6). Это отключает собственную проверку Entity Framework, но, конечно, выполняет собственную проверку базы данных.
— Сэмми С.

@SammyS. Я не сталкивался с этим, поэтому не могу говорить о деталях, но кажется странным, что EF будет беспокоиться о проверке, когда все равно удаляет строку.
— Эдвард Брей,

Ты абсолютно прав. Я перепутал deleteаналогичный обходной путь для создания updateсущностей без их загрузки.
— Сэмми С.

10

EF 6.1

public void DeleteWhere<TEntity>(Expression<Func<TEntity, bool>> predicate = null) 
where TEntity : class
{
    var dbSet = context.Set<TEntity>();
    if (predicate != null)
        dbSet.RemoveRange(dbSet.Where(predicate));
    else
        dbSet.RemoveRange(dbSet);

    context.SaveChanges();
} 

Использование:

// Delete where condition is met.
DeleteWhere<MyEntity>(d => d.Name == "Something");

Or:

// delete all from entity
DeleteWhere<MyEntity>();

7
Фактически это то же самое, что и db.People.RemoveRange (db.People.Where (x => x.State == "CA")); db.SaveChanges (); Так что нет прироста производительности.
— ReinierDG

4

Для EF 4.1

var objectContext = (myEntities as IObjectContextAdapter).ObjectContext;
objectContext.ExecuteStoreCommand("delete from [myTable];");

1
Это работает, но весь смысл использования Entity Framework заключается в объектно-ориентированном способе взаимодействия с базой данных. Это просто прямой запуск SQL-запроса.
— Артуро Торрес Санчес

4

Для этого вы можете использовать библиотеки расширений, такие как EntityFramework.Extended или Z.EntityFramework.Plus.EF6, которые доступны для EF 5, 6 или Core. Эти библиотеки имеют отличную производительность, когда вам нужно удалить или обновить, и они используют LINQ. Пример для удаления ( источник плюс ):

ctx.Users.Where(x => x.LastLoginDate < DateTime.Now.AddYears(-2)) .Delete();

или ( источник расширен )

context.Users.Where(u => u.FirstName == "firstname") .Delete();

В них используются нативные операторы SQL, поэтому производительность великолепна.


Заплатите 600 $ + за массовую работу генератора sql. Шутки в сторону?
— nicolay.anykienko

@ nicolay.anykienko Когда я использовал ее, эта библиотека была бесплатной, есть другие операции, где вы должны заплатить, не так ли, я не знаю, нужно ли вам платить
— UUHHIVS

3

Самый быстрый способ удалить это использовать хранимую процедуру. Я предпочитаю хранимые процедуры в проекте базы данных, а не динамический SQL, потому что переименования будут обрабатываться правильно и иметь ошибки компилятора. Динамический SQL может ссылаться на таблицы, которые были удалены / переименованы, вызывая ошибки во время выполнения.

В этом примере у меня есть две таблицы List и ListItems. Мне нужен быстрый способ удалить все элементы списка данного списка.

CREATE TABLE [act].[Lists]
(
    [Id] INT NOT NULL PRIMARY KEY IDENTITY, 
    [Name] NVARCHAR(50) NOT NULL
)
GO
CREATE UNIQUE INDEX [IU_Name] ON [act].[Lists] ([Name])
GO
CREATE TABLE [act].[ListItems]
(
    [Id] INT NOT NULL IDENTITY, 
    [ListId] INT NOT NULL, 
    [Item] NVARCHAR(100) NOT NULL, 
    CONSTRAINT PK_ListItems_Id PRIMARY KEY NONCLUSTERED (Id),
    CONSTRAINT [FK_ListItems_Lists] FOREIGN KEY ([ListId]) REFERENCES [act].[Lists]([Id]) ON DELETE CASCADE
)
go
CREATE UNIQUE CLUSTERED INDEX IX_ListItems_Item 
ON [act].[ListItems] ([ListId], [Item]); 
GO

CREATE PROCEDURE [act].[DeleteAllItemsInList]
    @listId int
AS
    DELETE FROM act.ListItems where ListId = @listId
RETURN 0

Теперь интересная часть удаления элементов и обновления Entity Framework с использованием расширения.

public static class ListExtension
{
    public static void DeleteAllListItems(this List list, ActDbContext db)
    {
        if (list.Id > 0)
        {
            var listIdParameter = new SqlParameter("ListId", list.Id);
            db.Database.ExecuteSqlCommand("[act].[DeleteAllItemsInList] @ListId", listIdParameter);
        }
        foreach (var listItem in list.ListItems.ToList())
        {
            db.Entry(listItem).State = EntityState.Detached;
        }
    }
}

Основной код теперь можно использовать как

[TestMethod]
public void DeleteAllItemsInListAfterSavingToDatabase()
{
    using (var db = new ActDbContext())
    {
        var listName = "TestList";
        // Clean up
        var listInDb = db.Lists.Where(r => r.Name == listName).FirstOrDefault();
        if (listInDb != null)
        {
            db.Lists.Remove(listInDb);
            db.SaveChanges();
        }

        // Test
        var list = new List() { Name = listName };
        list.ListItems.Add(new ListItem() { Item = "Item 1" });
        list.ListItems.Add(new ListItem() { Item = "Item 2" });
        db.Lists.Add(list);
        db.SaveChanges();
        listInDb = db.Lists.Find(list.Id);
        Assert.AreEqual(2, list.ListItems.Count);
        list.DeleteAllListItems(db);
        db.SaveChanges();
        listInDb = db.Lists.Find(list.Id);
        Assert.AreEqual(0, list.ListItems.Count);
    }
}

Спасибо за хороший пример использования хранимой процедуры, а затем ее реализации в виде расширения с использованием кода использования.
— Гленн Гарсон

3

Если вы хотите удалить все строки таблицы, вы можете выполнить команду sql

using (var context = new DataDb())
{
     context.Database.ExecuteSqlCommand("TRUNCATE TABLE [TableName]");
}

TRUNCATE TABLE (Transact-SQL) Удаляет все строки из таблицы без регистрации удалений отдельных строк. TRUNCATE TABLE похож на оператор DELETE без предложения WHERE; однако TRUNCATE TABLE работает быстрее и использует меньше ресурсов системы и журнала транзакций.


3
Также следует отметить, что вы не можете работать truncate tableс таблицами, на которые ссылается ограничение FOREIGN KEY. (Вы можете усечь таблицу с внешним ключом, который ссылается на себя.). Документация MSDN
— широкополосный

2

UUHHIVSЭто очень элегантный и быстрый способ удаления пакетов, но его следует использовать с осторожностью:

  • автоматическое создание транзакции: ее запросы будут охватываться транзакцией
  • независимость контекста базы данных: ее выполнение не имеет ничего общего с context.SaveChanges()

Эти проблемы можно обойти, взяв под контроль транзакцию. Следующий код иллюстрирует, как пакетное удаление и массовая вставка транзакционным способом:

var repo = DataAccess.EntityRepository;
var existingData = repo.All.Where(x => x.ParentId == parentId);  

TransactionScope scope = null;
try
{
    // this starts the outer transaction 
    using (scope = new TransactionScope(TransactionScopeOption.Required))
    {
        // this starts and commits an inner transaction
        existingData.Delete();

        // var toInsert = ... 

        // this relies on EntityFramework.BulkInsert library
        repo.BulkInsert(toInsert);

        // any other context changes can be performed

        // this starts and commit an inner transaction
        DataAccess.SaveChanges();

        // this commit the outer transaction
        scope.Complete();
    }
}
catch (Exception exc)
{
    // this also rollbacks any pending transactions
    scope?.Dispose();
}

2

Entity Framework Core

3,1 3,0 2,2 2,1 2,0 1,1 1,0

using (YourContext context = new YourContext ())
{
    var widgets = context.Widgets.Where(w => w.WidgetId == widgetId);
    context.Widgets.RemoveRange(widgets);
    context.SaveChanges();
}

Резюме :

Удаляет заданную коллекцию сущностей из контекста, лежащего в основе набора, при котором каждая сущность переводится в состояние «Удалено», так что она будет удалена из базы данных при вызове SaveChanges.

Замечания :

Обратите внимание, что если для System.Data.Entity.Infrastructure.DbContextConfiguration.AutoDetectChangesEnabled задано значение true (по умолчанию), то DetectChanges будет вызываться один раз перед удалением любых сущностей и больше не будет вызываться. Это означает, что в некоторых ситуациях RemoveRange может работать значительно лучше, чем вызов Remove несколько раз. Обратите внимание, что если какая-либо сущность существует в контексте в состоянии «Добавлено», этот метод приведет к ее отсоединению от контекста. Это связано с тем, что предполагается, что добавленная сущность не существует в базе данных, поэтому попытки ее удаления не имеют смысла.


1

Вы можете выполнять SQL-запросы напрямую следующим образом:

    private int DeleteData()
{
    using (var ctx = new MyEntities(this.ConnectionString))
    {
        if (ctx != null)
        {

            //Delete command
            return ctx.ExecuteStoreCommand("DELETE FROM ALARM WHERE AlarmID > 100");

        }
    }
    return 0;
}

Для выбора мы можем использовать

using (var context = new MyContext()) 
{ 
    var blogs = context.MyTable.SqlQuery("SELECT * FROM dbo.MyTable").ToList(); 
}

Учитывая, что EF должным образом не поддерживает отображение условий удаления, это, вероятно, ваш лучший выбор для выполнения работы.
— Тони О'Хаган

1

Вы также можете использовать метод DeleteAllOnSubmit () , передав его результаты в общий список, а не в var. Таким образом, ваш foreach сводится к одной строке кода:

List<Widgets> widgetList = context.Widgets
              .Where(w => w.WidgetId == widgetId).ToList<Widgets>();

context.Widgets.DeleteAllOnSubmit(widgetList);

context.SubmitChanges();

Вероятно, он все еще использует цикл внутри.


3
Похоже, вы не понимаете, что это varтакое.
— Libern-м

1

Ответ Тана работал лучше всего для меня. Удалил все мои записи за одну поездку на сервер. Я боролся с фактическим вызовом метода расширения, поэтому думал, что поделюсь своим (EF 6):

Я добавил метод расширения к вспомогательному классу в своем проекте MVC и изменил имя на «RemoveWhere». Я вставляю dbContext в мои контроллеры, но вы также можете сделать using.

// make a list of items to delete or just use conditionals against fields
var idsToFilter = dbContext.Products
    .Where(p => p.IsExpired)
    .Select(p => p.ProductId)
    .ToList();

// build the expression
Expression<Func<Product, bool>> deleteList = 
    (a) => idsToFilter.Contains(a.ProductId);

// Run the extension method (make sure you have `using namespace` at the top)
dbContext.RemoveWhere(deleteList);

Это сгенерировало один оператор удаления для группы.


0

EF 6. =>

var assignmentAddedContent = dbHazirBot.tbl_AssignmentAddedContent.Where(a =>
a.HazirBot_CategoryAssignmentID == categoryAssignment.HazirBot_CategoryAssignmentID);
dbHazirBot.tbl_AssignmentAddedContent.RemoveRange(assignmentAddedContent);
dbHazirBot.SaveChanges();

0

Лучший : in EF6 => .RemoveRange()

Пример:

db.Table.RemoveRange(db.Table.Where(x => Field == "Something"));

14
Чем это отличается от ответа Кайла?

-1

Смотрите ответ «любимый кусочек кода», который работает

Вот как я это использовал:

     // Delete all rows from the WebLog table via the EF database context object
    // using a where clause that returns an IEnumerable typed list WebLog class 
    public IEnumerable<WebLog> DeleteAllWebLogEntries()
    {
        IEnumerable<WebLog> myEntities = context.WebLog.Where(e => e.WebLog_ID > 0);
        context.WebLog.RemoveRange(myEntities);
        context.SaveChanges();

        return myEntities;
    }

1
Чем ваш ответ отличается от ответа пользователя user1308743 ?
— Сергей Березовский

Я просто поделился рабочим примером. Все, что я могу сделать, чтобы вернуть за помощь, которую я получил здесь.
— Брайан Куинн

-3

В EF 6.2 это работает отлично, отправляя удаление непосредственно в базу данных без предварительной загрузки сущностей:

context.Widgets.Where(predicate).Delete();

С фиксированным предикатом это довольно просто:

context.Widgets.Where(w => w.WidgetId == widgetId).Delete();

И если вам нужен динамический предикат, взгляните на LINQKit (доступен пакет Nuget), в моем случае что-то вроде этого работает нормально:

Expression<Func<Widget, bool>> predicate = PredicateBuilder.New<Widget>(x => x.UserID == userID);
if (somePropertyValue != null)
{
    predicate = predicate.And(w => w.SomeProperty == somePropertyValue);
}
context.Widgets.Where(predicate).Delete();

1
С необработанным EF 6.2 это невозможно. Может быть, вы используете Z.EntityFramework.Plusили что-то подобное? ( entityframework.net/batch-delete )
— Сэмми С.

Первый - это сырой EF 6.2 и поиск работы. Второй, как я уже говорил, использует LINQKit.
— Владимир

1
Хм, я не могу найти этот метод. Не могли бы вы проверить, в каком классе и в каком пространстве имен находится этот метод?
— Сэмми С.

Я третий, что ( Delete()метод по существу не существует).
— Сумма отсутствует
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.