Як видалити декілька рядків у Entity Framework (без передбачень)


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. Я просто хочу переконатися, що я нічого не пропускаю - це так добре, як виходить, правда? Я можу абстрагувати його методом розширення або помічником, але десь ми все ще будемо робити передбачення, правда?


1
Можливо, ви хочете переглянути прийняту відповідь.
Ерік Дж.

1
Якщо ви хочете залишитися виконавцем, можливо, ви можете перевірити мою відповідь тут stackoverflow.com/a/35033286/274589
Аді,

Відповіді:


49

Якщо ви не хочете виконувати SQL безпосередньо, викликаючи DeleteObject у циклі - це найкраще, що ви можете зробити сьогодні.

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

Хоча ця відповідь була за 3.5. Для 4.0 я б, ймовірно, використовував новий API ExecuteStoreCommand під кришкою, а не опускався до StoreConnection.


ExecuteStoreCommand не є належним способом.DeleteAllSubmit працює в linq to sql, але не в рамках сутності. Я хочу той самий варіант в рамках сутності.
Хірал

653

EntityFramework 6 зробив це трохи легше .RemoveRange().

Приклад:

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

31
Саме це нам і потрібно ... За винятком випадків, коли я використовую його на досить великому діапазоні, я отримую виняток із пам'яті! Я подумав, що вся суть RemoveRange полягає в тому, щоб передати обробку в базу даних, але, мабуть, ні.
Самер Адра

це WAAAYYYY швидше, ніж встановлення видаленого стану кожному об'єкту!
Джертер

54
Напевно, ця відповідь простіша, але розумна, вона може бути не великою. Чому? те, що цей exatly doet - це те саме, що видалити його в циклі foreach, він спочатку отримує всі рядки, а потім видаляє по черзі, лише виграш - це для збереження "DetectChanges буде викликано один раз перед видаленням будь-яких об'єктів і більше не буде викликано" відпочинок те саме, спробуйте скористатися інструментом, щоб побачити генерований sql.
Аншул Нігам

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

20
Проблема полягає в тому, що вхідний параметр RemoveRange є IEnumerable, тому для виконання видалення він перераховує всі сутності та виконує 1 DELETE запит на сутність.
bubi

74

це так добре, як виходить, правда? Я можу абстрагувати його методом розширення або помічником, але десь ми все ще будемо робити передбачення, правда?

Ну так, хіба що ви можете перетворити його на двоколірне:

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

76
Ви робите ToList (), який перемагає мету. Чим це відрізняється від оригінального рішення?
lahsrah

3
У мене виникають проблеми, оскільки у мене лише метод Remove в контекстному об'єкті.
Пнкт

2
Це, безумовно, не підходить рішення, коли очікується мільйон рядків (а то й кілька сотень). Однак якщо ми точно знаємо, буде лише кілька рядків, це рішення акуратне та працює чудово. Так, це вимагало б декількох поїздок до БД, але, на мою думку, втрачена абстракція, пов'язана з викликом SQL, безпосередньо переважає переваги.
Йогстер

Entity Framework, як випливає з назви, найкраще працює з даними на рівні сутності. Операції масових даних найкраще обробляються старими хорошими збереженими документами. Виконання продуктивності - це далеко не найкращі варіанти і обіграє будь-яку логіку EF, що вимагає циклу.
Пейсман

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
Джайдер

Якщо ви використовуєте String.Join, можливо, вам доведеться використовувати string.Formatта передавати команду вже сформовану рядок SQL. Поки у вашому списку є лише цілі числа, немає ризику ін'єкційної атаки. Перевірте це питання: як я можу передати масив команді Execute store?
Андрій

50

Я знаю, що це досить пізно, але якщо комусь потрібне просте рішення, класна річ, ви також можете додати до нього пункт де:

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
Це єдина відповідь, яка насправді відповідає на питання! Кожна інша відповідь видаляє кожен окремий предмет по одному, неймовірно.
Роклен

Це відчувається як найправильніша відповідь. Це дозволяє видалити дуже загальний спосіб, і він належним чином завантажує роботу в базу даних, а не на C #.
JesseNewman19

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

30

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

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

3
Проблеми з продуктивністю на великих столах, непридатні для моєї ситуації.
Томаш

@Tomas, що за видана парфюманність ви помітили? Наскільки гостро це було питання і наскільки великим був стіл? Хтось ще може це підтвердити?
Anestis Kivranoglou

Це дуже швидко порівняти з альтернативами там
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 });

Будьте уважні - це може не вдатися до перевірки сутності Entity Framework, оскільки ваші Widgetоб'єкти stub мають лише ініціалізовану 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 $ + за генератор великої площі експлуатації. Серйозно?
nicolay.anykienko

@ nicolay.anykienko Коли я ним користувався, ця бібліотека була безкоштовною, є й інші операції, за які потрібно платити, правда, не знаю, чи потрібно платити
UUHHIVS

3

Найшвидший спосіб видалення - це використання збереженої процедури. Я віддаю перевагу збереженим процедурам в проекті баз даних над динамічним 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);
    }
}

Дякуємо за прекрасний приклад використання збереженої процедури, а потім її реалізації як розширення з кодом використання.
glenn garson

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's є дуже елегантним і швидким способом пакетного видалення, але його потрібно використовувати обережно:

  • автоматичне генерування транзакції: її запити будуть охоплені транзакцією
  • Незалежність контексту бази даних: її виконання не має нічого спільного 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

3,1 3,0 2,2 2,1 1,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. Таким чином ваш передбачення зводиться до одного рядка коду:

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

context.Widgets.DeleteAllOnSubmit(widgetList);

context.SubmitChanges();

Він, ймовірно, все ще використовує цикл всередині.


3
Схоже, ви нерозумієте, що varтаке.
Freedomn-m

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чи щось подібне? ( entitframework.net/batch-delete )
Саммі С.

Перший - це сирий EF 6.2 і працює. Другий - це, як я вже згадував, використання LINQKit.
Володимир

1
Хм, я не можу знайти цей метод. Чи можете ви перевірити, для якого класу та в якому просторі імен цей метод знаходиться?
Семмі С.

Третє, що ( Delete()метод по суті не існує).
Сума немає
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.