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

Я удаляю несколько элементов из таблицы, используя 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

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

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

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

Ответ 2

EntityFramework 6 упростил это с помощью .RemoveRange().

Пример:

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

Ответ 3

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

Хорошо, да, кроме того, что вы можете сделать это в двухстрочный:

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

Ответ 4

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

Ответ 5

Я знаю это довольно поздно, но в случае, если кому-то нужно простое решение, здорово, вы также можете добавить предложение 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.

Update: Вышеприведенное решение не будет работать, когда 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);
        }

Он требует немного отражения, но работает хорошо.

Ответ 6

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

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

Ответ 7

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

Ответ 8

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

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

Ответ 9

Для EF 4.1,

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

Ответ 10

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

В этом примере у меня есть две таблицы List и ListItems. Мне нужен быстрый способ удалить все 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 с помощью расширения.

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

Ответ 11

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

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

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

Ответ 12

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

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

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

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

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

Ответ 13

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

Ответ 14

Вы можете выполнить 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(); 
}

Ответ 15

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

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

context.Widgets.DeleteAllOnSubmit(widgetList);

context.SubmitChanges();

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

Ответ 16

EF 6. = >

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

Ответ 17

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

Ответ 18

См. ответ "любимый бит кода", который работает

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

     // 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;
    }

Ответ 19

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

Пример:

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

Ответ 20

 int id = 5;
 db.tablename.RemoveRange(db.tablename.Where(c => c.firstid == id));