Search code examples
c#linqexpressionodata

Operator "IN" by key field in Linq to OData


Goodafternoon everyone! I'm trying to translate request in SQL:

SELECT *
FROM TABLE1
WHERE TABLE1.ID IN (SELECT ID FROM TABLE2)

into LINQ to OData. As far as "IN" is not supported by ODAta protocol,WHERE part must be like TABLE1.ID=1 OR TABLE1.ID=2 OR ..., I've tried to code generic method, that takes as input list of id's and returns correct Expression for LINQ in that way:

public static Expression<Func<T,bool>> Lambda<T>(this Expression expr ,List<int> ids)
    {
        ParameterExpression argParam = Expression.Parameter(typeof(T), "rep");
        Expression<Func<T, bool>> lambda = code => 1 == 0;
        var lambdaPred = Expression.Lambda<Func<T, bool>>(lambda.Body, argParam);
        var attr = (DataServiceKeyAttribute)typeof(T).GetCustomAttribute(typeof(DataServiceKeyAttribute));
        string keyName;
        try
        {
            keyName = attr.KeyNames.FirstOrDefault();//get name of key attribute
        }
        catch
        {
            return null;
        }
        foreach (int id in ids)
        {
            var property = typeof(T).GetProperty(keyName);
            Expression<Func<T, bool>> lambdatemp = code => (int)property.GetValue(code) == id;                               
            var tmp = Expression<Func<T, bool>>.Or(lambdaPred.Body, lambdatemp.Body);                
            lambdaPred = Expression.Lambda<Func<T, bool>>(tmp, argParam);
        }
        return lambdaPred;
    }

usage of this method:

Expression<Func<Client, bool>> lambda = code => 1 == 0;
var query = lambda.Body.Lambda<Client>(ids);
var retr = clientRepository.Retrieve(query).ToList();

but at runtime I'm getting error:

An exception of type 'System.NotSupportedException' occurred in Microsoft.Data.Services.Client.dll but was not handled in user code

Additional information: The expression (((((False Or (Convert(Int32 Id.GetValue(code)) == 1044)) Or (Convert(Int32 Id.GetValue(code)) == 8102)) Or (Convert(Int32 Id.GetValue(code)) == 5997)) Or (Convert(Int32 Id.GetValue(code)) == 7761)) Or (Convert(Int32 Id.GetValue(code)) == 15455)) is not supported.

Do you know any ways to fix this problem?


Solution

  • The generic method you wrote is quite unclear (both signature and implementation). As I understand, the idea is to build expression like this

    x => x.Id == Id1 || x.Id == Id2 || ....
    

    Here is one possible way of such a method by using Expression.Equal and Expression.OrElse

    public static class PredicateHelper
    {
        public static Expression<Func<T, bool>> In<T>(this Expression<Func<T, int>> idSelector, IEnumerable<int> ids)
        {
            Expression body = null;
            foreach (var id in ids)
            {
                var operand = Expression.Equal(idSelector.Body, Expression.Constant(id));
                body = body == null ? operand : Expression.OrElse(body, operand);
            }
            return body != null ? Expression.Lambda<Func<T, bool>>(body, idSelector.Parameters) : null;
        }
    }
    

    Sample usage equivalent to your example

    var idFilter = PredicateHelper.In((Client c) => c.Id, ids);
    var result = clientRepository.Retrieve(idFilter).ToList();