390

I want to delete several items from a table using Entity Framework. There is no foreign key / parent object, so I can't handle this with OnDeleteCascade.

Right now I'm doing this:

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

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

It works, but the foreach bugs me. I'm using EF4, but I don't want to execute SQL. I just want to make sure I'm not missing anything -- this is as good as it gets, right? I can abstract the code with an extension method or helper, but somewhere we're still going to be doing a foreach, right?

mfluehr
  • 2,832
  • 2
  • 23
  • 31
Jon Galloway
  • 52,327
  • 25
  • 125
  • 193

27 Answers27

784

EntityFramework 6 has made this a bit easier with .RemoveRange().

Example:

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

Warning! Do not use this on large datasets!

EF pulls all the data into memory, THEN deletes it. For smaller data sets this might not be an issue but generally avoid this style of delete unless you can guarantee you are only doing very small changes.

You could easily run your process out of memory while EF happily pulls in all the data you specified just to delete it.

Chris Weber
  • 5,555
  • 8
  • 44
  • 52
Kyle
  • 32,731
  • 39
  • 134
  • 184
  • 35
    That's exactly what we need... Except when I use it on a large enough range, I get an out-of-memory exception! I thought the whole point of RemoveRange was to pass the processing to the database, but apparently not. – Samer Adra Apr 22 '14 at 13:53
  • this is WAAAYYYY faster than setting the Deleted state to every entity! – Jerther Sep 06 '14 at 23:05
  • 68
    For sure this answer is easier but performance wise it might not be great. Why? what this exatly doet is same as deleting it in foreach loop, it first fetches all the rows and then delete is one by one, only gain is for saving "DetectChanges will be called once before delete any entities and will not be called again" rest is same, try using tool to see sql generated. – Anshul Nigam Nov 17 '14 at 07:03
  • 7
    For a large enough range, try something like .Take(10000) and looping until RemoveRange(...).Count() == 0. – Eric J. Nov 21 '14 at 17:00
  • If you need to test DateTime in your lambda reference http://stackoverflow.com/questions/9820401/dbarithmeticexpression-arguments-must-have-a-numeric-common-type – Brent Mar 02 '15 at 16:04
  • 25
    The problem is that RemoveRange input parameter is an IEnumerable so to perform delete it enumerates all the entities and run 1 DELETE query per entity. – bubi Mar 17 '15 at 09:42
  • Agree with @Anshul Nigam, this will delete one by one, so multiple round trips to the database that can impact badly your performance. – Jaider Sep 13 '16 at 17:00
  • as @bubi stated RemoveRange causes 1 DELETE query per IEnumerable. You also have to consider that RemoveRange will trigger a SELECT request PER IEnumerable entity to update its internal cache. – Moerwald May 04 '18 at 06:17
  • One way round the memory issue would be to use .Take(pageSize) and a loop to consume the entities you wish to remove in managable gulps. Though I would consider if making a half-eaten US state visible to other services would be a problem. – andrew pate Jun 18 '19 at 10:29
  • This answer fixes the performance problem: https://stackoverflow.com/a/16442521/3183349 – Doppelganger Nov 15 '19 at 15:08
  • 6
    It seems a very not effective way. I checked it in SQL Profiler: RemoveRange command actually executes SELECTcommand and SaveChanges execute DELETE command for every single record that found after the first command SEPARATELY. In my opinion, the best way is to write relevant store procedure and execute it from EF. – Lev Z Oct 28 '20 at 06:58
80
using (var context = new DatabaseEntities())
{
    context.ExecuteStoreCommand("DELETE FROM YOURTABLE WHERE CustomerID = {0}", customerId);
}

Addition: To support list of ids you can write

var listOfIds = String.Join(',',customerIds.Select(id => $"'{id}'").ToList());
var sql= $@"DELETE  [YOURTABLE] WHERE CustomerID in ({listOfIds})";

Note: if CustomerID Is a string, you should double-check for potential SQL injection risks, for integer CustomerID it’s safe

Michael Freidgeim
  • 26,542
  • 16
  • 152
  • 170
Vlad Bezden
  • 83,883
  • 25
  • 248
  • 179
  • But how can you do this with a list of ids? This solution does not handle "lists" very well. – JesseNewman19 Jul 18 '16 at 19:34
  • 12
    @JesseNewman19 If you already have a list of IDs, use a `WHERE IN ({0})`, and then the second argument should be `String.Join(",", idList)`. – Langdon Jul 25 '16 at 22:06
  • @Langdon that will not work, because it will send the command to sql like so: WHERE IN ("1, 2, 3"). The database then throws an error because you passed it a string instead of a list of integers. – JesseNewman19 Aug 01 '16 at 16:42
  • I wish to generate a statement like that with LINQ. The closest thing I found was a lib. `EntityFramework.Extended` – Jaider Sep 13 '16 at 19:15
  • 1
    If you are using `String.Join`, you may need to use `string.Format` and pass the already formed SQL string to the command. As long as your list only has integers, there's not risk of injection attack. Check this question: [how can I pass an array to a execute store command?](https://stackoverflow.com/questions/13169566/how-can-i-pass-an-array-to-a-execute-store-command) – Andrew May 17 '18 at 02:26
  • JesseNewman19, You can add this code to a foreach to delete rows from a database and pass in the customerId. I found this was the fastest way when I was deleting records from table with 250000+ rows – Kitemark76 Sep 19 '18 at 15:29
71

this is as good as it gets, right? I can abstract it with an extension method or helper, but somewhere we're still going to be doing a foreach, right?

Well, yes, except you can make it into a two-liner:

context.Widgets.Where(w => w.WidgetId == widgetId)
               .ToList().ForEach(context.Widgets.DeleteObject);
context.SaveChanges();
Klaus Byskov Pedersen
  • 117,245
  • 29
  • 183
  • 222
  • 88
    You are doing a ToList() which defeats the purpose. How is that any different from the original solution? – lahsrah May 31 '11 at 04:52
  • 3
    I have problems since I only have Remove method in context object. – Pnctovski Apr 29 '13 at 09:15
  • 2
    This is definitely not a suitable solution when a million rows (or even a few hundred) are expected. However if we know for sure there will only be a few rows this solution is neat and works perfectly well. Yes, it would involve a few round trips to the DB, but in my opinion the lost abstraction involved in calling SQL directly outweighs the benefits. – Yogster Dec 17 '14 at 10:38
  • Entity Framework, as the name suggests, works best with data at entity level. Bulk data operations are best handled by good old stored procs. Performance-wise they are by far the best options and will beat any EF logic requiring a loop. – Paceman Dec 27 '15 at 00:42
63

I know it's quite late but in case someone needs a simple solution, the cool thing is you can also add the where clause with it:

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

Note: just tested with MSSQL2008.

Update:

The solution above won't work when EF generates sql statement with parameters, so here's the update for 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);
}
        

It requires a little bit of reflection but works well.

Further update for EF Core 6 I modified it to this, no reflection.

public void RemoveWhere<T>(DbContext db, Expression<Func<T, bool>> filter) where T : class
{
    string selectSql = db.Set<T>().Where(filter).ToQueryString();
    int fromIndex = selectSql.IndexOf("FROM");
    int whereIndex = selectSql.IndexOf("WHERE");

    string fromSql = selectSql.Substring(fromIndex, whereIndex - fromIndex);
    string whereSql = selectSql.Substring(whereIndex);
    string aliasSQl = fromSql.IndexOf(" AS ") > -1 ? fromSql.Substring(fromSql.IndexOf(" AS ") + 4) : "";
    string deleteSql = string.Join(" ", "DELETE ", aliasSQl.Trim(), fromSql.Trim(), whereSql.Trim());
    db.Database.ExecuteSqlRaw(deleteSql);
}
Squirrel5853
  • 2,376
  • 1
  • 18
  • 34
Thanh Nguyen
  • 1,050
  • 7
  • 9
  • What is DbContext? I assume your auto-generated entity framework context? I do not have a method called Set. – Stealth Rabbi Aug 29 '13 at 15:24
  • @Stealth: Yea, it's your EF data context, I use code-first but the auto-generated context should be the same. Sorry for the mis-typed statement it should be Set() (my company retricts the internet access I couldn't paste the code, had to type by hand so...), codes updated :) – Thanh Nguyen Aug 30 '13 at 15:11
  • 3
    This is the only answer that actually answers the question! Every other answer deletes each individual item one at a time, unbelievable. – Rocklan Apr 12 '16 at 03:50
  • This feels like the most correct answer. It allows deletion in a very generic way, and it properly offloads the work to the database and not the C#. – JesseNewman19 Jul 18 '16 at 21:52
  • The execution command could return the number of records affected. – Bart Verkoeijen Aug 03 '16 at 10:23
  • 1
    Like JesseNewman19 says, I also feel this is the best answer. I stumbled on one case where it doesn't work though: when the entity has a many-to-many relationship with another one, the many-to-many record is not deleted. I've tried to get a hand on that many-to-many relationship through reflection, without success. Anyone has an idea how to address that ? (should I ask a new question ?) – Daniel Nov 14 '16 at 09:51
  • Thanks for this! But how do I use it if I f.e. have a dbset in my context like this: "context.Numbers" how do I execute the query (f.e.) "where Id < 10000"? I dont know how to write it. – Ravior Dec 09 '16 at 12:31
  • Okay I found out how to use this. Im using Entity Framework with MySql. I tried this function call: EFExtensions.DeleteWhere(current, k => k.Update.kID == komp.Id && k.Update.Zeitstempel < before); resulting in this deleteSql: DELETE [Extent1] FROM `dl` AS `Extent1` INNER JOIN `u` AS `Extent2` ON `Extent1`.`uID` = `Extent2`.`Id` WHERE (`Extent2`.`kID` = @p__linq__0) AND (`Extent2`.`Zeitstempel` < @p__linq__1) and this exception: "Only MySqlParameter objects may be stored". Can you help me fix this please? – Ravior Dec 09 '16 at 16:35
  • 2
    For all the less technical programmers out there, I wanted to elaborate a bit more on how to implement this excellent and generic solution, because it would have saved me a few minutes of time! Continued in next comment... – jdnew18 Mar 02 '17 at 15:44
  • First of all, note that the second version of this method (the one compatible with EF5) is an extension method for the DbContext class, so this method will need to go in a new public static class. Also, you will need to include the following namespaces in that new class for this to work: using System; using System.Data.Entity; using System.Data.Entity.Core.Objects; using System.Data.SqlClient; using System.Linq; using System.Linq.Expressions; using System.Reflection; Continued in next comment... – jdnew18 Mar 02 '17 at 15:44
  • To use this method, follow this example: using (var db = new YourDatabaseContextDerivedFromDbContext()) { Expression> myExpression = (a) => a.FieldNameInYourTable == someValue; db.DeleteWhere(myExpression); } The Expression class is in the following namespace: using System.Linq.Expressions; The Func class is in the following namespace: using System; I hope this helps! – jdnew18 Mar 02 '17 at 15:45
  • This answer is perfectly fits, without any unnecessary packages and libraries. I believe that it should be added as a default method(with small optimisation changes) to official EF package – fs_dm Aug 04 '21 at 14:46
  • Is there an update for this that works in EF Core 5+? The reflection code to get parameters doesn't work anymore. – lonix Apr 02 '22 at 16:31
  • What's the nuget package for 'System.Data.Entity.Core.Objects'? I'm having trouble resolving the type 'ObjectQuery'. – Seth Reno Jul 26 '22 at 22:46
  • Updated for .NET 6 (EF 6) ================== var query = db.Set().Where(filter); string selectSql = query.ToQueryString(); int a = selectSql.IndexOf(" AS [") + 3; int b = selectSql.IndexOf("]", a) + 1; string alias = selectSql.Substring(a, b - a); string deleteSql = selectSql.Substring(0, selectSql.IndexOf("SELECT")) + "DELETE " + alias + selectSql.Substring(selectSql.IndexOf("FROM")); db.Database.ExecuteSqlRaw(deleteSql); – Mehdi Anis Oct 26 '22 at 17:02
52

If you don't want to execute SQL directly calling DeleteObject in a loop is the best you can do today.

However you can execute SQL and still make it completely general purpose via an extension method, using the approach I describe here.

Although that answer was for 3.5. For 4.0 I would probably use the new ExecuteStoreCommand API under the hood, instead of dropping down to the StoreConnection.

Community
  • 1
  • 1
Alex James
  • 20,874
  • 3
  • 50
  • 49
31

For anyone using EF5, following extension library can be used: https://github.com/loresoft/EntityFramework.Extended

context.Widgets.Delete(w => w.WidgetId == widgetId);
andrew.fox
  • 7,435
  • 5
  • 52
  • 75
Marcelo Mason
  • 6,750
  • 2
  • 34
  • 43
21

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

Summary:

Removes the given collection of entities from the context underlying the set with each entity being put into the Deleted state such that it will be deleted from the database when SaveChanges is called.

Remarks:

Note that if System.Data.Entity.Infrastructure.DbContextConfiguration.AutoDetectChangesEnabled is set to true (which is the default), then DetectChanges will be called once before delete any entities and will not be called again. This means that in some situations RemoveRange may perform significantly better than calling Remove multiple times would do. Note that if any entity exists in the context in the Added state, then this method will cause it to be detached from the context. This is because an Added entity is assumed not to exist in the database such that trying to delete it does not make sense.

Nguyen Van Thanh
  • 805
  • 9
  • 18
20

Finally bulk delete has been introduced in Entity Framework Core 7 via the ExecuteDelete command:

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

Something to note here is that ExecuteDelete does not need a SaveChanges, as per its documentation:

This operation executes immediately against the database, rather than being deferred until DbContext.SaveChanges() is called. It also does not interact with the EF change tracker in any way: entity instances which happen to be tracked when this operation is invoked aren't taken into account, and aren't updated to reflect the changes.

I know that the question was asked for EF4, but if you upgrade this is a good alternative!

mfluehr
  • 2,832
  • 2
  • 23
  • 31
ihebiheb
  • 3,673
  • 3
  • 46
  • 55
12

Still seems crazy to have to pull anything back from the server just to delete it, but at least getting back just the IDs is a lot leaner than pulling down the full entities:

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 });
Edward Brey
  • 40,302
  • 20
  • 199
  • 253
  • 1
    Be careful - this can fail Entity Framework's entity validation because your stub `Widget` objects only have an initialized `Id` property. The way around this is to use `context.Configuration.ValidateOnSaveEnabled = false` (at least in EF6). This disables Entity Framework's own validation, but still performs the database's own validation of course. – Sammy S. May 11 '19 at 13:09
  • @SammyS. I haven't experienced that, so can't speak to the details, but it seems odd that EF would bother with validation when it's deleting the row anyway. – Edward Brey May 11 '19 at 17:13
  • You're absolutely correct. I confused the `delete` with a similar workaround for `update`ing entities without loading them. – Sammy S. May 12 '19 at 18:07
10

This answers is for EF Core 7 (I am not aware if they merged EF Core with EF now or not, before they kept the two separately).

EF Core 7 now supports ExecuteUpdate and ExecuteDelete (Bulk updates):

// Delete all Tags (BE CAREFUL!)
await context.Tags.ExecuteDeleteAsync();

// Delete Tags with a condition
await context.Tags.Where(t => t.Text.Contains(".NET")).ExecuteDeleteAsync();

The equivalent SQL queries are:

DELETE FROM [t]
FROM [Tags] AS [t]

DELETE FROM [t]
FROM [Tags] AS [t]
WHERE [t].[Text] LIKE N'%.NET%'
Luke Vo
  • 17,859
  • 21
  • 105
  • 181
9

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

Usage:

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

Or:

// delete all from entity
DeleteWhere<MyEntity>();
Prem
  • 303
  • 2
  • 9
mnsr
  • 12,337
  • 4
  • 53
  • 79
  • 12
    This is effectively the same as db.People.RemoveRange(db.People.Where(x => x.State == "CA")); db.SaveChanges(); So no performance gain. – ReinierDG Apr 11 '17 at 09:55
4

For EF 4.1,

var objectContext = (myEntities as IObjectContextAdapter).ObjectContext;
objectContext.ExecuteStoreCommand("delete from [myTable];");
Amit Pawar
  • 131
  • 2
  • 6
  • 1
    This works, but whole point of using Entity Framework is having an object-oriented way to interact with the database. This is just directly running the SQL query. – Arturo Torres Sánchez Jan 07 '15 at 22:17
4

The quickest way to delete is using a stored procedure. I prefer stored procedures in a database project over dynamic SQL because renames will be handled correctly and have compiler errors. Dynamic SQL could refer to tables that have been deleted/renamed causing run time errors.

In this example, I have two tables List and ListItems. I need a fast way to delete all the ListItems of a given list.

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

Now the interesting part of deleting the items and updating Entity framework using an extension.

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

The main code can now use it is as

[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);
    }
}
Xavier John
  • 8,474
  • 3
  • 37
  • 51
  • Thank you for a nice example of using a Stored Procedure, and then implementing it as an extension, with the Usage code. – glenn garson Apr 24 '15 at 12:40
4

You can use extensions libraries for doing that like EntityFramework.Extended or Z.EntityFramework.Plus.EF6, there are available for EF 5, 6 or Core. These libraries have great performance when you have to delete or update and they use LINQ. Example for deleting (source plus):

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

or (source extended)

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

These use native SQL statements, so performance is great.

UUHHIVS
  • 1,179
  • 11
  • 19
3

If you want to delete all rows of a table, you can execute sql command

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

TRUNCATE TABLE (Transact-SQL) Removes all rows from a table without logging the individual row deletions. TRUNCATE TABLE is similar to the DELETE statement with no WHERE clause; however, TRUNCATE TABLE is faster and uses fewer system and transaction log resources.

Amir
  • 782
  • 1
  • 9
  • 19
  • 3
    You should also mention that you can't run `truncate table` on tables that are referenced by a FOREIGN KEY constraint. (You can truncate a table that has a foreign key that references itself.). [MSDN documentation](https://msdn.microsoft.com/en-us/library/ms177570.aspx) – broadband Nov 08 '16 at 08:29
2

You can execute sql queries directly as follows :

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

For select we may use

using (var context = new MyContext()) 
{ 
    var blogs = context.MyTable.SqlQuery("SELECT * FROM dbo.MyTable").ToList(); 
}
Abhishek Sharma
  • 281
  • 2
  • 6
  • Given that EF does not properly support mapping of delete conditions this is probably your best bet for getting the job done. – Tony O'Hagan Jan 04 '16 at 22:48
2

UUHHIVS's is a very elegant and fast way for batch delete, but it must be used with care:

  • auto generation of transaction: its queries will be encompassed by a transaction
  • database context independence: its execution has nothing to do with context.SaveChanges()

These issues can be circumvented by taking control of the transaction. The following code illustrates how to batch delete and bulk insert in a transactional manner:

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();
}
Alexei - check Codidact
  • 22,016
  • 16
  • 145
  • 164
2

In EF 7 you can use bulk delete

    var ids = widgets.Select(x => x.Id).ToList();
    await _mrVodDbContext.Widgets.Where(x => ids.Contains(x.Id)).ExecuteDeleteAsync();

EF core generate

  DELETE FROM [i]
  FROM [Widgets] AS [i]
  WHERE [i].[Id] IN (4,3,2,1)

More about deleting or updating in release notes. https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-7.0/whatsnew#basic-executedelete-examples

DreamEvil
  • 169
  • 1
  • 13
1

You can also use the DeleteAllOnSubmit() method by passing it your results in a generic list rather than in var. This way your foreach reduces to one line of code:

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

context.Widgets.DeleteAllOnSubmit(widgetList);

context.SubmitChanges();

It probably still uses a loop internally though.

Hugo Nava Kopp
  • 2,906
  • 2
  • 23
  • 41
1

Thanh's answer worked best for me. Deleted all my records in a single server trip. I struggled with actually calling the extension method, so thought I would share mine (EF 6):

I added the extension method to a helper class in my MVC project and changed the name to "RemoveWhere". I inject a dbContext into my controllers, but you could also do a 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);

This generated a single delete statement for the group.

Steve Greene
  • 12,029
  • 1
  • 33
  • 54
  • Isn't this the same as the original? You are reading all the ids, then deleting the records, that's 2 SQL commands. – Neil Sep 14 '22 at 19:43
1

I came up with a great library Zack.EFCore.Batch. It will convert your expression into simple DELETE FROM .... WHERE query. (Like some answers proposed) https://github.com/yangzhongke/Zack.EFCore.Batch

The usage example:

await ctx.DeleteRangeAsync<Book>(b => b.Price > n);

The Zack.EFCore.Batch library has lots of benefits over Z.EntityFramework.Extended https://entityframework-extensions.net/ which does not have true Async methods. (They are just wrappers around sync methods) You can get lots of unexpected issues by using this library in high load environment.

Seagull
  • 3,319
  • 2
  • 31
  • 37
0

EF 6.=>

var assignmentAddedContent = dbHazirBot.tbl_AssignmentAddedContent.Where(a =>
a.HazirBot_CategoryAssignmentID == categoryAssignment.HazirBot_CategoryAssignmentID);
dbHazirBot.tbl_AssignmentAddedContent.RemoveRange(assignmentAddedContent);
dbHazirBot.SaveChanges();
Erçin Dedeoğlu
  • 4,950
  • 4
  • 49
  • 69
-1

Best : in EF6 => .RemoveRange()

Example:

db.Table.RemoveRange(db.Table.Where(x => Field == "Something"));
LOTUSMS
  • 10,317
  • 15
  • 71
  • 140
maXXis
  • 31
  • 3
-1
 context.Widgets.RemoveRange(context.Widgets.Where(w => w.WidgetId == widgetId).ToList());
                db.SaveChanges();
-1

If you are using Generic Repository:

Inside Generic repository, following could be new method.

       public void RemoveMultiple(Expression<Func<T, bool>> predicate)
        {
             IQueryable<T> query = _context.Set<T>().Where(predicate);
             _context.Set<T>().RemoveRange(query.AsNoTracking());
            
        }

Usage:

_unitOfWork.YOUR_ENTITY.RemoveMultiple(x => x.AccountId == accountId);
 _unitOfWork.Complete();
-2

See the answer 'favorite bit of code' that works

Here is how I used it:

     // 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;
    }
Brian Quinn
  • 57
  • 1
  • 4
-3

In EF 6.2 this works perfectly, sending the delete directly to the database without first loading the entities:

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

With a fixed predicate it's quite straightforward:

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

And if you need a dynamic predicate have a look at LINQKit (Nuget package available), something like this works fine in my case:

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();
Vladimir
  • 1,425
  • 16
  • 31
  • 1
    With raw EF 6.2 this is not possible. Maybe you are using `Z.EntityFramework.Plus` or something similar? (https://entityframework.net/batch-delete) – Sammy S. May 11 '19 at 13:06
  • First one is raw EF 6.2 and works find. Second one is, as I mentioned, using LINQKit. – Vladimir May 12 '19 at 14:14
  • 1
    Hmm, I can't find this method. Could you check on which class and in which namespace this method resides? – Sammy S. May 12 '19 at 18:06
  • I third that (`Delete()` method is inherently non-existent). – Sum None Aug 09 '19 at 11:35