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

针对单个关联实体,如何复用表达式实现查询过滤?

EF Core关联实体过滤:一对多反向查询的表达式复用问题

背景

我首次尝试用表达式减少代码重复,针对两个关联实体分别创建了表达式过滤函数:

  • CMSite(站点)
  • CMSiteServer(站点服务器)

实体定义如下:

public class CMSite
{
   public int ID {get;set;}
   public string Name {get;set;}
   public ICollection<CMSiteServer> SiteServers { get; set; }
}

public class CMSiteServer
{
   public int ID {get;set;}
   public string Name {get;set;}
   public int SiteID {get;set;}
   [ForeignKey("CMSite")]
   public decimal SiteID { get; set; } // 注:此处存在重复定义,建议修正为统一类型
   public CMSite Site {get;set;}
}

两者为一对多关系:1个CMSite对应多个CMSiteServer。我已经为每个实体创建了过滤对象,以及对应的表达式生成方法:

public static Expression<Func<CMSiteServer, bool>> GetServerExpression(CMSiteServerFilter filter, bool Active) {
   // 实现逻辑:根据filter生成CMSiteServer的过滤表达式
}

public static Expression<Func<CMSite, bool>> GetSiteExpression(CMSiteFilter filter, bool Active) {
   // 实现逻辑:根据filter生成CMSite的过滤表达式
}

用例与问题

需要实现两个查询场景:

  1. 搜索包含符合条件服务器的站点(已实现)
  2. 搜索属于符合条件站点的服务器(未实现)

已实现的用例1代码

public static IQueryable<CMSite> ApplyQueryFilterServer(this IQueryable<CMSite> qry, CMSiteServerFilter filter, bool Active)
{
    if (filter == null)
        return qry;

    var exp = GetServerExpression(filter, Active);
    qry = qry.Where(s => s.SiteServers.AsQueryable().Any(exp));

    return qry;
}

未实现的用例2代码(存在问题)

// 方法返回值错误,应该是IQueryable<CMSiteServer>而非IQueryable<CMSite>
public static IQueryable<CMSite> ApplyQueryFilterSite(this IQueryable<CMSiteServer> qry, CMSiteFilter filter, bool Active)
{
    if (filter == null)
        return qry;

    var exp = GetServerExpression(filter, Active); // 此处误用了服务器的表达式,应该用站点的表达式
    // 错误原因:s.CMSite是单个实体,无法调用AsQueryable()和Any()
    qry = qry.Where((s) => s.CMSite.AsQueryable().Any(exp));

    return qry;
}

解决方案

核心思路:复用已有的GetSiteExpression生成的站点过滤表达式,将其转换为针对CMSiteServer的过滤逻辑——因为每个CMSiteServer关联单个CMSite,只需判断该关联的CMSite是否满足过滤条件即可。

正确实现方式(支持EF Core SQL翻译)

避免使用Compile()(会导致客户端评估,影响性能),而是通过表达式树拼接将站点表达式转换为服务器的过滤表达式:

public static IQueryable<CMSiteServer> ApplyQueryFilterSite(this IQueryable<CMSiteServer> qry, CMSiteFilter filter, bool Active)
{
    if (filter == null)
        return qry;

    // 获取针对CMSite的过滤表达式
    var siteFilterExp = GetSiteExpression(filter, Active);

    // 构建针对CMSiteServer的表达式:参数为s(CMSiteServer),判断s.Site是否满足siteFilterExp
    var parameter = Expression.Parameter(typeof(CMSiteServer), "s");
    // 获取s.Site属性的表达式
    var siteProperty = Expression.Property(parameter, nameof(CMSiteServer.Site));
    // 调用siteFilterExp表达式,传入s.Site作为参数
    var filterBody = Expression.Invoke(siteFilterExp, siteProperty);
    // 生成最终的Lambda表达式:Func<CMSiteServer, bool>
    var serverFilterExp = Expression.Lambda<Func<CMSiteServer, bool>>(filterBody, parameter);

    // 应用过滤条件
    qry = qry.Where(serverFilterExp);

    return qry;
}

简化版(适合简单场景,注意客户端评估风险)

如果不需要EF Core将整个查询转换为SQL(比如数据量小),可以直接编译表达式后调用:

public static IQueryable<CMSiteServer> ApplyQueryFilterSite(this IQueryable<CMSiteServer> qry, CMSiteFilter filter, bool Active)
{
    if (filter == null)
        return qry;

    var siteFilter = GetSiteExpression(filter, Active).Compile();
    qry = qry.Where(s => siteFilter(s.Site));

    return qry;
}

关键说明

  1. 修正了原方法的返回值错误:输入是IQueryable<CMSiteServer>,返回值应该对应为IQueryable<CMSiteServer>而非IQueryable<CMSite>
  2. 复用已有的GetSiteExpression,避免重复编写站点过滤逻辑
  3. 表达式树拼接的方式能让EF Core正确生成SQL查询,避免客户端评估带来的性能问题

内容的提问来源于stack exchange,提问作者Justin

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 13:57:51