How to dynamic add filters to a LINQ query against an Odata Source in C#

爱⌒轻易说出口 提交于 2019-12-22 11:09:25

问题


I have a query like the following

var query = (from x in NetworkDevices
where   
x.Name == "blabla1" ||
x.Name == "blabla2" 
select x );

and i'm running it against an Odata endpoint, so it effectively gets translated into the following URL

https://targetserver/endpoint.svc/NetworkDevices()?$filter=Name eq 'blabla1' or Name eq 'blabla2'

So i want to dynamically add lots of those where filters... In C# i can just keep adding it to my query, but that isn't dynamic. I want to do it at runtime. If i was calling this from Javascript, well i can easily just update the URL as well.

My Question, is in C# how do i dynamically add these filters to the where clause.

in plain old LINQ (like linq 2 objects) i could do something like this.

var machines = new string[] { "blabla1" , "blabla2" } ;
res1.Where ( x => machines.Contains(x.Name) ).ToArray()

and that would work, but that doesn't work against the Odata Endpoint as i get an error like this.

The method 'Contains' is not supported

So i presume the only way would be to dynamically , edit the expression tree or something to add those filters. Does anybody know how to do that?


回答1:


Following some other links , i found a couple of options. One would be to dynamically create the expression tree which became a quagmire rather quickly, the other is to build the $filter manually. However and add it with .AddQueryOption() . However if there are already other where clauses in the query this breaks since the resulting URL now has two $filter entries.. So what i do is take my original query, and then grab the URL and the querystring and grab the $filter , and then if it exists add my own dynamic stuff and run a new query. here is a demo (running in linqpad)

//Grab original query as a DataServiceQuery
DataServiceQuery<NetworkDevice> originalquery = (DataServiceQuery<NetworkDevice>) 
    (from x in NetworkDevices
    where   
    x.Type == "switch"
    select x);
//Get the HTTP QueryString
var querystr = (originalquery).RequestUri.Query;
var filter = System.Web.HttpUtility.ParseQueryString(querystr)["$filter"];

/* Create our own dynamic filter equivilant to 
    x.Name == "x" ||
    x.Name == "y" 
*/
string[] names = { "device1", "device2" };
StringBuilder sb = new StringBuilder();
sb.Append("(");
foreach (string s in names)
{
    sb.Append(String.Format("Name eq '{0}'",s));
    sb.Append(" or ");
}
sb.Remove(sb.Length - 4, 4);
sb.Append(")");
var dynamicfilter = sb.ToString();
// If there was an original filter we'll add the dynamic one with AND , otherwise we'll just use the dynamicone
var newfilter = dynamicfilter;
if ( filter != null && filter.Trim() != string.Empty )
{
newfilter = filter + " and " + newfilter;
}
newfilter.Dump();


var finalquery = 
    (from x in NetworkDevices.AddQueryOption("$filter",newfilter)
    select x).Take(50);

finalquery.Dump();



回答2:


Here's an example of an ExpressionVistor which I used to convert a Contains into an OrElse:

    public class WhereContainsTreeModifier : ExpressionVisitor
    {
        private Expression TranslateContains(LambdaExpression lambda)
        {
            var methodCall = lambda.Body as MethodCallExpression;
            var member = methodCall.Object as MemberExpression;
            var objectMember = Expression.Convert(member, typeof(object));
            var getterLambda = Expression.Lambda<Func<object>>(objectMember);
            var getter = getterLambda.Compile();

            var list = (IEnumerable)getter();

            Expression result = null;
            foreach (object item in list)
            {
                var equal = Expression.Equal(methodCall.Arguments[0], Expression.Constant(item));
                if (result == null)
                    result = equal;
                else
                    result = Expression.OrElse(result, equal);
            }

            result = Expression.Lambda(lambda.Type, result, lambda.Parameters);
            return result;
        }

        protected override Expression VisitLambda<T>(Expression<T> node)
        {
            if ((node.Body as MethodCallExpression).Method.Name == "Contains")
                return TranslateContains(node);
            else
                return node;
        }
    }


来源:https://stackoverflow.com/questions/15777219/how-to-dynamic-add-filters-to-a-linq-query-against-an-odata-source-in-c-sharp

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!