How can I transform this linq expression?

我的梦境 提交于 2019-12-04 09:21:07

This should help. However you are going to have to concrete up the Anonymous type for this to work. My LinqPropertyChain will not work with it, since its going to be difficult to create the Expression<Func<Anonymous, Person>> whilst its still Anonymous.

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;
            }
        }
    }
}

Figured out a way to do this with less 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;
            }
        }
    }
}

Since you're ordering by Rank first, and the Rank values are identical within each sequence, you should be able to just sort independently and then concatenate. It sounds like the hiccup here would be that, according to your post, Entity Framework isn't maintaining sorting across Concat or Union operations. You should be able to get around this by forcing the concatenation to happen client-side:

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

This should render the Rank property unnecessary and probably simplify the SQL queries being executed against your database, and it doesn't require mucking about with expression trees.

The downside is that, once you call AsEnumerable(), you no longer have the option of appending additional database-side operations (i.e., if you chain additional LINQ operators after Concat, they will use the LINQ-to-collections implementations). Looking at your code, I don't think this would be a problem for you, but it's worth mentioning.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!