我怎样才能转换这个linq表达式?

时间:2013-09-26 14:48:52

标签: linq entity-framework lambda linq-to-entities expression-trees

假设我有一个实体,我想查询应用的排名:

public class Person: Entity
{
    public int Id { get; protected set; }
    public string Name { get; set; }
    public DateTime Birthday { get; set; }
}

在我的查询中,我有以下内容:

Expression<Func<Person, object>> orderBy = x => x.Name;

var dbContext = new MyDbContext();

var keyword = "term";
var startsWithResults = dbContext.People
    .Where(x => x.Name.StartsWith(keyword))
    .Select(x => new {
        Rank = 1,
        Entity = x,
    });
var containsResults = dbContext.People
    .Where(x => !startsWithResults.Select(y => y.Entity.Id).Contains(x.Id))
    .Where(x => x.Name.Contains(keyword))
    .Select(x => new {
        Rank = 2,
        Entity = x,
    });

var rankedResults = startsWithResults.Concat(containsResults)
    .OrderBy(x => x.Rank);

// TODO: apply thenby ordering here based on the orderBy expression above

dbContext.Dispose();

我在选择具有Rank属性的匿名对象之前尝试对结果进行排序,但排序最终会丢失。似乎linq to entities丢弃了单独集合的顺序,并在ConcatUnion期间转换回自然顺序。

我认为我可以做的是将orderBy变量中定义的表达式从x => x.Name动态转换为x => x.Entity.Name,但我不确定如何:

if (orderBy != null)
{
    var transformedExpression = ???
    rankedResults = rankedResults.ThenBy(transformedExpression);
}

我如何能够使用Expression.Lambdax => x.Name包裹到x => x.Entity.Name中?当我将x => x.Entity.Name硬编码到ThenBy中时,我得到了我想要的顺序,但orderBy是由查询的调用类提供的,所以我不想 - 我把它编码在上面的例子中,只是为了解释的简单。

2 个答案:

答案 0 :(得分:4)

这应该有所帮助。但是,您必须具体使用Anonymous类型才能使其正常工作。我的LinqPropertyChain将无法使用它,因为它仍然难以创建Expression<Func<Anonymous, Person>>,而它仍然是匿名。

Expression<Func<Person, object>> orderBy = x => x.Name;

using(var dbContext = new MyDbContext())
{
var keyword = "term";
var startsWithResults = dbContext.People
    .Where(x => x.Name.StartsWith(keyword))
    .Select(x => new {
        Rank = 1,
        Entity = x,
    });
var containsResults = dbContext.People
    .Where(x => !startsWithResults.Select(y => y.Entity.Id).Contains(x.Id))
    .Where(x => x.Name.Contains(keyword))
    .Select(x => new {
        Rank = 2,
        Entity = x,
    });


var rankedResults = startsWithResults.Concat(containsResults)
    .OrderBy(x => x.Rank)
    .ThenBy(LinqPropertyChain.Chain(x => x.Entity, orderBy));

// TODO: apply thenby ordering here based on the orderBy expression above

}

public static class LinqPropertyChain 
{

    public static Expression<Func<TInput, TOutput>> Chain<TInput, TOutput, TIntermediate>(
        Expression<Func<TInput, TIntermediate>> outter,
        Expression<Func<TIntermediate, TOutput>> inner
        )
    {

        Console.WriteLine(inner);
        Console.WriteLine(outter);
        var visitor = new Visitor(new Dictionary<ParameterExpression, Expression>
        {
            {inner.Parameters[0], outter.Body}
        });

        var newBody = visitor.Visit(inner.Body);
        Console.WriteLine(newBody);
        return Expression.Lambda<Func<TInput, TOutput>>(newBody, outter.Parameters);
    }

    private class Visitor : ExpressionVisitor
    {
        private readonly Dictionary<ParameterExpression, Expression> _replacement;

        public Visitor(Dictionary<ParameterExpression, Expression> replacement)
        {
            _replacement = replacement;
        }

        protected override Expression VisitParameter(ParameterExpression node)
        {
            if (_replacement.ContainsKey(node))
                return _replacement[node];
            else
            {
                return node;
            }
        }
    }
}

用较少的Explicite Generics找出一种方法。

Expression<Func<Person, object>> orderBy = x => x.Name;
Expression<Func<Foo, Person>> personExpression = x => x.Person;

var helper = new ExpressionChain(personExpression);
var chained = helper.Chain(orderBy).Expression;


// Define other methods and classes here
public class ExpressionChain<TInput, TOutput>
{
    private readonly Expression<Func<TInput, TOutput>> _expression; 
    public ExpressionChain(Expression<Func<TInput, TOutput>> expression)
    {
        _expression = expression;
    }

    public Expression<Func<TInput, TOutput>> Expression { get { return _expression; } }

    public ExpressionChain<TInput, TChained> Chain<TChained>
        (Expression<Func<TOutput, TChained>> chainedExpression)
    {
        var visitor = new Visitor(new Dictionary<ParameterExpression, Expression>
        {
            {_expression.Parameters[0], chainedExpression.Body}
        });
        var lambda = Expression.Lambda<Func<TInput, TOutput>>(newBody, outter.Parameters);
        return new ExpressionChain(lambda);
    }

    private class Visitor : ExpressionVisitor
    {
        private readonly Dictionary<ParameterExpression, Expression> _replacement;

        public Visitor(Dictionary<ParameterExpression, Expression> replacement)
        {
            _replacement = replacement;
        }

        protected override Expression VisitParameter(ParameterExpression node)
        {
            if (_replacement.ContainsKey(node))
                return _replacement[node];
            else
            {
                return node;
            }
        }
    }
}

答案 1 :(得分:1)

由于您首先按Rank排序,并且Rank值在每个序列中相同,因此您应该能够独立排序然后连接。这听起来像是打嗝,根据你的帖子,实体框架不会维持ConcatUnion操作的排序。你应该能够通过强制连接发生在客户端来解决这个问题:

var rankedResults = startsWithResults.OrderBy(orderBy)
                                     .AsEnumerable()
                                     .Concat(containsResults.OrderBy(orderBy));

这应该使Rank属性不必要,并且可能简化对数据库执行的SQL查询,并且不需要对表达式树进行修改。

缺点是,一旦调用AsEnumerable(),就不再可以选择附加额外的数据库端操作(即,如果在Concat之后链接其他LINQ运算符,它们将使用LINQ到集合的实现)。看看你的代码,我不认为这对你来说是个问题,但值得一提。