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()
方案说明
createCompositeKeyFilter函数接收复合键列表,以及两个用于获取实体键值的Quotation,动态构建(键1 = 值1 AND 键2 = 值2) OR ...的条件表达式。- 通过
QuotationToExpression.ToExpression将F# Quotation转换为Expression<Func<'TEntity, bool>>,EF Core能直接解析该表达式并生成预期的多OR条件SQL。 - 如果传入的键列表为空,会直接返回永远为
false的条件,确保查询结果为空。
注意事项
- 若复合键数量过大,需要注意数据库对SQL语句长度的限制,此时建议分批次执行查询。
内容的提问来源于stack exchange,提问作者torcylius
相关产品推荐
相关产品推荐

