Search code examples
c#entity-framework-coredbcontextsoft-deleteglobal-query-filter

Apply Gobal Query Filter on FindAsync


I implemented a soft-delete query behaviour in my ASP.NET Core API:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
   //...
   modelBuilder.Entity<OneOfMyEntity>().HasQueryFilter(entity => !entity.IsDeleted);
   modelBuilder.Entity<AnotherEntity>().HasQueryFilter(entity => !entity.IsDeleted);
}

Now this works well in two of my frequently used methods in my Repository that encapsulates my DbContext:

public async Task<List<TEntity>> GetAllExcludeDeleted()
{
   return await _context.Set<TEntity>().ToListAsync();
}
public async Task<List<TEntity>> GetAll()
{
   return await _context.Set<TEntity>().IgnoreQueryFilters().ToListAsync();
}

But then, when I was writing integration tests on my repository, I realized that it is not a solution for a DbContext.FindAsync(int) method:

public async Task<TEntity> GetById(int id)
{
   return await _context.Set<TEntity>().FindAsync(id);
}

My solution

I can imagine handling this problem with replacing FindAsync(id) with FirstOrDefault(e => !e.IsDeleted)

Actual question

What do you guys think, what would be a better solution than replacing FindAsync with a Linq query? Does FirstOrDefault has some extreme performance downgrade compared to FindAsync?


Solution

  • Does FirstOrDefault has some extreme performance downgrade compared to FindAsync?

    Actually Find{Async} uses FirstOrDefault{Async} when the entity with specified key is not tracked (contained) in the local cache.

    So the only benefit of Find{Async} is when used multiple times and the target entities are cached locally, as it avoids database trip.

    From the other side, Find{Async} does not support eager loading (Include / ThenInclude), and usually is used in short lived contexts for one time seeks for a specific keys, so normally there is no real benefit of using it.

    Another difference though, which has nothing to do with performance is that Find{Async} allows you to locate the new uncommit entities (with state Added), while all other methods query database and return only existing (or marked as Deleted) entities. But it can't and doesn't apply global query filters.

    So it really depends of the concrete usage. I would say in most common cases you simply use FirstOrDefault{Async}, and in very rare cases - Find{Async}. I guess most of the "repository" examples use Find{Async} because it has more compact syntax, and not because of the performance or its primary purpose.