Search code examples
c#entity-framework-coreexpression-treesiqueryablelinq-expressions

Column Mapping Using Expressions and Dictionary


I'm having trouble coding an expression for IQueryable object. I can't figure out the right way to use the expression to map a string to object properties.

this is my query object and mapping dictionary:

var query = context.Industries.AsQueryable();
var columnsMap = new Dictionary<string, Expression<Func<Industry, object>>>()
            {
                ["name"] = v => v.Name,
                ["isicCode"] = v => v.IsicCode.Data,
                ["isicCodeTitle"] = v => v.IsicCode.Title,
                ["isActive"] = v => v.IsActive,
            };

and I'm using columnsMap dictionary for applying Orderby to my query in an extentions class:

public static IQueryable<T> ApplyOrdering<T>(this IQueryable<T> query, IQueryObject queryObj, Dictionary<string, Expression<Func<T, object>>> columnsMap)
{
    if (String.IsNullOrWhiteSpace(queryObj.SortBy) || !columnsMap.ContainsKey(queryObj.SortBy))
        return query;
    if (queryObj.IsSortAsc)
        return query.OrderBy(columnsMap[queryObj.SortBy]);
    else
        return query.OrderByDescending(columnsMap[queryObj.SortBy]);
}

it's ok for OrderBy but I need to do the same thing for ApplyFiltering but for filter an IQueryable object I need a different expression for where method Expression<Func<T, bool>>

public static IQueryable<T> ApplyFiltering<T>(this IQueryable<T> query, IQueryObject queryObj, Dictionary<string, Expression<Func<T, object>>> columnsMap)
{           
    query.Where(columnsMap['name'] == "test Name"); //this is the problem. 
    return query;
}

the question is how can I use my columnsMap in ApplyFiltering method? or should I change my columnsMap for that?


Solution

  • It is quite simple. The only problem is when you have a value type column (an int or a bool or a DateTime for example)... An Expression<Func<Industry, object>> will introduce a boxing of the field/property to object that we have to remove. This problem is absent for strings.

    // isActive is a bool
    Expression<Func<Industry, object>> exp = columnsMap["isActive"];
    object value = true; // You can't use "true" (string) here! isActive is a bool
    
    // Other exammple
    // Expression<Func<Industry, object>> exp = columnsMap["name"];
    // object value = "Foo";
    
    var body = exp.Body;
    
    // Remove the boxing for value types
    if (body.NodeType == ExpressionType.Convert)
    {
        body = ((UnaryExpression)body).Operand;
    }
    
    var eq = Expression.Equal(body, Expression.Constant(value, body.Type));
    
    var exp2 = Expression.Lambda<Func<T, bool>>(eq, exp.Parameters);
    
    return query.Where(exp2);
    

    Example using string value:

    Expression<Func<Industry, object>> exp = columnsMap["isActive"];
    string value = "true";
    // Other exammple
    //Expression<Func<Industry, object>> exp = columnsMap["name"];
    //string value = "Foo";
    
    var body = exp.Body;
    
    // Remove the boxing for value types
    if (body.NodeType == ExpressionType.Convert)
    {
        body = ((UnaryExpression)body).Operand;
    }
    
    object value2 = value;
    
    if (value2 != null && body.Type != value2.GetType())
    {
        value2 = Convert.ChangeType(value2, body.Type);
    }
    
    var eq = Expression.Equal(body, Expression.Constant(value2, body.Type));
    
    var exp2 = Expression.Lambda<Func<Industry, bool>>(eq, exp.Parameters);
    
    return query.Where(exp2);