Як видалити декілька рядків у 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.