EF Core中用泛型类构建表达式树查询TPH架构的SignTask
问题描述
我采用TPH架构,类层次结构如下:
abstract class Task { public int A { get; set; } } abstract class DocumentTask { public virtual Document Doc { get;set; } public int B { get; set; } } class DocumentTask<TDocument> : DocumentTask where TDocument : Document, new() { public override TDocument Doc { get; set; } } /* Some classes are inherited from DocumentTask<TDocument> */ class SignTask<TDocument> : DocumentTask<TDocument> where TDocument : Document, new() { public int C { get;set; } }
我需要从数据库中获取所有指定C值的SignTask,但由于没有非泛型的SignTask类,无法编写类似db.Where(t => t is SignTask && ((SignTask)t).C == 5)的查询。我尝试直接构建表达式树,目前能筛选出SignTask<>类型的实体:
var taskTypes = typeof(SignTask<>).Assembly .GetTypes() .Where(t => (t?.BaseType?.IsGenericType ?? false) && t?.BaseType?.GetGenericTypeDefinition() == typeof(SignTask<>)).ToList(); var parameter = Expression.Parameter(typeof(Task), "e"); var body = taskTypes.Select(type => Expression.TypeIs(parameter, type)) .Aggregate<Expression>(Expression.OrElse); var predicate = Expression.Lambda<Func<Task, bool>>(body, parameter); var tasks = await _dbContext .Tasks .Where(predicate) .ToArrayAsync();
但无法按C属性进行过滤,因为无法为未指定泛型参数的泛型类型构建Expression.Parameter。请问是否有办法实现该查询?
解决方案
方法一:通过表达式树动态构建类型转换与属性判断
针对每个具体的SignTask<TDocument>实现类,我们可以先将参数表达式转换为该具体类型,再访问其C属性并与目标值比较,最后把类型判断和属性判断的条件组合起来,再聚合为最终的查询表达式:
int targetC = 5; var parameter = Expression.Parameter(typeof(Task), "e"); var conditions = new List<Expression>(); // 获取所有SignTask<>的具体实现类型 var taskTypes = typeof(SignTask<>).Assembly .GetTypes() .Where(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(SignTask<>)); foreach (var type in taskTypes) { // 将参数转换为当前SignTask具体类型 var typeCast = Expression.TypeAs(parameter, type); // 访问该类型的C属性 var cProperty = Expression.Property(typeCast, "C"); // 构建属性值与目标值相等的表达式 var equalsCondition = Expression.Equal(cProperty, Expression.Constant(targetC)); // 组合类型判断和属性判断:(e 是当前SignTask类型) 并且 (该类型的C等于目标值) var combinedCondition = Expression.AndAlso(Expression.TypeIs(parameter, type), equalsCondition); conditions.Add(combinedCondition); } // 将所有条件用OrElse聚合,匹配任意一个符合条件的SignTask具体类型即可 var finalBody = conditions.Aggregate(Expression.OrElse); var predicate = Expression.Lambda<Func<Task, bool>>(finalBody, parameter); var tasks = await _dbContext.Tasks.Where(predicate).ToArrayAsync();
方法二:新增非泛型接口统一属性访问(推荐)
如果可以修改现有类结构,建议新增一个非泛型接口,让所有SignTask<TDocument>实现该接口,这样就能直接通过接口访问C属性,无需复杂的表达式树构建:
// 新增非泛型接口 public interface ISignTask { int C { get; } } // 修改SignTask泛型类,实现ISignTask接口 class SignTask<TDocument> : DocumentTask<TDocument>, ISignTask where TDocument : Document, new() { public int C { get;set; } }
之后就能直接编写简洁的查询:
var tasks = await _dbContext.Tasks .OfType<ISignTask>() .Where(t => t.C == 5) .ToArrayAsync();
这种方式代码可读性更高,维护成本更低,是优先选择的方案。
内容的提问来源于stack exchange,提问作者Lev Kostychenko
相关产品推荐
相关产品推荐

