Creating a dynamic Linq extraction clause from expressions

Let's say I defined the following variables:

IQueryable<MyClass> myQueryable;
Dictionary<string, Expression<Func<MyClass, bool>>> extraFields;
// the dictionary is keyed by a field name

Now I want to bind some dynamic fields to IQueryable so that it returns IQueryable<ExtendedMyClass>where it ExtendedMyClassis defined as:

class ExtendedMyClass
{
  public MyClass MyObject {get; set;}
  public IEnumerable<StringAndBool> ExtraFieldValues {get; set;}
}

class StringAndBool
{
  public string FieldName {get; set;}
  public bool IsTrue {get; set;}
}

In other words, for each value in extraFieldsI want to have a value in ExtendedMyClass.ExtraFieldValuesrepresenting whether this expression is True or not for this string.

I have a feeling that this should be done in dynamic Linq and LinqKit, although I have never used this seriously before. I am also open to other suggestions, especially if it can be done in a good Linq style.

I use Linq for Entities, so the query needs to be translated into SQL.

+4
2

, , , , .

, , , . , , , . , , , , . , .

public static Expression<Func<TFirstParam, TResult>>
    Combine<TFirstParam, TIntermediate, TResult>(
    this Expression<Func<TFirstParam, TIntermediate>> first,
    Expression<Func<TFirstParam, TIntermediate, TResult>> second)
{
    var param = Expression.Parameter(typeof(TFirstParam), "param");

    var newFirst = first.Body.Replace(first.Parameters[0], param);
    var newSecond = second.Body.Replace(second.Parameters[0], param)
        .Replace(second.Parameters[1], newFirst);

    return Expression.Lambda<Func<TFirstParam, TResult>>(newSecond, param);
}

. , .

:

internal class ReplaceVisitor : ExpressionVisitor
{
    private readonly Expression from, to;
    public ReplaceVisitor(Expression from, Expression to)
    {
        this.from = from;
        this.to = to;
    }
    public override Expression Visit(Expression node)
    {
        return node == from ? to : base.Visit(node);
    }
}

public static Expression Replace(this Expression expression,
    Expression searchEx, Expression replaceEx)
{
    return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}

, , . , , , .

; , ( ) .

public static Expression<Func<T, IEnumerable<TResult>>> AsSequence<T, TResult>(
    this IEnumerable<Expression<Func<T, TResult>>> expressions)
{
    var param = Expression.Parameter(typeof(T));
    var body = Expression.NewArrayInit(typeof(TResult),
        expressions.Select(selector =>
            selector.Body.Replace(selector.Parameters[0], param)));
    return Expression.Lambda<Func<T, IEnumerable<TResult>>>(body, param);
}

, , .

, MyClass StringAndBool, . Combine , lambda StringAndBool .

IEnumerable<Expression<Func<MyClass, StringAndBool>>> stringAndBools =
    extraFields.Select(pair => pair.Value.Combine((foo, isTrue) =>
        new StringAndBool()
        {
            FieldName = pair.Key,
            IsTrue = isTrue
        }));

AsSequence, , :

Expression<Func<MyClass, IEnumerable<StringAndBool>>> extrafieldsSelector =
    stringAndBools.AsSequence();

. Combine , MyClass ExtendedMyClass :

var finalQuery = myQueryable.Select(
    extrafieldsSelector.Combine((foo, extraFieldValues) =>
        new ExtendedMyClass
        {
            MyObject = foo,
            ExtraFieldValues = extraFieldValues,
        }));

, , , :

var finalQuery = myQueryable.Select(extraFields
    .Select(pair => pair.Value.Combine((foo, isTrue) =>
        new StringAndBool()
        {
            FieldName = pair.Key,
            IsTrue = isTrue
        }))
    .AsSequence()
    .Combine((foo, extraFieldValues) =>
        new ExtendedMyClass
        {
            MyObject = foo,
            ExtraFieldValues = extraFieldValues,
        }));

, , Expression , , , , . , , , , , , , , - , , , . , , , -, .

+6

, extraFields, , , , , .

, :

var extraFields = new Dictionary<string, Expression<Func<MyClass, bool>>>
{
    { "Foo", x => x.Foo },
    { "Bar", x => x.Bar }
};

- :

myQueryable.Select(
    x => new ExtendedMyClass
    {
        MyObject = x,
        ExtraFieldValues =
            new[]
            {
                new StringAndBool { FieldName = "Foo", IsTrue = x.Foo },
                new StringAndBool { FieldName = "Bar", IsTrue = x.Bar }
            }
    });

API LINQKit :

public static IQueryable<ExtendedMyClass> Extend(
    IQueryable<MyClass> myQueryable,
    Dictionary<string, Expression<Func<MyClass, bool>>> extraFields)
{
    Func<Expression<Func<MyClass, bool>>, MyClass, bool> invoke =
        LinqKit.Extensions.Invoke;

    var parameter = Expression.Parameter(typeof(MyClass));

    var extraFieldsExpression =
        Expression.Lambda<Func<MyClass, StringAndBool[]>>(
            Expression.NewArrayInit(
                typeof(StringAndBool),
                extraFields.Select(
                    field => Expression.MemberInit(
                        Expression.New(typeof(StringAndBool)),
                        new MemberBinding[]
                        {
                            Expression.Bind(
                                typeof(StringAndBool).GetProperty("FieldName"),
                                Expression.Constant(field.Key)),
                            Expression.Bind(
                                typeof(StringAndBool).GetProperty("IsTrue"),
                                Expression.Call(
                                    invoke.Method,
                                    Expression.Constant(field.Value),
                                    parameter))
                        }))),
            parameter);

    Expression<Func<MyClass, ExtendedMyClass>> selectExpression =
        x => new ExtendedMyClass
        {
            MyObject = x,
            ExtraFieldValues = extraFieldsExpression.Invoke(x)
        };

    return myQueryable.Select(selectExpression.Expand());
}
+2

Source: https://habr.com/ru/post/1538738/


All Articles