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
?
Does
FirstOrDefault
has some extreme performance downgrade compared toFindAsync
?
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.