Entity to DropDownListItem using Lambda expressions in method

463 Views Asked by At

I have Entity e.g. CategoryEntity, RubricEntity, CityEntity etc. I need to show dropdowns for such entities. This entities have different properties. All of them I need to convert to DropDownListItem to show as dropdown, so I think I can use such method to work with DB, but I getting exception

The LINQ expression node type 'Invoke' is not supported in LINQ to Entities.

public static IQueryable<DropDownListItem> ToDropDownList<TSource>(IQueryable<TSource> query, Expression<Func<TSource, long>> value, Expression<Func<TSource, string>> text)
        {
            var valueLambda = value.Compile();
            var textLambda = text.Compile();

            return query.Select(x => new DropDownListItem
            {
                Value = (long) valueLambda(x),
                Text = (string) textLambda(x)
            });
        }

I think that I can use something like this for it but don't understand how to make it using expressions and lambda.

As a result I want something like

ToDropDownList2<RubricEntity>(_service.RubricAsQueryable(), x => x.Id, x => x.DisplayName)
1

There are 1 best solutions below

2
On BEST ANSWER

The problem is that Linq To Entities doesn't support compiled expressions, and you are compiled both expression and trying to invoke them. But LINQ is trying to parse them as expressions and translate them into SQL code, and it can't do it. And you want to pass both text and value as different parameters, so you can't combine them.

You can do like this:

public static IQueryable<DropDownListItem> ToDropDownList<TSource>(IQueryable<TSource> query, Expression<Func<TSource, DropDownListItem>> value)
{
    return query.Select(value);
}

ToDropDownList<RubricEntity>(_service.RubricAsQueryable(), x => new DropDownListItem() { Value = x.Id, Text = x.DisplayName });

But as for me it has't a lot of sense...

If you still want to pass them as separate parameters, then you can try combine them in runtime like this:

public static IQueryable<DropDownListItem> ToDropDownList<TSource>(IQueryable<TSource> query, Expression<Func<TSource, long>> value, Expression<Func<TSource, string>> text)
{
    Expression<Func<TSource, DropDownListItem>> func = x => new DropDownListItem
    {
        Value = 1,
        Text = "1"
    };


    var replacer = new ExpressionReplacer<TSource>()
                   {
                       Text = text,
                       Value = value,
                       Parameter = func.Parameters[0] // we will take X parameter

                   };
    var convertedFunc = replacer.Visit(func) as Expression<Func<TSource, DropDownListItem>>;

    return query.Select(convertedFunc);
}


private class ExpressionReplacer<TSource> : ExpressionVisitor
{
    public Expression<Func<TSource, long>> Value { get; set; }
    public Expression<Func<TSource, string>> Text { get; set; }
    public ParameterExpression Parameter { get; set; }

    protected override Expression VisitConstant(ConstantExpression node)
    {
        if (node.Type == typeof(long))
            return this.Visit(Value.Body);
        if (node.Type == typeof(string)) 
            return this.Visit(Text.Body);
        return base.VisitConstant(node);
    }

    protected override Expression VisitParameter(ParameterExpression node)
    {
        // we will replace all usage to X. it has the same type, but it isn't linked to expiression
        return Parameter; 
    }
}

ToDropDownList<RubricEntity>(_service.RubricAsQueryable(), x => x.Key, x => x.Value);

Basically we just create stub expression with constant values, and then replace constant values depending on type to expression that comes in paremeters. And then send replaced expression to Select method, so it finaly expression will be look something like:

Select(x => new DropDownListItem() { Value = x.Id, Text = x.DisplayName })