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

EF Core迁移后反射构建Contains表达式树遇空值错误

解决EF Core中Expression Tree构建导致的Null值错误

这个问题我之前帮别人排查过,核心原因是EF Core和旧版EF在处理Nullable类型的表达式转换时的行为差异:旧版EF会自动为Nullable类型生成SQL层面的null检查,但EF Core的表达式解析更严格,当数据库字段存在null值时,直接对Nullable做Convert操作会触发内存中的null引用错误。

问题根源分析

你的代码里用了Expression.Convert(property, searchValuesType),如果property对应的数据库字段是Nullable类型(比如int?),当数据库里有null值时:

  • 旧版EF会把这个转换逻辑翻译成SQL的CAST,同时自动处理null情况;
  • EF Core在某些场景下会先把数据加载到内存中评估表达式,这时候对null的Nullable调用转换方法就会抛出Data is Null的错误。

修复方案

我们需要在表达式中显式添加null检查逻辑,确保只有非null的字段才会参与Contains判断。修改后的代码如下:

public static IQueryable Where(this IQueryable source, string propertyName, IEnumerable searchValues)
{
    // 获取目标实体类型
    var targetType = source.GetType().GetGenericArguments().FirstOrDefault();
    if (targetType == null) 
        throw new ArgumentException("Source must be IEnumerable<T>", nameof(source));

    // 获取搜索值的元素类型
    Type searchValuesType = searchValues.GetType().IsArray 
        ? searchValues.GetType().GetElementType() 
        : searchValues.GetType().GetGenericArguments().FirstOrDefault();
    if (searchValuesType == null) 
        throw new ArgumentException("Search values must be IEnumerable<T>", nameof(searchValues));

    // 构建参数表达式 i => ...
    var parameter = Expression.Parameter(targetType, "i");
    // 获取属性访问表达式
    var property = Expression.Property(parameter, propertyName);
    var propertyType = property.Type;

    // 处理Nullable类型的情况
    Expression containsBody;
    Type underlyingNullableType = Nullable.GetUnderlyingType(propertyType);
    
    if (underlyingNullableType != null)
    {
        // 1. 先检查属性不为null
        var notNullCondition = Expression.NotEqual(property, Expression.Constant(null, propertyType));
        // 2. 转换非null的属性值到搜索值类型
        var convertedProperty = Expression.Convert(property, searchValuesType);
        // 3. 构建Contains调用
        var containsCall = Expression.Call(
            typeof(Enumerable), 
            "Contains", 
            new[] { searchValuesType }, 
            Expression.Constant(searchValues), 
            convertedProperty);
        // 合并条件:属性不为null 且 在搜索列表中
        containsBody = Expression.AndAlso(notNullCondition, containsCall);
    }
    else
    {
        // 非Nullable类型,直接构建Contains调用
        var convertedProperty = Expression.Convert(property, searchValuesType);
        containsBody = Expression.Call(
            typeof(Enumerable), 
            "Contains", 
            new[] { searchValuesType }, 
            Expression.Constant(searchValues), 
            convertedProperty);
    }

    // 构建Lambda表达式并应用到Where方法
    var lambda = Expression.Lambda(containsBody, parameter);
    return source.Provider.CreateQuery(
        Expression.Call(
            typeof(Queryable), 
            "Where", 
            new[] { targetType }, 
            source.Expression, 
            lambda));
}

关键修改点说明

  • 添加Nullable类型判断:通过Nullable.GetUnderlyingType识别属性是否为Nullable类型;
  • 显式null检查:用Expression.AndAlso把property != null和Contains逻辑合并,确保只有非null值才会进入Contains判断;
  • 保持类型安全:转换操作只针对非null的属性值,避免内存中对null的Nullable执行转换。

另外还有个小建议:如果searchValues是空集合,最好提前处理(直接返回空的IQueryable),避免生成无效的SQL或者不必要的表达式评估。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.13 08:52:56