是否有可能获得IQueryable <t>上使用的谓词(Expression <func <t,bool>&gt;)并将其应用于另一个Lambda函数?

时间:2017-02-14 10:39:07

标签: c# mongodb lambda expression iqueryable

我有以下问题:

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

可能的解决方案:

  • 从原始Expression<Func<T,bool>>获取IQueryable<T>并将其应用于mongoCollection<T>.Count(filter)。这绕过了这个问题。

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

我附加了一个LINQPad脚本,该脚本使用本地MongoDB安装来填充数据,然后使用从ExpressionVisitor创建的新Expression进行计数。

我希望有一种更简单的方法来重复使用&#34; Where&#34;来自新MongoDB中的原始表达式FilterDefinitionBuilder<T>.Where(originalWhereExpression)

脚本依赖项是: References (F4)

代码:

<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>LINQKit.Core</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>LinqKit</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 = 100000;
private bool _doPrep = true;

void Main()
{
    _connectToMongoDB();

    if (_doPrep)
        _prepMongo();

    var mongoDataQuery = _queryDemoData().Result;
    mongoDataQuery.Expression.ToString().Dump("Original Expression");

    var whereFinder = new WhereFinder();
    whereFinder.SetWhere(mongoDataQuery.Expression);

    var tempColl = _getPeopleCollection();

    if (!string.IsNullOrEmpty(whereFinder.WhereClause))
    {
        var filter = new FilterDefinitionBuilder<Person>();
        tempColl.Count(filter.Where(_createWherePredicate<Person>(whereFinder.GetLambdaParts<Person>()))).Dump("Dynamic where count");
    }
    else
        tempColl.Count(FilterDefinition<Person>.Empty).Dump("No filter count");

    "Done".Dump();
}

// Define other methods and classes here

//
private void _replaceExpressionTypes(ref StringBuilder whereBuilder, Dictionary<ExpressionType,string> expressionTypes)
{
    foreach (var expType in expressionTypes.Keys)
    {
        whereBuilder.Replace($" {expType} ", $" {expressionTypes[expType]} ");
    }

    var openBracketCount = whereBuilder.ToString().Count(s => s == char.Parse("("));
    var closeBracketCount = whereBuilder.ToString().Count(s=> s==char.Parse(")"));

    //whereBuilder.Replace("new DateTime(1974, 1, 1)","\"1974-01-01T00:00:00.00Z\"");
    whereBuilder.Insert(0,"(",1);
    whereBuilder.Append(")");

    $"OpenBrackets: {openBracketCount} vs CloseBrackets: {closeBracketCount}".Dump("Found Brackets");

    if(openBracketCount==closeBracketCount)
        return;
    if (openBracketCount > closeBracketCount)
    {
        var firstopenBracket = whereBuilder.ToString().IndexOf("(");
        whereBuilder.Remove(firstopenBracket,1);
    }
    var lastCloseBracket = whereBuilder.ToString().LastIndexOf(")");
    if(lastCloseBracket>-1)
        whereBuilder.Remove(lastCloseBracket,1);
}
private Dictionary<ExpressionType, string> _buildExpressionTypePairs()
{
    var result = new Dictionary<ExpressionType, string>();

    result.Add(ExpressionType.Not, "!");
    result.Add(ExpressionType.Add, "+");
    result.Add(ExpressionType.AddChecked, "+");
    result.Add(ExpressionType.Subtract, "-");
    result.Add(ExpressionType.SubtractChecked, "-");
    result.Add(ExpressionType.Multiply, "*");
    result.Add(ExpressionType.MultiplyChecked, "*");
    result.Add(ExpressionType.Divide, "/");
    result.Add(ExpressionType.Modulo, "%");
    result.Add(ExpressionType.And, "&");
    result.Add(ExpressionType.AndAlso, "&&");
    result.Add(ExpressionType.Or, "|");
    result.Add(ExpressionType.OrElse, "||");
    result.Add(ExpressionType.LessThan, "<");
    result.Add(ExpressionType.LessThanOrEqual, "<=");
    result.Add(ExpressionType.GreaterThan, ">");
    result.Add(ExpressionType.GreaterThanOrEqual, ">=");
    result.Add(ExpressionType.Equal, "==");
    result.Add(ExpressionType.NotEqual, "!=");

    return result;
}
private Expression<Func<Person, bool>> _createWherePredicate<T>(LamdaParts<T> lamdaParts)
{
    var whereBuilder = new StringBuilder(lamdaParts.ExpressionString);
    _replaceExpressionTypes(ref whereBuilder, _buildExpressionTypePairs());

    whereBuilder.ToString().Dump("Manipulated where cluase");

    var parameter = Expression.Parameter(lamdaParts.ParamterType, lamdaParts.ExpressionParameter);
    //lamdaParts.ParamterType.Dump("Parameter");
    //var parameter = Expression.Parameter(typeof(Person), "p");
    var expression = myAlias.DynamicExpression.ParseLambda(new[] { parameter }, null, whereBuilder.ToString());

    //return Expression.Lambda<Func<Person, bool>>(whereExpression, parameter);
    return Expression.Lambda<Func<Person, bool>>(expression.Body, expression.Parameters);
}
private async Task<IMongoQueryable<Person>> _queryDemoData()
{
    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");

}
private void _prepMongo()
{
    _mongoDb.DropCollection(_mongoDBCollection, CancellationToken.None);

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

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

    testData.ForEachOverTpl((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);
}

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 : MongoDB.Driver.Linq.ExpressionVisitor
{
    //private IList<MethodCallExpression> whereExpressions = new List<MethodCallExpression>();
    private bool _foundWhere = false;
    private bool _setWhere = false;

    public string WhereClause { get; set; }
    public string Parameter { get; set; }


    public LamdaParts<T> GetLambdaParts<T>()
    {
        return new LamdaParts<T> {
            ExpressionParameter=Parameter,
            ExpressionString = WhereClause
        };
    }   
    public void SetWhere(Expression expression)
    {
        Visit(expression);
        //return whereExpressions;  
    }

    protected override Expression VisitBinary(BinaryExpression node)
    {
        //$"{node.Left} {_convertNodeType(node.NodeType)} {node.Right}".Dump();
        if (_foundWhere && !_setWhere)
        {
            //node.ToString().Dump("VisitBinary");
            $"{node.Left} {_convertNodeType(node.NodeType)} {node.Right}".Dump("Setting Where Clause");
            WhereClause= $"{node.Left} {_convertNodeType(node.NodeType)} {node.Right}";
            //WhereClause.Dump("WhereClause");
            _setWhere=true;
        }
        return base.VisitBinary(node);
    }
    private string _convertNodeType(ExpressionType nodeType)
    {
        switch (nodeType)
        {
            case ExpressionType.Not:
                return "!";
            case ExpressionType.Add:
            case ExpressionType.AddChecked:
                return "+";
            case ExpressionType.Subtract:
            case ExpressionType.SubtractChecked:
                return "-";
            case ExpressionType.Multiply:
            case ExpressionType.MultiplyChecked:
                return "*";
            case ExpressionType.Divide:
                return "/";
            case ExpressionType.Modulo:
                return "%";
            case ExpressionType.And:
                return "&";
            case ExpressionType.AndAlso:
                return "&&";
            case ExpressionType.Or:
                return "|";
            case ExpressionType.OrElse:
                return "||";
            case ExpressionType.LessThan:
                return "<";
            case ExpressionType.LessThanOrEqual:
                return "<=";
            case ExpressionType.GreaterThan:
                return ">";
            case ExpressionType.GreaterThanOrEqual:
                return ">=";
            case ExpressionType.Equal:
                return "==";
            case ExpressionType.NotEqual:
                return "!=";
            default:
                throw new Exception(string.Format("Unhandled expression type: '{0}'", nodeType));
        }
    }

    protected override Expression VisitParameter(ParameterExpression node)
    {
        if (_foundWhere)
        {
            //node.ToString().Dump("VisitParameter");
            Parameter=node.ToString();
        }
        return base.VisitParameter(node);
    }

    protected override Expression VisitMethodCall(MethodCallExpression expression)
    {
        if (expression.Method.Name == "Where")
        {
            //whereExpressions.Add(expression);
            _foundWhere = true;
        }

        if (expression?.Arguments != null)
        {
            foreach (var arg in expression.Arguments)
            {
                Visit(arg);
            }
        }

        return expression;
    }
}
public class LamdaParts<T>
{   
    public Type ParamterType
    {
        get
        {
            return typeof(T);
        }
    }
    public string ExpressionParameter { get; set; }
    public string ExpressionString { get;set;}
}
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);
    }
}

<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>LINQKit.Core</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>LinqKit</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 = 100000; private bool _doPrep = true; void Main() { _connectToMongoDB(); if (_doPrep) _prepMongo(); var mongoDataQuery = _queryDemoData().Result; mongoDataQuery.Expression.ToString().Dump("Original Expression"); var whereFinder = new WhereFinder(); whereFinder.SetWhere(mongoDataQuery.Expression); var tempColl = _getPeopleCollection(); if (!string.IsNullOrEmpty(whereFinder.WhereClause)) { var filter = new FilterDefinitionBuilder<Person>(); tempColl.Count(filter.Where(_createWherePredicate<Person>(whereFinder.GetLambdaParts<Person>()))).Dump("Dynamic where count"); } else tempColl.Count(FilterDefinition<Person>.Empty).Dump("No filter count"); "Done".Dump(); } // Define other methods and classes here // private void _replaceExpressionTypes(ref StringBuilder whereBuilder, Dictionary<ExpressionType,string> expressionTypes) { foreach (var expType in expressionTypes.Keys) { whereBuilder.Replace($" {expType} ", $" {expressionTypes[expType]} "); } var openBracketCount = whereBuilder.ToString().Count(s => s == char.Parse("(")); var closeBracketCount = whereBuilder.ToString().Count(s=> s==char.Parse(")")); //whereBuilder.Replace("new DateTime(1974, 1, 1)","\"1974-01-01T00:00:00.00Z\""); whereBuilder.Insert(0,"(",1); whereBuilder.Append(")"); $"OpenBrackets: {openBracketCount} vs CloseBrackets: {closeBracketCount}".Dump("Found Brackets"); if(openBracketCount==closeBracketCount) return; if (openBracketCount > closeBracketCount) { var firstopenBracket = whereBuilder.ToString().IndexOf("("); whereBuilder.Remove(firstopenBracket,1); } var lastCloseBracket = whereBuilder.ToString().LastIndexOf(")"); if(lastCloseBracket>-1) whereBuilder.Remove(lastCloseBracket,1); } private Dictionary<ExpressionType, string> _buildExpressionTypePairs() { var result = new Dictionary<ExpressionType, string>(); result.Add(ExpressionType.Not, "!"); result.Add(ExpressionType.Add, "+"); result.Add(ExpressionType.AddChecked, "+"); result.Add(ExpressionType.Subtract, "-"); result.Add(ExpressionType.SubtractChecked, "-"); result.Add(ExpressionType.Multiply, "*"); result.Add(ExpressionType.MultiplyChecked, "*"); result.Add(ExpressionType.Divide, "/"); result.Add(ExpressionType.Modulo, "%"); result.Add(ExpressionType.And, "&"); result.Add(ExpressionType.AndAlso, "&&"); result.Add(ExpressionType.Or, "|"); result.Add(ExpressionType.OrElse, "||"); result.Add(ExpressionType.LessThan, "<"); result.Add(ExpressionType.LessThanOrEqual, "<="); result.Add(ExpressionType.GreaterThan, ">"); result.Add(ExpressionType.GreaterThanOrEqual, ">="); result.Add(ExpressionType.Equal, "=="); result.Add(ExpressionType.NotEqual, "!="); return result; } private Expression<Func<Person, bool>> _createWherePredicate<T>(LamdaParts<T> lamdaParts) { var whereBuilder = new StringBuilder(lamdaParts.ExpressionString); _replaceExpressionTypes(ref whereBuilder, _buildExpressionTypePairs()); whereBuilder.ToString().Dump("Manipulated where cluase"); var parameter = Expression.Parameter(lamdaParts.ParamterType, lamdaParts.ExpressionParameter); //lamdaParts.ParamterType.Dump("Parameter"); //var parameter = Expression.Parameter(typeof(Person), "p"); var expression = myAlias.DynamicExpression.ParseLambda(new[] { parameter }, null, whereBuilder.ToString()); //return Expression.Lambda<Func<Person, bool>>(whereExpression, parameter); return Expression.Lambda<Func<Person, bool>>(expression.Body, expression.Parameters); } private async Task<IMongoQueryable<Person>> _queryDemoData() { 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"); } private void _prepMongo() { _mongoDb.DropCollection(_mongoDBCollection, CancellationToken.None); var testData = _getDemoList(_demoCount); var people = _getPeopleCollection(); people.Indexes.CreateOne(Builders<Person>.IndexKeys.Ascending(_ => _.LastName)); people.Indexes.CreateOne(Builders<Person>.IndexKeys.Ascending(_ => _.Email)); testData.ForEachOverTpl((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); } 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 : MongoDB.Driver.Linq.ExpressionVisitor { //private IList<MethodCallExpression> whereExpressions = new List<MethodCallExpression>(); private bool _foundWhere = false; private bool _setWhere = false; public string WhereClause { get; set; } public string Parameter { get; set; } public LamdaParts<T> GetLambdaParts<T>() { return new LamdaParts<T> { ExpressionParameter=Parameter, ExpressionString = WhereClause }; } public void SetWhere(Expression expression) { Visit(expression); //return whereExpressions; } protected override Expression VisitBinary(BinaryExpression node) { //$"{node.Left} {_convertNodeType(node.NodeType)} {node.Right}".Dump(); if (_foundWhere && !_setWhere) { //node.ToString().Dump("VisitBinary"); $"{node.Left} {_convertNodeType(node.NodeType)} {node.Right}".Dump("Setting Where Clause"); WhereClause= $"{node.Left} {_convertNodeType(node.NodeType)} {node.Right}"; //WhereClause.Dump("WhereClause"); _setWhere=true; } return base.VisitBinary(node); } private string _convertNodeType(ExpressionType nodeType) { switch (nodeType) { case ExpressionType.Not: return "!"; case ExpressionType.Add: case ExpressionType.AddChecked: return "+"; case ExpressionType.Subtract: case ExpressionType.SubtractChecked: return "-"; case ExpressionType.Multiply: case ExpressionType.MultiplyChecked: return "*"; case ExpressionType.Divide: return "/"; case ExpressionType.Modulo: return "%"; case ExpressionType.And: return "&"; case ExpressionType.AndAlso: return "&&"; case ExpressionType.Or: return "|"; case ExpressionType.OrElse: return "||"; case ExpressionType.LessThan: return "<"; case ExpressionType.LessThanOrEqual: return "<="; case ExpressionType.GreaterThan: return ">"; case ExpressionType.GreaterThanOrEqual: return ">="; case ExpressionType.Equal: return "=="; case ExpressionType.NotEqual: return "!="; default: throw new Exception(string.Format("Unhandled expression type: '{0}'", nodeType)); } } protected override Expression VisitParameter(ParameterExpression node) { if (_foundWhere) { //node.ToString().Dump("VisitParameter"); Parameter=node.ToString(); } return base.VisitParameter(node); } protected override Expression VisitMethodCall(MethodCallExpression expression) { if (expression.Method.Name == "Where") { //whereExpressions.Add(expression); _foundWhere = true; } if (expression?.Arguments != null) { foreach (var arg in expression.Arguments) { Visit(arg); } } return expression; } } public class LamdaParts<T> { public Type ParamterType { get { return typeof(T); } } public string ExpressionParameter { get; set; } public string ExpressionString { get;set;} } 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); } }

1 个答案:

答案 0 :(得分:2)

一旦理解了表达式树和可能的根级表达式方法名称,解决方案就相当简单了。感谢@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;
    }

}