c# – 是否可以获取IQueryable上使用的谓词(Expression>)并将其应用于另一个Lambda函数?

我有以下问题:

>我得到了一个IQueryable< T>来自LinqToQueryString
>这个IQueryable< T>用于查询MongoDB
> IQueryable< T>用于返回分页数据集以及确定页面等的项目总数
> MongoDB在IQueryable< T> .Where().Count()上向Count添加一个group by.这会导致Count操作运行得非常慢.

可能的方法:

>获取表达式< Func< T,bool>>从最初的IQueryable< T>并将其应用于mongoCollection< T> .Count(过滤器).这绕过了这个问题.

我试图从IQueryable< T> .Expression中获取“Where”,然后将ExpressionType操作为可以在DynamicExpression.ParseLambda()中使用的格式.在大多数情况下,这工作正常,直到我使用DateTime表达式测试代码.

我附加了一个LINQPad脚本,该脚本使用本地MongoDB安装来填充数据,然后使用从ExpressionVisitor创建的新表达式计数.

我希望有一种更简单的方法可以在新的MongoDB FilterDefinitionBuilder< T> .Where(originalWhereExpression)中重用原始Expression中的“Where”.

脚本依赖是:
《c# – 是否可以获取IQueryable上使用的谓词(Expression>)并将其应用于另一个Lambda函数?》” /></a></p><p>码:</p><pre><code><Query Kind=

最佳答案 一旦理解了表达式树和可能的根级表达式方法名称,解决方案就相当简单.感谢@bolanki的帮助.

附件是更新的LINQPad脚本(测试集_doPrep = true):

<Query Kind="Program">
  <Reference>&lt;RuntimeDirectory&gt;\System.Linq.dll</Reference>
  <Reference>&lt;RuntimeDirectory&gt;\System.Linq.Expressions.dll</Reference>
  <Reference>&lt;RuntimeDirectory&gt;\System.Linq.Queryable.dll</Reference>
  <NuGetReference>Faker</NuGetReference>
  <NuGetReference>mongocsharpdriver</NuGetReference>
  <NuGetReference>MongoDB.Driver</NuGetReference>
  <NuGetReference>NBuilder</NuGetReference>
  <NuGetReference>Newtonsoft.Json</NuGetReference>
  <NuGetReference>System.Linq.Dynamic</NuGetReference>
  <Namespace>FizzWare.NBuilder</Namespace>
  <Namespace>MongoDB.Bson</Namespace>
  <Namespace>MongoDB.Bson.Serialization.Attributes</Namespace>
  <Namespace>MongoDB.Driver</Namespace>
  <Namespace>MongoDB.Driver.Builders</Namespace>
  <Namespace>MongoDB.Driver.Linq</Namespace>
  <Namespace>myAlias = System.Linq.Dynamic</Namespace>
  <Namespace>Newtonsoft.Json</Namespace>
  <Namespace>System.Linq</Namespace>
  <Namespace>System.Linq.Expressions</Namespace>
  <Namespace>System.Threading.Tasks</Namespace>
  <Namespace>System.Threading.Tasks.Dataflow</Namespace>
</Query>

private string _mongoDBConnectionString = "mongodb://localhost";
private string _mongoDBDatabase = "LinqToQ";
private string _mongoDBCollection = "People";

private IMongoClient _mongoClient;
private IMongoDatabase _mongoDb;

private int _demoCount = 2000000;
private bool _doPrep = false;

void Main()
{
    _connectToMongoDB();

    // Should demo data be generated
    if (_doPrep)
        _prepMongo();

    // Get the queryable to test with
    var mongoDataQuery = _getIQueryable();
    // Print the original expression    
    //mongoDataQuery.Expression.ToString().Dump("Original Expression");

    // Evaluate the expression and try find the where expression
    var whereFinder = new WhereFinder<Person>(mongoDataQuery.Expression);

    // Get the MongoCollection to be Filtered and Count
    var tempColl = _getPeopleCollection();

    if (whereFinder.FoundWhere)
    {
        //whereFinder.TheWhereExpression.ToString().Dump("Calculated where expression");
        var filter = new FilterDefinitionBuilder<Person>();
        var stopwatch = new Stopwatch();
        stopwatch.Start();
        tempColl.Count(filter.Where(whereFinder.TheWhereExpression)).Dump("Dynamic where count");
        var afterCalculatedWhere = stopwatch.Elapsed;
        mongoDataQuery.Count().Dump("IQueryable<T> where count");
        var afterIQuerableWhere = stopwatch.Elapsed;
        stopwatch.Stop();

        $"Calculated where:{afterCalculatedWhere:c}\nIQueryable where:{afterIQuerableWhere:c}".Dump("Where Durations");
    }
    else
        tempColl.Count(FilterDefinition<Person>.Empty).Dump("No filter count");

    "Done".Dump();
}

///////////////////////////////////////////////////////
// END SOLUTION
///////////////////////////////////////////////////////
private IMongoQueryable<Person> _getIQueryable()
{
    var people = _getPeopleCollection();

    //return people.AsQueryable().Where(p => p.DateOfBirth <= new DateTime(1974, 1, 1));
    return people.AsQueryable().Where(p => p.LastName == "Anderson" && p.FirstName.Contains("f") && p.DateOfBirth >= new DateTime(1968, 1, 1) && p.DateOfBirth < new DateTime(1974, 1, 1));
    //return people.AsQueryable().Where(p => p.LastName == "Anderson" && p.FirstName.Contains("f") && (p.DateOfBirth>=new DateTime(1968,1,1) && p.DateOfBirth<new DateTime(1974,1,1)));
    //return people.AsQueryable().Where(p => p.LastName == "Anderson" && p.FirstName.Contains("f"));
    //return people.AsQueryable().Where(p => p.FirstName.Contains("f"));
    //return people.AsQueryable().Where(p => p.LastName == "Anderson");

}
public class Person
{
    [BsonId]
    public string Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Email { get; set; }
    public DateTime DateOfBirth { get; set; }

}
public class WhereFinder<T> : MongoDB.Driver.Linq.ExpressionVisitor
{
    private bool _processingWhere = false;
    private bool _processingLambda = false;
    public ParameterExpression _parameterExpression { get; set; }   

    public WhereFinder(Expression expression)
    {
        Visit(expression);
    }

    public Expression<Func<T, bool>> TheWhereExpression { get; set; }
    public bool FoundWhere
    {
        get { return TheWhereExpression != null; }
    }

    protected override Expression VisitBinary(BinaryExpression node)
    {
        var result = base.VisitBinary(node);
        if(_processingWhere)
            TheWhereExpression = (Expression<Func<T, bool>>)Expression.Lambda(node, _parameterExpression);
        return result;
    }
    protected override Expression VisitParameter(ParameterExpression node)
    {
        if (_processingWhere || _processingLambda || _parameterExpression==null)
            _parameterExpression = node;
        return base.VisitParameter(node);
    }
    protected override Expression VisitMethodCall(MethodCallExpression expression)
    {
        string methodName = expression.Method.Name;
        if (TheWhereExpression==null && ( methodName == "Where" || methodName == "Contains"))
        {
            _processingWhere = true;
            if (expression?.Arguments != null)
                foreach (var arg in expression.Arguments)
                    Visit(arg);
            _processingWhere = false;
        }

        return expression;
    }
    protected override Expression VisitLambda(LambdaExpression exp)
    {
        if (_parameterExpression == null)
            _parameterExpression = exp.Parameters?.FirstOrDefault();

        TheWhereExpression = (Expression<Func<T, bool>>)Expression.Lambda(exp.Body, _parameterExpression);
        return exp;
    }

}
///////////////////////////////////////////////////////
// END SOLUTION
///////////////////////////////////////////////////////



///////////////////////////////////////////////////////
// BEGIN DEMO DATA
///////////////////////////////////////////////////////
private void _prepMongo()
{
    _mongoDb.DropCollection(_mongoDBCollection, CancellationToken.None);

    var testData = _getDemoList(_demoCount);
    var people = _getPeopleCollection();

    people.Indexes.CreateOne(Builders<Person>.IndexKeys.Ascending(_ => _.FirstName));
    people.Indexes.CreateOne(Builders<Person>.IndexKeys.Ascending(_ => _.LastName));
    people.Indexes.CreateOne(Builders<Person>.IndexKeys.Ascending(_ => _.Email));
    people.Indexes.CreateOne(Builders<Person>.IndexKeys.Ascending(_ => _.DateOfBirth));

    $"Inserting ...{testData.Count}... demo records".Dump();

    Extensions.ForEachOverTpl<Person>(testData, (person) =>
    {
        people.InsertOneAsync(person).Wait();
    });

    $"Inserted {testData.Count} demo records".Dump();
}
private IList<Person> _getDemoList(int demoCount)
{
    var result = Builder<Person>.CreateListOfSize(demoCount)
        .All()
        .With(p => p.FirstName = Faker.NameFaker.FirstName())
        .With(p => p.LastName = Faker.NameFaker.LastName())
        .With(p => p.Email = Faker.InternetFaker.Email())
        .With(p => p.DateOfBirth = Faker.DateTimeFaker.BirthDay(21, 50))
        .Build();

    return result;
}
private IMongoCollection<Person> _getPeopleCollection()
{
    return _mongoDb.GetCollection<Person>(_mongoDBCollection);
}
private void _connectToMongoDB()
{
    _mongoClient = new MongoClient(_mongoDBConnectionString);
    _mongoDb = _mongoClient.GetDatabase(_mongoDBDatabase);
}
///////////////////////////////////////////////////////
// END DEMO DATA
///////////////////////////////////////////////////////
public static class Extensions
{
    public static void ForEachOverTpl<T>(this IEnumerable<T> enumerable, Action<T> call)
    {
        var cancellationTokenSource = new CancellationTokenSource();
        var actionBlock = new ActionBlock<T>(call, new ExecutionDataflowBlockOptions
        {
            TaskScheduler = TaskScheduler.Current,
            MaxDegreeOfParallelism = Environment.ProcessorCount * 2,
            CancellationToken = cancellationTokenSource.Token,
        });
        foreach (T item in enumerable)
        {
            if (cancellationTokenSource.IsCancellationRequested) return;
            actionBlock.Post(item);
        }
        actionBlock.Complete();
        actionBlock.Completion.Wait(cancellationTokenSource.Token);
    }
}

更新: – 修复包含Take,OrderBy等的表达式.

public class WhereFinder<T> : MongoDB.Driver.Linq.ExpressionVisitor
    {
      private bool _processingWhere = false;
      private bool _processingLambda = false;
      public ParameterExpression _parameterExpression { get; set; }

      public WhereFinder(Expression expression)
      {
        Visit(expression);
      }

      public Expression<Func<T, bool>> TheWhereExpression { get; set; }
      public bool FoundWhere
      {
        get { return TheWhereExpression != null; }
      }

      protected override Expression Visit(Expression exp)
      {
        return base.Visit(exp);
      }
      protected override Expression VisitBinary(BinaryExpression node)
      {
        var result = base.VisitBinary(node);
        if (_processingWhere)
        {
          TheWhereExpression = (Expression<Func<T, bool>>) Expression.Lambda(node, _parameterExpression);
        }
        return result;
      }
      protected override Expression VisitParameter(ParameterExpression node)
      {
        if (_processingWhere || _processingLambda || _parameterExpression == null)
          _parameterExpression = node;
        return base.VisitParameter(node);
      }
      protected override Expression VisitMethodCall(MethodCallExpression expression)
      {
        string methodName = expression.Method.Name;

        if (methodName == "Where")
          _processingWhere = true;
        if (expression?.Arguments != null)
          foreach (var arg in expression.Arguments)
            Visit(arg);
        _processingWhere = false;

        return expression;
    }
    protected override Expression VisitLambda(LambdaExpression exp)
    {
        if (_processingWhere)
        {
            if (_parameterExpression == null)
                _parameterExpression = exp.Parameters?.FirstOrDefault();

            TheWhereExpression = (Expression<Func<T, bool>>)Expression.Lambda(exp.Body, _parameterExpression);
        }
        return exp;
    }

}
点赞