如何实现带Where子句的泛型表达式?实体内部列表筛选泛化
泛型表达式实现实体内部列表的多语言筛选
现有实体结构
public class FoodMenuT { public int Id {get;set;} public int LanguageId {get;set;} public string Name {get;set;} }
public class FoodMenu { public int Id {get;set;} public IEnumerable<FoodMenuT> FoodMenuTList {get;set;} }
已实现的非泛型筛选方法
针对FoodMenu的非泛型多语言筛选方法如下:
public static Expression<Func<FoodMenu, IEnumerable<FoodMenuT>>> LanguageFilter() { return e => e.FoodMenuT.Where(x => x.LanguageId == 1); }
泛化需求
希望将上述方法泛化为通用版本,签名如下:
public static Expression<Func<T, IEnumerable<TT>>> LanguageFilter<T, TT>() { }
目前已编写部分代码,但无法将获取实体列表的表达式与LanguageId筛选的Where条件表达式组合起来,现有代码片段:
public static Expression<Func<T, IEnumerable<TT>>> LanguageFilter<T, TT>() { var headerInfo = ServiceTool.ServiceProvider.GetService<IHeaderInfo>(); var entity = typeof(T); var ttEntity = typeof(TT); var propertyInfo = entity.GetProperty(ttEntity.Name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase); var arg = Expression.Parameter(entity, "e"); var property = Expression.Property(arg, propertyInfo!.Name); // e=>e.FoodMenuT var selector = Expression.Lambda(property, new ParameterExpression[] { arg }); var propertyInfo2 = ttEntity.GetProperty("LanguageId", BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase); var arg2 = Expression.Parameter(ttEntity, "x"); var property2 = Expression.Property(arg2, propertyInfo2!.Name); var languageId = Expression.Constant(headerInfo!.LanguageId); var equalLanguage = Expression.Equal(property2, languageId); // x=>x.LanguageId==1 var selector2 = Expression.Lambda<Func<TT, bool>>(equalLanguage, arg2); var lambda = Expression.Lambda<Func<T, IEnumerable<TT>>>(null, arg); return lambda; }
解决方案
要完成泛型方法,核心是构造调用Enumerable.Where方法的表达式,将列表属性和筛选条件组合起来。完整实现如下:
public static Expression<Func<T, IEnumerable<TT>>> LanguageFilter<T, TT>() { var headerInfo = ServiceTool.ServiceProvider.GetService<IHeaderInfo>(); if (headerInfo == null) throw new InvalidOperationException("IHeaderInfo service not available."); var tType = typeof(T); var ttType = typeof(TT); // 获取T中对应TT列表的属性(按命名约定:TT类名作为属性名,可根据实际情况调整逻辑) var listProperty = tType.GetProperty(ttType.Name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase); if (listProperty == null) throw new ArgumentException($"Type {tType.Name} does not have a public property named {ttType.Name}."); // 构建参数e => ... var param = Expression.Parameter(tType, "e"); // 获取e.FoodMenuT的表达式 var listExpr = Expression.Property(param, listProperty); // 构建x => x.LanguageId == headerInfo.LanguageId的表达式 var ttParam = Expression.Parameter(ttType, "x"); var languageIdProp = ttType.GetProperty("LanguageId", BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase); if (languageIdProp == null) throw new ArgumentException($"Type {ttType.Name} does not have a public LanguageId property."); var languageIdExpr = Expression.Property(ttParam, languageIdProp); var constantLanguageId = Expression.Constant(headerInfo.LanguageId); var equalExpr = Expression.Equal(languageIdExpr, constantLanguageId); var filterLambda = Expression.Lambda<Func<TT, bool>>(equalExpr, ttParam); // 获取Enumerable.Where方法(针对IEnumerable<TT>的泛型方法) var whereMethod = typeof(Enumerable).GetMethods() .Where(m => m.Name == "Where" && m.GetParameters().Length == 2) .First() .MakeGenericMethod(ttType); // 构建e.FoodMenuT.Where(x => x.LanguageId == ...)的表达式 var whereCall = Expression.Call(whereMethod, listExpr, filterLambda); // 最终组合成完整的Lambda表达式 return Expression.Lambda<Func<T, IEnumerable<TT>>>(whereCall, param); }
关键说明
- 属性查找逻辑:当前代码按TT类名查找T中的对应列表属性,若你的实际命名规则不同(比如属性名是
{TT类名}List),需要调整GetProperty的参数为$"{ttType.Name}List"。 - 方法调用构造:通过反射获取
Enumerable.Where的泛型方法,再用Expression.Call将列表表达式和筛选Lambda组合起来,这是实现泛型筛选的核心步骤。 - 异常处理:增加了必要的空值检查,避免因服务未注册、属性不存在等情况导致运行时错误。
内容的提问来源于stack exchange,提问作者Baris Yerlikaya
相关产品推荐
相关产品推荐

