如何将一个IQueryable<T>的Expression复用至另一个同类型IQueryable<T>?
问题分析与解决方案
你遇到的问题核心在于:从emptyList.AsQueryable()生成的query.Expression并非单纯的Where过滤条件,而是包含原始空数据源的完整查询表达式树。当调用dbQuery.Provider.CreateQuery时,这个表达式树仍指向最初的空列表,而非新创建的dbItems集合,因此返回空结果。
方案1:直接提取Where条件表达式
针对简单的单Where场景,可直接解析表达式树,提取出过滤用的Lambda表达式,再将其应用到目标IQueryable上。
示例代码:
[Test] public void CreateQuerySimple_GivenExpression_ShouldGenerateReport() { // arrange var emptyList = new List<OutputChannelLogTest>().AsQueryable(); var query = emptyList.Where(x => x.Channel == "Channel2"); var expression = query.Expression; var dbItems = new List<OutputChannelLogTest> { new OutputChannelLogTest(){Channel = "Channel1", CreatedBy = "createdBy1"}, new OutputChannelLogTest(){Channel = "Channel1", CreatedBy = "createdBy2"}, new OutputChannelLogTest(){Channel = "Channel2", CreatedBy = "createdBy3"}, new OutputChannelLogTest(){Channel = "Channel2", CreatedBy = "createdBy4"}, }; var dbQuery = dbItems.AsQueryable(); // action:提取Where条件并应用 var methodCallExpr = expression as MethodCallExpression; var lambdaExpr = methodCallExpr?.Arguments[1] as UnaryExpression; var filter = lambdaExpr?.Operand as Expression<Func<OutputChannelLogTest, bool>>; var reportItems = dbQuery.Where(filter); // assert reportItems.Count().Should().Be(2); }
方案2:用ExpressionVisitor替换数据源
如果是包含多个链式操作(如Where+OrderBy+Select)的复杂查询,可通过ExpressionVisitor遍历表达式树,将原始数据源节点替换为目标数据源。
自定义ExpressionVisitor
public class ReplaceQuerySourceVisitor : ExpressionVisitor { private readonly Expression _oldSource; private readonly Expression _newSource; public ReplaceQuerySourceVisitor(Expression oldSource, Expression newSource) { _oldSource = oldSource; _newSource = newSource; } protected override Expression VisitConstant(ConstantExpression node) { // 替换原始Queryable数据源 if (node.Value == ((ConstantExpression)_oldSource).Value) { return _newSource; } return base.VisitConstant(node); } }
使用示例
[Test] public void CreateQuerySimple_GivenExpression_ShouldGenerateReport() { // arrange var emptyList = new List<OutputChannelLogTest>().AsQueryable(); var query = emptyList.Where(x => x.Channel == "Channel2"); var expression = query.Expression; var dbItems = new List<OutputChannelLogTest> { new OutputChannelLogTest(){Channel = "Channel1", CreatedBy = "createdBy1"}, new OutputChannelLogTest(){Channel = "Channel1", CreatedBy = "createdBy2"}, new OutputChannelLogTest(){Channel = "Channel2", CreatedBy = "createdBy3"}, new OutputChannelLogTest(){Channel = "Channel2", CreatedBy = "createdBy4"}, }; var dbQuery = dbItems.AsQueryable(); // action:替换数据源并执行查询 var visitor = new ReplaceQuerySourceVisitor(emptyList.Expression, dbQuery.Expression); var newExpression = visitor.Visit(expression); var reportItems = dbQuery.Provider.CreateQuery<OutputChannelLogTest>(newExpression); // assert reportItems.Count().Should().Be(2); }
补充说明
- 方案1适合简单场景,代码更简洁;
- 方案2通用性更强,支持任意复杂的查询链式操作;
- 两种方案的核心逻辑都是确保表达式树中的数据源节点指向目标集合,而非原始空列表。
内容的提问来源于stack exchange,提问作者JDeVil
相关产品推荐
相关产品推荐

