将 Linq Select 重写为新的子路径

Rewriting Linq Select to a new subpath

我目前正在尝试动态构建 linq 查询。我希望能够在其他 Linq 表达式中重用 Linq 表达式。 例如:

    public class Dummy
    {

        public string Test { get; set; }
        public Sub Sub { get; set; }
    }


    public class Sub
    {
        public static Expression<Func<Sub, string>> Converter
        {
            get
            {
                return x => x.Id + ": " + x.Text;
            }
        }

        public int Id { get; set; }
        public string Text { get; set; }
    }

为 Dummy class 编写转换器时,转换器应该能够重用 Sub.Converter。为此,我编写了一个 DynamicSelect<> 扩展方法:

var result = Query
                .Where(x=>x.Sub != null)
                .DynamicSelect(x => new Result())
                    .Select(x => x.SubText, x => x.Sub,Sub.Converter)
                .Apply()
            .ToList();

DynamicSelect 创建一个新的 SelectionBuilder, select 部分将目标 属性 (Result.SubText) 作为第一个输入, 作为第二个 属性,我们要转换的输入 属性,作为第三个输入,Sub 属性 的转换器。 然后 .Apply 调用 returns 构建表达式树。

我设法让它适用于更简单的用例(没有子路径):

var result = Query.DynamicSelect(x => new Result())
                .Select(x => x.ResolvedTest, x => x.Inner == null ? x.Test : x.Inner.Test)
                .Select(x => x.SubText, x => x.Sub == null ? null : x.Sub.Text)
                .Apply()
                .ToList();

但是如何将表达式变基到另一个子路径?

到目前为止的代码:

public static class SelectBuilder
{
    public static LinqDynamicConverter<TInput,TOutput> DynamicSelect<TInput,TOutput>(
        this IQueryable<TInput> data,
        Expression<Func<TInput, TOutput>> initialConverter) where TOutput : new()
    {
        return new LinqDynamicConverter<TInput,TOutput>(data, initialConverter);
    }
}

public class LinqDynamicConverter<TInput,TOutput> where TOutput: new()
{
    #region inner classes

    private class MemberAssignmentVisitor : ExpressionVisitor
    {
        private IDictionary<MemberInfo, MemberAssignment> SinglePropertyToBinding { get; set; }

        public MemberAssignmentVisitor(IDictionary<MemberInfo, MemberAssignment> singlePropertyToBinding)
        {
            SinglePropertyToBinding = singlePropertyToBinding;
        }

        protected override MemberAssignment VisitMemberAssignment(MemberAssignment node)
        {
            SinglePropertyToBinding[node.Member] = node;
            return base.VisitMemberAssignment(node);
        }
    }

    private class MemberInfoVisitor : ExpressionVisitor
    {
        internal MemberInfo SingleProperty { get; private set; }

        public MemberInfoVisitor()
        {
        }

        protected override Expression VisitMember(MemberExpression node)
        {
            SingleProperty = node.Member;

            return base.VisitMember(node);
        }
    }

    #endregion
    #region properties

    private IQueryable<TInput> Data { get;set; }
    private Expression<Func<TInput, TOutput>> InitialConverter { get;set;}
    private IDictionary<MemberInfo, MemberAssignment> SinglePropertyToBinding { get; set; }

    #endregion

    #region constructor

    internal LinqDynamicConverter(IQueryable<TInput> data,
        Expression<Func<TInput, TOutput>> initialConverter)
    {
        Data = data;

        InitialConverter = x => new TOutput(); // start with a clean plate
        var replace = initialConverter.Replace(initialConverter.Parameters[0], InitialConverter.Parameters[0]);
        SinglePropertyToBinding = new Dictionary<MemberInfo, MemberAssignment>();
        MemberAssignmentVisitor v = new MemberAssignmentVisitor(SinglePropertyToBinding);
        v.Visit(initialConverter);

    }

    #endregion

    public LinqDynamicConverter<TInput,TOutput> Select<TProperty,TConverted>(
        Expression<Func<TOutput, TConverted>> initializedOutputProperty,
        Expression<Func<TInput, TProperty>> subPath,
        Expression<Func<TProperty, TConverted>> subSelect)
    {
        //????

        return this;
    }

    // this one works
    public LinqDynamicConverter<TInput,TOutput> Select<TProperty>(
        Expression<Func<TOutput, TProperty>> initializedOutputProperty,
        Expression<Func<TInput, TProperty>> subSelect)
    {
        var miv = new MemberInfoVisitor();
        miv.Visit(initializedOutputProperty);
        var mi = miv.SingleProperty;

        var param = InitialConverter.Parameters[0];
        Expression<Func<TInput, TProperty>> replace = (Expression<Func<TInput, TProperty>>)subSelect.Replace(subSelect.Parameters[0], param);
        var bind = Expression.Bind(mi, replace.Body);
        SinglePropertyToBinding[mi] = bind;

        return this;
    }

    public IQueryable<TOutput> Apply()
    {
        var converter = Expression.Lambda<Func<TInput, TOutput>>(
            Expression.MemberInit((NewExpression)InitialConverter.Body, SinglePropertyToBinding.Values), InitialConverter.Parameters[0]);
        return Data.Select(converter);
    }
}

找到解决方案。我需要编写一个新的表达式访问者来添加额外的成员访问:

    /// <summary>
    /// rebinds a full expression tree to a new single property
    /// Example: we get x => x.Sub as subPath. Now the visitor starts visiting a new
    /// expression x => x.Text. The result should be x => x.Sub.Text.
    /// Traversing member accesses always starts with the rightmost side working toward the parameterexpression.
    /// So when we reach the parameterexpression we have to inject the whole subpath including the parameter of the subpath
    /// </summary>
    /// <typeparam name="TConverted"></typeparam>
    /// <typeparam name="TProperty"></typeparam>
    private class LinqRebindVisitor<TConverted, TProperty> : ExpressionVisitor
    {
        public Expression<Func<TInput, TConverted>> ResultExpression { get; private set; }
        private ParameterExpression SubPathParameter { get; set; }
        private Expression SubPathBody { get; set; }
        private bool InitialMode { get; set; }

        public LinqRebindVisitor(Expression<Func<TInput, TProperty>> subPath)
        {
            SubPathParameter = subPath.Parameters[0];
            SubPathBody = subPath.Body;
            InitialMode = true;
        }

        protected override Expression VisitMember(MemberExpression node)
        {
            // Note that we cannot overwrite visitparameter because that method must return a parameterexpression
            // So whenever we detect that our next expression will be a parameterexpression, we inject the subtree
            if (node.Expression is ParameterExpression && node.Expression != SubPathParameter)
            {
                var expr = Visit(SubPathBody);
                return Expression.MakeMemberAccess(expr, node.Member);
            }
            return base.VisitMember(node);
        }

        protected override Expression VisitLambda<T>(Expression<T> node)
        {
            bool initialMode = InitialMode;
            InitialMode = false;
            Expression<T> expr = (Expression<T>)base.VisitLambda<T>(node);
            if (initialMode)
            {
                ResultExpression = Expression.Lambda<Func<TInput, TConverted>>(expr.Body,SubPathParameter);
            }
            return expr;
        }
    }

单个 属性 Select 方法相当简单:

    public LinqDynamicConverter<TInput,TOutput> Select<TProperty,TConverted>(
        Expression<Func<TOutput, TConverted>> initializedOutputProperty,
        Expression<Func<TInput, TProperty>> subPath,
        Expression<Func<TProperty, TConverted>> subSelect)
    {
        LinqRebindVisitor<TConverted, TProperty> rebindVisitor = new LinqRebindVisitor<TConverted, TProperty>(subPath);
        rebindVisitor.Visit(subSelect);
        var result = rebindVisitor.ResultExpression;
        return Property<TConverted>(initializedOutputProperty, result);
    }