Как удалить несколько строк в Entity Framework (без foreach) - PullRequest
280 голосов
/ 26 марта 2010

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

Ответы [ 19 ]

609 голосов
/ 31 января 2014

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

Пример:

db.People.RemoveRange(db.People.Where(x => x.State == "CA"));
db.SaveChanges();
74 голосов
/ 26 марта 2010

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

Ну, да, за исключением того, что вы можете превратить его в двухслойный:

context.Widgets.Where(w => w.WidgetId == widgetId)
               .ToList().ForEach(context.Widgets.DeleteObject);
context.SaveChanges();
68 голосов
/ 07 июля 2011
using (var context = new DatabaseEntities())
{
    context.ExecuteStoreCommand("DELETE FROM YOURTABLE WHERE CustomerID = {0}", customerId);
}
47 голосов
/ 26 марта 2010

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

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

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

44 голосов
/ 18 мая 2013

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

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

30 голосов
/ 13 августа 2013

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

context.Widgets.Delete(w => w.WidgetId == widgetId);
11 голосов
/ 04 февраля 2017

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

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 });
10 голосов
/ 02 мая 2014

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>();
4 голосов
/ 07 февраля 2015

Самый быстрый способ удаления - использование хранимой процедуры. Я предпочитаю хранимые процедуры в проекте базы данных, а не динамический 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 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);
    }
}
4 голосов
/ 07 марта 2013

Для EF 4.1,

var objectContext = (myEntities as IObjectContextAdapter).ObjectContext;
objectContext.ExecuteStoreCommand("delete from [myTable];");
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...