You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

EF Core查询过滤器中添加空值检查的最优实现方案咨询

EF Core查询过滤器优化:包含空值记录的高效实现

需求说明

当DbContext中的指定属性(如TenantId)有值时,查询需要返回两类记录:

  • 实体中对应属性值与DbContext属性值匹配的记录
  • 实体中对应属性值为null的记录

举个例子:
Flat表有3条记录:

  • Flat 1:TenantId = 222
  • Flat 2:TenantId = 333
  • Flat 3:TenantId = NULL

当DbContext的TenantId设为333时,查询需返回Flat 2和Flat 3。

现有实现代码

我已编写泛型扩展方法,通过传入属性名与过滤值表达式的字典来实现通用过滤,核心代码如下:

public static class ModelBuilderExtensions
{
   public static void ApplyQueryFiltersForProperties<TProp>(this ModelBuilder builder,
        Dictionary<string, Expression<Func<TProp>>> propertiesToFilter)
    {
        foreach (var (propertyName, expression) in propertiesToFilter)
        {
            foreach (var entityType in builder.Model.GetEntityTypes())
            {
                var tenantProp = entityType.GetProperties().FirstOrDefault(p => p.Name == propertyName);
                if (tenantProp == null)
                    continue;

                var entityParam = Expression.Parameter(entityType.ClrType, "e");

                var contextPropertyAccess = expression.Body;

                var propertyExpression = GetPropertyExpression(entityParam, tenantProp);
                if (propertyExpression.Type != contextPropertyAccess.Type)
                    propertyExpression = Expression.Convert(propertyExpression, contextPropertyAccess.Type);

                // ctx.Property == null || ctx.Property == e.Property
                var filterBody = (Expression) Expression.OrElse(
                    Expression.Equal(contextPropertyAccess, Expression.Default(contextPropertyAccess.Type)),
                    Expression.Equal(contextPropertyAccess, propertyExpression));

                // 添加:实体属性为null时也纳入结果
                filterBody = (Expression) Expression.Or(
                    filterBody,
                    Expression.Equal(propertyExpression, Expression.Default(contextPropertyAccess.Type)));

                var filterLambda = entityType.GetQueryFilter();

                // 合并已有过滤器
                if (filterLambda != null)
                {
                    filterBody = ReplacingExpressionVisitor.Replace(entityParam, filterLambda.Parameters[0], filterBody);
                    filterBody = Expression.AndAlso(filterLambda.Body, filterBody);
                    filterLambda = Expression.Lambda(filterBody, filterLambda.Parameters);
                }
                else
                {
                    filterLambda = Expression.Lambda(filterBody, entityParam);
                }

                entityType.SetQueryFilter(filterLambda);
            }
        }
    }

    private static Expression GetPropertyExpression(Expression objExpression, IProperty property)
    {
        Expression propExpression;
        if (property.PropertyInfo == null)
        {
            // 影子属性,通过EF.Property访问
            propExpression = Expression.Call(typeof(EF), nameof(EF.Property), new[] {property.ClrType},
                objExpression, Expression.Constant(property.Name));
        }
        else
        {
            // 常规属性,直接访问成员
            propExpression = Expression.MakeMemberAccess(objExpression, property.PropertyInfo);
        }

        return propExpression;
    }
}

当前问题

上述代码逻辑能满足需求,但生成的SQL语句极为复杂,采用了多个CASE语句结合按位或的写法,可能存在性能隐患:

WHERE ((CASE
          WHEN (@__ef_filter__p_0 = CAST(1 AS bit)) OR ((@__ef_filter__TenantId_1 = [b].[TenantId]) AND [b].[TenantId] IS NOT NULL) THEN CAST(1 AS bit)
          ELSE CAST(0 AS bit)
      END | CASE
          WHEN [b].[TenantId] IS NULL THEN CAST(1 AS bit)
          ELSE CAST(0 AS bit)
      END) = CAST(1 AS bit))

优化咨询

当前实现是否为最优方案?有没有更高效的方式来实现这个需求?


内容的提问来源于stack exchange,提问作者Paul

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 00:20:52