EF Core迁移后反射构建Contains表达式树遇空值错误
解决EF Core中Expression Tree构建导致的Null值错误
这个问题我之前帮别人排查过,核心原因是EF Core和旧版EF在处理Nullable类型的表达式转换时的行为差异:旧版EF会自动为Nullable类型生成SQL层面的null检查,但EF Core的表达式解析更严格,当数据库字段存在null值时,直接对NullableConvert操作会触发内存中的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
相关产品推荐
相关产品推荐

