Search code examples
c#linqlambdaexpressionentity-framework-core

How to create "inline if statement" with expressions in dynamic select for null checking


How to create "inline if statement" with expressions, in dynamic select, for null checking ?

I wrote a dynamic select LINQ expression, for a nested property of an object, but it throws an exception when it is null. so i want to check whether that property is null or not, that simple !

here is what i mean:

X.Where(...)
 .Select(X => new Y{
    ...
    Z = X.Titles == null ? "" : [Linq]
    ...
}).FirstOrDefault();

here is what i wrote

private static Expression GetLocalizedString(Expression stringExpression, SupportedCulture supportedCulture)
    {
        var expression = Expression.Parameter(typeof(APILocalizedString), nameof(APILocalizedString));
        
        var prop = Expression.Property(expression, nameof(APILocalizedString.SupportedCulture));
        var value = Expression.Constant(supportedCulture);
        var condition = Expression.Equal(prop, value);

        var where = Expression.Call(
            typeof (Enumerable),
            nameof(Enumerable.Where),
            new Type[] { typeof(APILocalizedString) },
            stringExpression,
            Expression.Lambda<Func<APILocalizedString, bool>>(condition, expression));

        var select = Expression.Call(
            typeof(Enumerable),
            nameof(Enumerable.Select),
            new Type[] { typeof(APILocalizedString), typeof(string) },
            where,
            Expression.Lambda<Func<APILocalizedString, string>>(
                Expression.Property(expression, nameof(APILocalizedString.Text)),
                expression
            ));

        var first = Expression.Call(
            typeof(Enumerable),
            nameof(Enumerable.First),
            new Type[] { typeof(APILocalizedString) },
            stringExpression);

        var defaultIfEmpty = Expression.Call(
            typeof(Enumerable),
            nameof(Enumerable.DefaultIfEmpty),
            new Type[] { typeof(string) },
            select,
            first);

        var firstOrDefault =
            Expression.Call(
            typeof(Enumerable),
            nameof(Enumerable.FirstOrDefault),
            new Type[] { typeof(string) },
            defaultIfEmpty);


        var nullCheck = Expression.Equal(stringExpression, Expression.Constant(null, stringExpression.Type));
        var result = Expression.IfThenElse(nullCheck, Expression.Constant(""), firstOrDefault);
            
        return result;
    }

here is what GetLocalizedString generated:

{IIF((X.Titles == null), "", X.Titles.Where(APILocalizedString => (APILocalizedString.SupportedCulture == EN)).DefaultIfEmpty(X.Titles.First()).Select(APILocalizedString => APILocalizedString.Text).FirstOrDefault())}

Select expression

... bindings.Add(Expression.Bind(property, GetLocalizedString(Expression.Property(parameter, "Titles"), SupportedCulture.EN))); ...

and here is the error message:

System.ArgumentException: 'Argument types do not match'

Select property is of type String

is there any way to create an expresion like X.Titles == null ? "" : [Linq] ?


Solution

  • The expression equivalent of the C# conditional ?: operator is Expression.Condition. While Expression.IfThenElse you are using is the equivalent of C# if then else block.

    Both methods return ConditionalExpression with Test, IfTrue and IfFalse properties populated. The difference is that the result Type of the Condition is the type of the operands, while for IfThenElse it is void, hence cannot be used in query expression trees.

    So the answer to your concrete question is:

    var result = Expression.Condition(nullCheck, Expression.Constant(""), firstOrDefault);
    

    P.S. As a side node, I'm getting several errors from your code snippet, so I had to rearrange it like this in order to get w/o error to the above line:

    private static Expression GetLocalizedString(Expression stringExpression, SupportedCulture supportedCulture)
    {
        var expression = Expression.Parameter(typeof(APILocalizedString), nameof(APILocalizedString));
    
        var prop = Expression.Property(expression, nameof(APILocalizedString.SupportedCulture));
        var value = Expression.Constant(supportedCulture);
        var condition = Expression.Equal(prop, value);
    
        var where = Expression.Call(
            typeof(Enumerable),
            nameof(Enumerable.Where),
            new Type[] { typeof(APILocalizedString) },
            stringExpression,
            Expression.Lambda<Func<APILocalizedString, bool>>(condition, expression));
    
        var first = Expression.Call(
            typeof(Enumerable),
            nameof(Enumerable.First),
            new Type[] { typeof(APILocalizedString) },
            stringExpression);
    
        var defaultIfEmpty = Expression.Call(
            typeof(Enumerable),
            nameof(Enumerable.DefaultIfEmpty),
            new Type[] { typeof(APILocalizedString) },
            where,
            first);
    
        var select = Expression.Call(
            typeof(Enumerable),
            nameof(Enumerable.Select),
            new Type[] { typeof(APILocalizedString), typeof(string) },
            defaultIfEmpty,
            Expression.Lambda<Func<APILocalizedString, string>>(
                Expression.Property(expression, nameof(APILocalizedString.Text)),
                expression
            ));
    
        var firstOrDefault =
            Expression.Call(
            typeof(Enumerable),
            nameof(Enumerable.FirstOrDefault),
            new Type[] { typeof(string) },
            select);
    
    
        var nullCheck = Expression.Equal(stringExpression, Expression.Constant(null, stringExpression.Type));
        var result = Expression.Condition(nullCheck, Expression.Constant(""), firstOrDefault);
    
        return result;
    }