在C#中如何实现Func<T1, bool>到Func<T2, bool>的映射?
嘿,这个需求是可以实现的,但直接用AutoMapper的Map方法转换委托肯定行不通——AutoMapper本来就不是干这个的,它主要负责对象属性的映射,对委托这种编译后的逻辑块没辙。不过我们有两种思路来解决这个问题,取决于你能不能调整原有的委托定义:
一、最优解:用表达式树转换(支持EF服务器端查询)
如果你能把原来的Func<CarDto, bool>改成表达式树Expression<Func<CarDto, bool>>,那我们可以通过解析表达式结构、替换参数类型的方式,生成针对Car类型的查询表达式。这种方式的好处是EF可以直接把表达式解析成SQL,避免把全表数据加载到内存再过滤(也就是服务器端评估),性能要好得多。
实现步骤:
- 定义一个表达式访问器,用来替换表达式中的参数类型:
public class ParameterTypeSwitchVisitor<TFrom, TTo> : ExpressionVisitor { private readonly ParameterExpression _targetParam; public ParameterTypeSwitchVisitor(ParameterExpression targetParam) { _targetParam = targetParam; } protected override Expression VisitParameter(ParameterExpression node) { // 把原表达式中TFrom类型的参数,替换成TTo类型的新参数 return node.Type == typeof(TFrom) ? _targetParam : base.VisitParameter(node); } }
- 写一个通用的表达式转换方法:
public static Expression<Func<TTo, bool>> ConvertQueryExpression<TFrom, TTo>(Expression<Func<TFrom, bool>> sourceExpression) { // 创建新的参数(类型为TTo,和原参数同名) var newParam = Expression.Parameter(typeof(TTo), sourceExpression.Parameters[0].Name); // 遍历表达式树,替换参数类型 var visitor = new ParameterTypeSwitchVisitor<TFrom, TTo>(newParam); var updatedBody = visitor.Visit(sourceExpression.Body); // 构建并返回新的表达式树 return Expression.Lambda<Func<TTo, bool>>(updatedBody, newParam); }
- 实际使用示例:
// 定义针对CarDto的表达式树(不要编译成Func) Expression<Func<CarDto, bool>> dtoQuery = carDto => carDto.Name == "BMW"; // 转换为针对Car的表达式树 var carQuery = ConvertQueryExpression<CarDto, Car>(dtoQuery); // 直接传入DbSet的Where,EF会自动解析成SQL查询 var bmwCars = someDbContext.Cars.Where(carQuery).ToList(); // 如果确实需要编译成Func<Car, bool>,可以这样做: Func<Car, bool> carFilter = carQuery.Compile();
二、妥协方案:包装委托(仅适用于小数据量)
如果你必须使用已有的Func<CarDto, bool>委托(已经编译好,没办法解析内部逻辑),那只能通过“先映射对象,再用原委托判断”的方式包装成Func<Car, bool>。但要注意:这种方式会触发EF的客户端评估——也就是EF会先把整个Cars表的数据加载到内存,再逐个映射成CarDto后过滤,数据量大的时候性能会非常差。
实现示例:
// 已有的Func<CarDto, bool>委托 var dtoFilter = new Func<CarDto, bool>(carDto => carDto.Name == "BMW"); // 先配置AutoMapper的Car到CarDto映射(确保属性对应) var mapperConfig = new MapperConfiguration(cfg => cfg.CreateMap<Car, CarDto>()); var mapper = mapperConfig.CreateMapper(); // 包装成Func<Car, bool> Func<Car, bool> carFilter = car => dtoFilter(mapper.Map<CarDto>(car)); // 使用时注意:这会加载全表数据到内存后过滤 var bmwCars = someDbContext.Cars.Where(carFilter).ToList();
总结
- 优先选择表达式树转换的方式,既能满足你的需求,又能保留EF的服务器端查询性能;
- 万不得已才用委托包装的方式,一定要注意数据量的问题。
内容的提问来源于stack exchange,提问作者Lab Lab
相关产品推荐
相关产品推荐

