首页 > 解决方案 > C# EF Core QueryableExtensions.FirstOrDefaultAsync 奇怪的行为

问题描述

我有一个通用存储库,我将Expression<Func<TEntity, bool>> filter. 例如,这个表达式看起来像:

filterExpr = part =>
    (filter.ProductId != null ? filter.ProductId == part.ProductId : true) &&
    (filter.SerialNumber != null ? filter.SerialNumber == part.SerialNumber : true);

在存储库QueryableExtensions.FirstOrDefaultAsync(filterExpr)中,我调用了从 DbContext 获得的 DbSet:

this.dbSet = context.Set<TEntity>()
// ....
IQueryable<TEntity> query = dbSet;
return query.FirstOrDefaultAsync(filter);

ProductId如果定义了一个或SerialNumber两个,它会很好地工作。如果所有过滤器属性都是null,我希望表达式的计算结果为true并因此FirstOrDefaultAsync返回查询表中的第一个元素。但相反,EF 核心似乎试图加载整个表。我的内存消耗增加了几 GB,然后我收到了超时。当我使用 InMemoryDatabase 执行 UnitTest 时,我得到null的结果也不是我所期望的。

编辑:这是所要求的 UnitTest。

    [Fact]
    public void GetPartWithEmptyFilterTest()
    {
        var optionsBuilder = new DbContextOptionsBuilder<ProductStatusContext>();
        optionsBuilder.UseInMemoryDatabase("GetPartWithEmptyFilterTest");
        var context = new ProductStatusContext(optionsBuilder.Options);

        var part = new Part { SerialNumber = "abcdef" };

        context.Parts.Add(part);
        context.SaveChanges();

        var unitOfWork = new UnitOfWork(context);
        var service = new PartService(unitOfWork, null, null);

        service.GetPartAsync(new PartFilterResource()).Result.Should().Be(part);
    }

Edit2:我目前的解决方法是测试所有过滤器属性的null值并仅在至少在属性上不同于null

private bool AllPropertiesNull(object obj)
{
    return !obj.GetType().GetProperties().Any(propInfo => propInfo.GetValue(obj) != null);
}

接着:

if(filter != null && AllPropertiesNull(filter))
    filterExp = part => true;

但我想避免这种情况。

标签: c#genericsentity-framework-coreexpression

解决方案


根据我使用 EF Core 的经验,查询翻译器正试图消除常量谓词表达式,以便生成类似于使用条件的条件(或根本没有条件)Where

但是,它似乎可以正确处理二进制表达式(&&, ||)并且无法为条件? :表达式执行此操作。您可以将其报告给他们的 GitHub 问题跟踪器,但由于他们正在使用 EF Core 3.0,我认为它不会在早期版本中得到解决。并且 3.0 仍处于许多事情不起作用的状态,因此不能用于评估这在最终版本中是否可行。

我建议的解决方法(并且似乎在所有版本中都运行良好)是使用基于等效||的条件,例如

filterExpr = part =>
    (filter.ProductId == null || filter.ProductId == part.ProductId) &&
    (filter.SerialNumber == null || filter.SerialNumber == part.SerialNumber); 

推荐阅读