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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 14:20:41