Home > Enterprise >  Filter IQueryable
Filter IQueryable

Time:08-25

I had a tip that using ToList or ToListAsync is bad for performance and we have to use IQueryable to get data from database.

I tested it and it was correct, I got at least 2 times more performance doing it. Except one problem, I can't filter the data.

q.Where(x => x.Categories != null && 
             x.Categories.Any(y => dto.Categories.Contains(y.Id)))

Here I want to filter from the list of Ids that user sends as category Ids but it throws error and explicitly says use ToList

Is there a way to filter IQueryable without using ToList?

public class ProductEntity : BaseEntity {
    public string? Title { get; set; }
    public string? Subtitle { get; set; }
    public string? Description { get; set; }
    public IEnumerable<CategoryEntity>? Categories { get; set; }
}


public class ProductFilterDto {
    public IEnumerable<Guid>? Categories { get; set; }
}  




System.InvalidOperationException: The LINQ expression 'DbSet<ProductEntity>()
    .Where(p => p.UseCase == __dto_UseCase_0)
    .Where(p => MaterializeCollectionNavigation(
        Navigation: ProductEntity.Categories,
        subquery: DbSet<Dictionary<string, object>>("CategoryEntityProductEntity")
            .Where(c => EF.Property<Guid?>(p, "Id") != null && object.Equals(
                objA: (object)EF.Property<Guid?>(p, "Id"), 
                objB: (object)EF.Property<Guid?>(c, "ProductsId")))
            .Join(
                inner: DbSet<CategoryEntity>(), 
                outerKeySelector: c => EF.Property<Guid?>(c, "CategoriesId"), 
                innerKeySelector: c0 => EF.Property<Guid?>(c0, "Id"), 
                resultSelector: (c, c0) => new TransparentIdentifier<Dictionary<string, object>, CategoryEntity>(
                    Outer = c, 
                    Inner = c0
                ))
            .Select(ti => ti.Inner)) != null && DbSet<Dictionary<string, object>>("CategoryEntityProductEntity")
        .Where(c1 => EF.Property<Guid?>(p, "Id") != null && object.Equals(
            objA: (object)EF.Property<Guid?>(p, "Id"), 
            objB: (object)EF.Property<Guid?>(c1, "ProductsId")))
        .Join(
            inner: DbSet<CategoryEntity>(), 
            outerKeySelector: c1 => EF.Property<Guid?>(c1, "CategoriesId"), 
            innerKeySelector: c2 => EF.Property<Guid?>(c2, "Id"), 
            resultSelector: (c1, c2) => new TransparentIdentifier<Dictionary<string, object>, CategoryEntity>(
                Outer = c1, 
                Inner = c2
            ))
        .Any(ti0 => __dto_Categories_1
            .Contains(ti0.Inner.Id)))' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.

CodePudding user response:

Plus 1 on the comments for null check inside of the expression. EF will automatically accommodate if a Product does not have any Categories. EF's treatment of Linq is to build SQL rather than operate against objects in memory. So checks that you would use for in-memory assertions can end up causing translation issues in EF queries.

The Null-ability of the collection inside of the DTO could also possibly result in issues. To be safe I would recommend trying something like:

if (dto.Categories != null)
{
    var categoryIds = dto.Categories.ToList();
    q.Where(x => x.Catgories.Any(y => categoryIds.Contains(y.Id)));
}
  • Related