47

In Entity Framework Core I have the following Entity:

public class File {
  public Int32 Id { get; set; }
  public Byte[] Content { get; set; }
  public String Name { get; set; }
}

And I have a list of files ids which I need to delete:

List<Int32> ids = new List<Int32> { 4, 6, 8 }; // Ids example

How can I delete the 3 files without loading each file Content property?

_context.Files.Remove(??);

I do not want to load each file Content property as it is big in size.

Miguel Moura
  • 36,732
  • 85
  • 259
  • 481

5 Answers5

29

If you are sure the all Ids exist in the database and context does not contain (is not tracking) other entities with the same keys, you can use simple fake (stub) entities:

_context.RemoveRange(ids.Select(id => new File { Id = id }));

To avoid problem with non existing ids, you can get the existing ids from the database:

var existingIds = _context.Files.Where(f => ids.Contains(f.Id)).Select(f => f.Id).ToList();

_context.RemoveRange(existingIds.Select(id => new File { Id = id }));

To avoid tracking entity problem, you can use the FindTracked custom extension method from my answer to Delete loaded and unloaded objects by ID in EntityFrameworkCore and combine it with any of the above.

var existingIds = _context.Files.Where(f => ids.Contains(f.Id)).Select(f => f.Id).ToList();

_context.RemoveRange(
    existingIds.Select(id => _context.FindTracked(id) ?? new File { Id = id }));
Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343
  • 13
    `.ToList();` != "without loading them into memory" – Toolkit Dec 30 '18 at 11:17
  • `RemoveRange` fires as many executions as there are elements in the list. EF is good only for demos – Toolkit Dec 30 '18 at 11:32
  • 12
    @Toolkit (1) Loading PKs is not the same as loading full records. (2) EF indeed is not good for mass modifications – Ivan Stoev Dec 30 '18 at 12:50
  • This is not a proper approach. Check my answer https://stackoverflow.com/a/53977262/631527 – Toolkit Dec 30 '18 at 13:02
  • 8
    @Toolkit Not a "proper"? I don't need to check your approach. I'm aware of that **3rd party** library, but it's not EF **out of the box** solution (in fact it bypasses EF). One can use plain SQL with the same effect. This doesn't mean the library is not good - it is, just it isn't "standard" solution, and some people don't want/are not allowed to use 3rd party libraries. Anyway, it's your right to have a different opinion, just don't tell me what is "proper". – Ivan Stoev Dec 30 '18 at 13:21
  • and where did it say it should be "out of the box"? Anyway `Database.ExecuteSqlCommand` is out of the box. And it doesn't "bypass EF", what does it even mean – Toolkit Dec 30 '18 at 13:47
25

Entity tracking can work manually and without any database call, so long as you can uniquely identify the entity.

What you are after is documented here.

var entity = new EntityModel {
   Id = yourId
};

var entry = context.Entry(entity);
entry.State = EntityState.Deleted;
context.SaveChanges();

Which is the same as ...

var entity = new EntityModel {
   Id = yourId
};

var entry = context.Entry(entity);
context.Remove(entry);
context.SaveChanges();
Matt
  • 873
  • 1
  • 9
  • 24
  • 4
    This is the only way I know, but I would really like if it could do it from a Queryable. – Savo Pejović Feb 02 '21 at 10:17
  • 3
    @Ablue I can't believe I've used EF for so long and just learned this trick. Absolutely brilliant. I'd buy you a drink right now if I knew how to send you money. – Alex Dresko Mar 10 '22 at 15:12
18

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
6

Install Z.EntityFramework.Extensions or Z.EntityFramework.Extensions.EFCore package according to your dotnet version.

Then use DeleteFromQuery() or DeleteFromQueryAsync() method after your query.

await _dbContext.MyTable.Where(w => w.TypeId == 5).DeleteFromQueryAsync();

DeleteFromQuery gives you access to directly execute a DELETE statement in the database and provide a HUGE performance improvement without select and load objects.

Performance Comparisons :

Operations : 1,000 Entitie - 2,000 Entities - 5,000 Entities

SaveChange : 1,000 ms - 2,000 ms - 5,000 ms

DeleteFromQuery : 1 ms - 1 ms - 1 ms

M Komaei
  • 7,006
  • 2
  • 28
  • 34
-5

You can try EntityFramework-Plus and Database.BeginTransaction()

var db = new YourDbContext();
var dbContextTransaction = db.Database.BeginTransaction();
ctx.Users.Where(x => x.LastLoginDate < DateTime.Now.AddYears(-2)).Delete();
//some other DB actions 
db.SaveChanges();
dbContextTransaction.Commit();

This way Delete is committed with the transaction

Update - it is now a paid version, thus the downvotes I guess

Toolkit
  • 10,779
  • 8
  • 59
  • 68
  • internally it uses raw SQL code, see https://github.com/zzzprojects/EntityFramework-Plus/blob/master/src/shared/Z.EF.Plus.BatchDelete.Shared/BatchDelete.cs – Paolo Fulgoni Nov 18 '19 at 15:42
  • 1
    the library is also mentioned here https://learn.microsoft.com/en-us/ef/core/extensions/ – Paolo Fulgoni Nov 18 '19 at 16:06