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

EF Core使用Quotation按复合键ID列表筛选实体的实现方案

EF Core 基于复合键元组列表筛选实体的F#实现

问题背景

现有一个使用复合键的F#实体:

[<CLIMutable>]
type Entity =
  { Id1: int
    Id2: int
    Number: string }

需要通过EF Core传入(int * int)类型的复合键元组列表筛选实体,要求生成的SQL形如:

SELECT * FROM Entities
WHERE 
    (Id1 = 1 AND Id2 = 1) OR
    (Id1 = 2 AND Id2 = 2) -- 更多匹配项以此类推

现有方案的问题

  • 直接使用Linq.Enumerable.Any的写法无法被EF Core正确解析,无法生成预期SQL:
    let getMany(context: DbCtx) (ids: (int * int) array) =
      context
        .Entities
        .Where(fun ef ->
          Linq.Enumerable.Any(ids, fun (id1, id2) ->
            ef.Id1 = id1
            && ef.Id2 = id2))
        .ToListAsync()
    
  • 拼接多个Where条件的方案会生成通过UNION ALL组合的多查询SQL,不符合需求。

基于F# Quotation的解决方案

我们可以利用F#代码引用(Quotation)动态构建包含多OR条件的表达式树,让EF Core正确解析为预期SQL。

实现代码

首先需要安装NuGet包FSharp.Quotations.Evaluator,用于将Quotation转换为EF Core可识别的表达式:

open System
open System.Linq.Expressions
open FSharp.Quotations
open FSharp.Quotations.Evaluator

module QueryHelpers =
    /// 构建复合键筛选表达式
    let createCompositeKeyFilter<'TEntity when 'TEntity : not struct> 
        (keys: ('TKey1 * 'TKey2) array) 
        (getKey1: Expr<'TEntity -> 'TKey1>) 
        (getKey2: Expr<'TEntity -> 'TKey2>) =
        
        if Array.isEmpty keys then
            <@ fun _ -> false @>
        else
            // 初始化第一个匹配条件
            let firstKey1, firstKey2 = keys.[0]
            let initialCondition = 
                <@ fun (entity: 'TEntity) -> 
                    (%getKey1) entity = %firstKey1 && (%getKey2) entity = %firstKey2 @>
            
            // 遍历剩余键,拼接OR条件
            keys.[1..]
            |> Array.fold (fun accExpr (key1, key2) ->
                let nextCondition = 
                    <@ fun (entity: 'TEntity) -> 
                        (%getKey1) entity = %key1 && (%getKey2) entity = %key2 @>
                <@ fun entity -> (%accExpr) entity || (%nextCondition) entity @>) initialCondition
            |> QuotationToExpression.ToExpression<Func<'TEntity, bool>>

// 使用示例
let getMany(context: DbCtx) (ids: (int * int) array) =
    let filterExpr = QueryHelpers.createCompositeKeyFilter ids <@ fun e -> e.Id1 @> <@ fun e -> e.Id2 @>
    context.Entities.Where(filterExpr).ToListAsync()

方案说明

  1. createCompositeKeyFilter函数接收复合键列表,以及两个用于获取实体键值的Quotation,动态构建(键1 = 值1 AND 键2 = 值2) OR ...的条件表达式。
  2. 通过QuotationToExpression.ToExpression将F# Quotation转换为Expression<Func<'TEntity, bool>>,EF Core能直接解析该表达式并生成预期的多OR条件SQL。
  3. 如果传入的键列表为空,会直接返回永远为false的条件,确保查询结果为空。

注意事项

  • 若复合键数量过大,需要注意数据库对SQL语句长度的限制,此时建议分批次执行查询。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 14:29:52