Programmatically chain OrderBy/ThenBy using LINQ / Entity Framework - linq

I have a reporting interface where the end user gets to select multiple fields for the sort order of the returned report. The problem I am having is that I can't really chain the OrderBy / ThenBy methods, since I'm iterating through a list of sort fields. I'm thinking something like this:
foreach (string sort in data.SortParams)
{
switch (sort)
{
case "state":
query = query.ThenBy(l => l.RegionCode);
break;
case "type":
query = query.ThenBy(l => l.Type);
break;
case "color":
query = query.ThenBy(l => l.Color);
break;
case "category":
query = query.OrderBy(l => l.Category);
break;
}
}
(Note: I've removed the switch determining if this is the first sort item for simplicity's sake.)
Any thoughts on how to iterate through a collection to determine the sort order?

You could do what you want if you use an initial "seed" OrderBy:
EDIT you need to call OrderBy to create an IOrderedEnumerable (or IOrderedQueryable) first before attaching ThenBy clauses:
var orderedQuery = query.OrderBy(l => 0);
foreach (string sort in data.SortParams)
{
switch (sort)
{
case "state":
orderedQuery = orderedQuery.ThenBy(l => l.RegionCode);
break;
case "type":
orderedQuery = orderedQuery.ThenBy(l => l.Type);
break;
case "color":
orderedQuery = orderedQuery.ThenBy(l => l.Color);
break;
case "category":
orderedQuery = orderedQuery.ThenBy(l => l.Category);
break;
}
}
query = orderedQuery; // cast back to original type.
If you want something more flexible check out this answer

I've created these extension methods to tackle an identical problem as stated in the question:
public static class QueryableExtensions
{
public static IOrderedQueryable<T> AppendOrderBy<T, TKey>(this IQueryable<T> query, Expression<Func<T, TKey>> keySelector)
=> query.Expression.Type == typeof(IOrderedQueryable<T>)
? ((IOrderedQueryable<T>) query).ThenBy(keySelector)
: query.OrderBy(keySelector);
public static IOrderedQueryable<T> AppendOrderByDescending<T, TKey>(this IQueryable<T> query, Expression<Func<T, TKey>> keySelector)
=> query.Expression.Type == typeof(IOrderedQueryable<T>)
? ((IOrderedQueryable<T>)query).ThenByDescending(keySelector)
: query.OrderByDescending(keySelector);
}
The code in the question could then be refactored to:
foreach (string sort in data.SortParams)
{
switch (sort)
{
case "state":
query = query.AppendOrderBy(l => l.RegionCode);
break;
case "type":
query = query.AppendOrderBy(l => l.Type);
break;
case "color":
query = query.AppendOrderBy(l => l.Color);
break;
case "category":
query = query.AppendOrderBy(l => l.Category);
break;
}
}
REMARK These extension methods only check the previous expression in the expression tree to determine wether to use OrderBy or ThenBy, no other expressions are allowed in-between. If you also want to tackle that, you'll have to walk through the complete tree which might just add that overhead you don't want :)

using System.Linq.Expressions;
abstract class BaseEntity
{
public int Id { get; set; }
}
class Product : BaseEntity
{
public string Name { get; set; } = String.Empty;
public override string ToString() => $"Id: {Id}, Name: {Name}";
}
class OrderSpecification<T> where T : BaseEntity
{
public bool IsDescending { get; private set; }
public Expression<Func<T, object>> Selector { get; private set; }
public OrderSpecification(Expression<Func<T, object>> selector, bool isDescending = false)
{
Selector = selector;
IsDescending = isDescending;
}
}
static class EntityExtensions
{
public static IQueryable<T> Sort<T>(this IQueryable<T> query, OrderSpecification<T>[] specs) where T : BaseEntity
{
if (specs.Length == 0)
return query;
int i = 0;
foreach (var spec in specs)
{
if (i++ == 0)
query = spec.IsDescending ? query.OrderByDescending(spec.Selector) : query.OrderBy(spec.Selector);
else
{
var temp = query as IOrderedQueryable<T>;
query = spec.IsDescending ? temp!.ThenByDescending(spec.Selector) : temp!.ThenBy(spec.Selector);
}
}
return query;
}
}
class MyDatabase
{
static void Main()
{
var products = new[]
{
new Product{ Id=4, Name="Cindy"},
new Product{ Id=2, Name="Andy"},
new Product{ Id=1, Name="Victor"},
new Product{ Id=1, Name="Bobby"},
new Product{ Id=3, Name="Austin"}
}
.AsQueryable();
var output = products.Sort(new OrderSpecification<Product>[]
{
new OrderSpecification<Product>(p=>p.Id), // Order by Id first,
new OrderSpecification<Product>(p=>p.Name, true), // then order by Name descendingly
});
foreach (var p in output) Console.WriteLine(p);
Console.ReadKey();
}
}

May be using all in one Linq query is not the best option from readability point of view. I would use IQueryable to construct your query in memory. Use similar sort of switch statement (but with IQueryable) and then in the end do .ToList (i.e. enumerate) to execute desired query at the server.

Related

Intersection of arrays in LINQ to CosmosDB

I'm trying find all items in my database that have at least one value in an array that matches any value in an array that I have in my code (the intersection of the two arrays should not be empty).
Basically, I'm trying to achieve this :
public List<Book> ListBooks(string partitionKey, List<string> categories)
{
return _client.CreateDocumentQuery<Book>(GetCollectionUri(), new FeedOptions
{
PartitionKey = new PartitionKey(partitionKey)
})
.Where(b => b.Categories.Any(c => categories.Contains(c))
.ToList();
}
With the Book class looking like this :
public class Book
{
public string id {get;set;}
public string Title {get;set;}
public string AuthorName {get;set;}
public List<string> Categories {get;set;}
}
However the SDK throws an exception saying that Method 'Any' is not supported when executing this code.
This doesn't work either :
return _client.CreateDocumentQuery<Book>(GetCollectionUri(), new FeedOptions
{
PartitionKey = new PartitionKey(partitionKey)
})
.Where(b => categories.Any(c => b.Categories.Contains(c))
.ToList();
The following code works because there's only one category to find :
public List<Book> ListBooksAsync(string category)
{
return _client.CreateDocumentQuery<Book>(GetCollectionUri())
.Where(b => b.Categories.Contains(category))
.ToList();
}
In plain SQL, I can queue multiple ARRAY_CONTAINS with several OR the query executes correctly.
SELECT * FROM root
WHERE ARRAY_CONTAINS(root["Categories"], 'Humor')
OR ARRAY_CONTAINS(root["Categories"], 'Fantasy')
OR ARRAY_CONTAINS(root["Categories"], 'Legend')
I'm trying to find the best way to achieve this with LINQ, but I'm not even sure it's possible.
In this situation I've used a helper method to combine expressions in a way that evaluates to SQL like in your final example. The helper method 'MakeOrExpression' below lets you pass a number of predicates (in your case the individual checks for b.Categories.Contains(category)) and produces a single expression you can put in the argument to .Where(expression) on your document query.
class Program
{
private class Book
{
public string id { get; set; }
public string Title { get; set; }
public string AuthorName { get; set; }
public List<string> Categories { get; set; }
}
static void Main(string[] args)
{
var comparison = new[] { "a", "b", "c" };
var target = new Book[] {
new Book { id = "book1", Categories = new List<string> { "b", "z" } },
new Book { id = "book2", Categories = new List<string> { "s", "t" } },
new Book { id = "book3", Categories = new List<string> { "z", "a" } } };
var results = target.AsQueryable()
.Where(MakeOrExpression(comparison.Select(x => (Expression<Func<Book, bool>>)(y => y.Categories.Contains(x))).ToArray()));
foreach (var result in results)
{
// Should be book1 and book3
Console.WriteLine(result.id);
}
Console.ReadLine();
}
private static Expression<Func<T,bool>> MakeOrExpression<T>(params Expression<Func<T,bool>>[] inputExpressions)
{
var combinedExpression = inputExpressions.Skip(1).Aggregate(
inputExpressions[0].Body,
(agg, x) => Expression.OrElse(agg, x.Body));
var parameterExpression = Expression.Parameter(typeof(T));
var replaceParameterVisitor = new ReplaceParameterVisitor(parameterExpression,
Enumerable.SelectMany(inputExpressions, ((Expression<Func<T, bool>> x) => x.Parameters)));
var mergedExpression = replaceParameterVisitor.Visit(combinedExpression);
var result = Expression.Lambda<Func<T, bool>>(mergedExpression, parameterExpression);
return result;
}
private class ReplaceParameterVisitor : ExpressionVisitor
{
private readonly IEnumerable<ParameterExpression> targetParameterExpressions;
private readonly ParameterExpression parameterExpression;
public ReplaceParameterVisitor(ParameterExpression parameterExpressionParam, IEnumerable<ParameterExpression> targetParameterExpressionsParam)
{
this.parameterExpression = parameterExpressionParam;
this.targetParameterExpressions = targetParameterExpressionsParam;
}
public override Expression Visit(Expression node)
=> targetParameterExpressions.Contains(node) ? this.parameterExpression : base.Visit(node);
}
}

How to select multiple class properties in LINQ Expression?

If I have a class like this
`
class Person
{
public string First;
public string Last;
public bool IsMarried;
public int Age;
}`
Then how can I write a LINQ Expression where I could select properties of a Person. I want to do something like this (user can enter 1..n properties)
SelectData<Person>(x=>x.First, x.Last,x.Age);
What would be the input expression of my SelectData function ?
SelectData(Expression<Func<TEntity, List<string>>> selector); ?
EDIT
In my SelectData function I want to extract property names and then generate SELECT clause of my SQL Query dynamically.
SOLUTION
Ok, so what I have done is to have my SelectData as
public IEnumerable<TEntity> SelectData(Expression<Func<TEntity, object>> expression)
{
NewExpression body = (NewExpression)expression.Body;
List<string> columns = new List<string>();
foreach(var arg in body.Arguments)
{
var exp = (MemberExpression)arg;
columns.Add(exp.Member.Name);
}
//build query
And to use it I call it like this
ccc<Person>().SelectData(x => new { x.First, x.Last, x.Age });
Hopefully it would help someone who is looking :)
Thanks,
IY
I think it would be better to use delegates instead of Reflection. Apart from the fact that delegates will be faster, the compiler will complain if you try to fetch property values that do not exist. With reflection you won't find errors until run time.
Luckily there is already something like that. it is implemented as an extension function of IEnumerable, and it is called Select (irony intended)
I think you want something like this:
I have a sequence of Persons, and I want you to create a Linq
statement that returns per Person a new object that contains the
properties First and Last.
Or:
I have a sequence of Persns and I want you to create a Linq statement
that returns per Person a new object that contains Age, IsMarried,
whether it is an adult and to make it difficult: one Property called
Name which is a combination of First and Last
The function SelectData would be something like this:
IEnumerable<TResult> SelectData<TSource, TResult>(this IEnumerable<TSource> source,
Func<TSource, TResult> selector)
{
return source.Select(selector);
}
Usage:
problem 1: return per Person a new object that contains the
properties First and Last.
var result = Persons.SelectData(person => new
{
First = person.First,
Last = person.Last,
});
problem 2: return per Person a new object that contains Age, IsMarried, whether he is an adult and one Property called Name which is a combination
of First and Last
var result = Persons.SelectData(person => new
{
Age = person.Name,
IsMarried = person.IsMarried,
IsAdult = person.Age > 21,
Name = new
{
First = person.First,
Last = person.Last,
},
});
Well let's face it, your SelectData is nothing more than Enumerable.Select
You could of course create a function where you'd let the caller provide a list of properties he wants, but (1) that would limit his possibilities to design the end result and (2) it would be way more typing for him to call the function.
Instead of:
.Select(p => new
{
P1 = p.Property1,
P2 = p.Property2,
}
he would have to type something like
.SelectData(new List<Func<TSource, TResult>()
{
p => p.Property1, // first element of the property list
p -> p.Property2, // second element of the property list
}
You won't be able to name the returned properties, you won't be able to combine several properties into one:
.Select(p => p.First + p.Last)
And what would you gain by it?
Highly discouraged requirement!
You could achive similar result using Reflection and Extension Method
Model:
namespace ConsoleApplication2
{
class Person
{
public string First { get; set; }
public string Last { get; set; }
public bool IsMarried { get; set; }
public int Age { get; set; }
}
}
Service:
using System.Collections.Generic;
using System.Linq;
namespace Test
{
public static class Service
{
public static IQueryable<IQueryable<KeyValuePair<string, object>>> SelectData<T>(this IQueryable<T> queryable, string[] properties)
{
var queryResult = new List<IQueryable<KeyValuePair<string, object>>>();
foreach (T entity in queryable)
{
var entityProperties = new List<KeyValuePair<string, object>>();
foreach (string property in properties)
{
var value = typeof(T).GetProperty(property).GetValue(entity);
var entityProperty = new KeyValuePair<string, object>(property, value);
entityProperties.Add(entityProperty);
}
queryResult.Add(entityProperties.AsQueryable());
}
return queryResult.AsQueryable();
}
}
}
Usage:
using System;
using System.Collections.Generic;
using System.Linq;
namespace Test
{
class Program
{
static void Main(string[] args)
{
var list = new List<Person>()
{
new Person()
{
Age = 18,
First = "test1",
IsMarried = false,
Last = "test2"
},
new Person()
{
Age = 40,
First = "test3",
IsMarried = true,
Last = "test4"
}
};
var queryableList = list.AsQueryable();
string[] properties = { "Age", "Last" };
var result = queryableList.SelectData(properties);
foreach (var element in result)
{
foreach (var property in element)
{
Console.WriteLine($"{property.Key}: {property.Value}");
}
}
Console.ReadKey();
}
}
}
Result:
Age: 18
Last: test2
Age: 40
Last: test4

Building a query (LINQ) with a sub-query

For simplicity sake lets assume I have the following two classes:
public class ComplexClass
{
public List<SubClass> SubClasses { get; set; }
public string Name { get; set; }
}
public class SubClass
{
public string Name { get; set; }
}
I have a List<ComplexClass> and I need to build a query based on some parameters.
It's an easy task if all I need to do is use the Name property of ComplexClass. Here's an example:
static IQueryable<ComplexClass> GetQuery(string someParameter, string someOtherParameter)
{
var query = list.AsQueryable();
if (!String.IsNullOrEmpty(someParameter))
query = query.Where(c => c.Name.StartsWith(someParameter));
if (!String.IsNullOrEmpty(someOtherParameter))
query = query.Where(c => c.Name.EndsWith(someOtherParameter));
return query;
}
Based on the parameters I have I can add more query elements. Of course the above example is simple, but the actual problem contains more parameters, and that number can grow.
Things aren't as simple if I want to find those ComplexClass instances which have SubClass instances which meet criteria based on parameters:
static IQueryable<ComplexClass> GetSubQuery(string someParameter, string someOtherParameter)
{
var query = list.AsQueryable();
if (!String.IsNullOrEmpty(someParameter))
if (!String.IsNullOrEmpty(someOtherParameter))
return query.Where(c => c.SubClasses.Where(sc => sc.Name.StartsWith(someParameter) && sc.Name.EndsWith(someOtherParameter)).Any());
else
return query.Where(c => c.SubClasses.Where(sc => sc.Name.StartsWith(someParameter)).Any());
else
if (!String.IsNullOrEmpty(someOtherParameter))
return query.Where(c => c.SubClasses.Where(sc => sc.Name.EndsWith(someOtherParameter)).Any());
else
return null;
}
I can no longer just add bits of the query based on each parameter, I now need to write the whole query in one go, and this means I need to check every combination of parameters, which is hardly ideal.
I suspect the key is to build an Expression class and create a lambda expression from that, but I'm not sure how to tackle the problem.
Any suggestions? :)
EDIT:
My initial idea was this:
static IQueryable<ComplexClass> GetSubQuery(string someParameter, string someOtherParameter)
{
var query = list.AsQueryable();
query = query.Where(c =>
{
var subQuery = c.SubClasses.AsQueryable();
if (!String.IsNullOrEmpty(someParameter))
subQuery = subQuery.Where(sc => sc.Name.StartsWith(someParameter));
if (!String.IsNullOrEmpty(someOtherParameter))
subQuery = subQuery.Where(sc => sc.Name.EndsWith(someOtherParameter));
return subQuery.Any();
});
return query;
}
This works in my small console test application as it's using LINQ to Objects. Unfortunately, I need to use Entity Framework and LINQ to Entities, which causes an implementation similar to the one above to throw a A lambda expression with a statement body cannot be converted to an expression tree error message.
I'm assuming that in you real-life code the SubClasses property is IQueryable<SubClass> rather than List<SubClass>?
If so, then your query building becomes easy:
static IQueryable<ComplexClass> GetSubQuery(
string someParameter, string someOtherParameter)
{
var query = list.AsQueryable();
if (!String.IsNullOrEmpty(someParameter))
query = query.Where(c => c.SubClasses
.Where(sc => sc.Name.StartsWith(someParameter)).Any());
if (!String.IsNullOrEmpty(someOtherParameter))
query = query.Where(c => c.SubClasses
.Where(sc => sc.Name.StartsWith(someOtherParameter)).Any());
return query;
}
Mixing IEnumerable<T> and IQueryable<T> using AsQueryable() is never a good idea.
I implemented my solution in a simple Console Project:
internal class Program
{
#region Constants and Fields
private static readonly List<ComplexClass> list = new List<ComplexClass>
{
new ComplexClass
{
Name = "complex",
SubClasses = new List<SubClass>
{
new SubClass
{
SubName = "foobar"
}
}
},
new ComplexClass
{
Name = "complex",
SubClasses = new List<SubClass>
{
new SubClass
{
SubName = "notfoobar"
}
}
}
};
#endregion
#region Public Methods
public static void Main(string[] args)
{
Console.WriteLine("foo / bar :");
GetSubQuery("foo", "bar");
Console.WriteLine();
Console.WriteLine("foo / null :");
GetSubQuery("foo", null);
Console.WriteLine();
Console.WriteLine("string.Empty / bar :");
GetSubQuery(string.Empty, "bar");
Console.WriteLine();
Console.WriteLine("maeh / bar :");
GetSubQuery("maeh", "bar");
Console.ReadKey();
}
#endregion
#region Methods
private static void GetSubQuery(string startsWith,
string endsWith)
{
var query = from item in list
let StartIsNull = string.IsNullOrEmpty(startsWith)
let EndIsNull = string.IsNullOrEmpty(endsWith)
where
(StartIsNull || item.SubClasses.Any(sc => sc.SubName.StartsWith(startsWith)))
&& (EndIsNull || item.SubClasses.Any(sc => sc.SubName.EndsWith(endsWith)))
select item;
foreach (var complexClass in query)
{
Console.WriteLine(complexClass.SubClasses.First().SubName);
}
}
#endregion
public class ComplexClass
{
#region Public Properties
public string Name { get; set; }
public List<SubClass> SubClasses { get; set; }
#endregion
}
public class SubClass
{
#region Public Properties
public string SubName { get; set; }
#endregion
}
}
The Console Output is:
foo / bar :
foobar
foo / null :
foobar
string.Empty / bar :
foobar
notfoobar
maeh / bar :

How to retrieve ordering information from IQueryable object?

Let's say, I have an instance of IQueryable. How can I found out by which parameters it was ordered?
Here is how OrderBy() method looks like (as a reference):
public static IOrderedQueryable<T> OrderBy<T, TKey>(
this IQueryable<T> source, Expression<Func<T, TKey>> keySelector)
{
return (IOrderedQueryable<T>)source.Provider.CreateQuery<T>(
Expression.Call(null,
((MethodInfo)MethodBase.GetCurrentMethod()).MakeGenericMethod(
new Type[] { typeof(T), typeof(TKey) }
),
new Expression[] { source.Expression, Expression.Quote(keySelector) }
)
);
}
A hint from Matt Warren:
All queryables (even IOrderedQueryable's) have expression trees underlying them that encode the activity they represent. You should find using the IQueryable.Expression property a method-call expression node representing a call to the Queryable.OrderBy method with the actual arguments listed. You can decode from the keySelector argument the expression used for ordering. Take a look at the IOrderedQueryable object instance in the debugger to see what I mean.
This isn't pretty, but it seems to do the job:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Linq.Expressions;
using System.Windows.Forms;
public class Test
{
public int A;
public string B { get; set; }
public DateTime C { get; set; }
public float D;
}
public class QueryOrderItem
{
public QueryOrderItem(Expression expression, bool ascending)
{
this.Expression = expression;
this.Ascending = ascending;
}
public Expression Expression { get; private set; }
public bool Ascending { get; private set; }
public override string ToString()
{
return (Ascending ? "asc: " : "desc: ") + Expression;
}
}
static class Program
{
public static List<QueryOrderItem> GetQueryOrder(Expression expression)
{
var members = new List<QueryOrderItem>(); // queue for easy FILO
GetQueryOrder(expression, members, 0);
return members;
}
static void GetQueryOrder(Expression expr, IList<QueryOrderItem> members, int insertPoint)
{
if (expr == null) return;
switch (expr.NodeType)
{
case ExpressionType.Call:
var mce = (MethodCallExpression)expr;
if (mce.Arguments.Count > 1)
{ // OrderBy etc is expressed in arg1
switch (mce.Method.Name)
{ // note OrderBy[Descending] shifts the insertPoint, but ThenBy[Descending] doesn't
case "OrderBy": // could possibly check MemberInfo
members.Insert(insertPoint, new QueryOrderItem(mce.Arguments[1], true));
insertPoint = members.Count; // swaps order to enforce stable sort
break;
case "OrderByDescending":
members.Insert(insertPoint, new QueryOrderItem(mce.Arguments[1], false));
insertPoint = members.Count;
break;
case "ThenBy":
members.Insert(insertPoint, new QueryOrderItem(mce.Arguments[1], true));
break;
case "ThenByDescending":
members.Insert(insertPoint, new QueryOrderItem(mce.Arguments[1], false));
break;
}
}
if (mce.Arguments.Count > 0)
{ // chained on arg0
GetQueryOrder(mce.Arguments[0], members, insertPoint);
}
break;
}
}
static void Main()
{
var data = new[] {
new Test { A = 1, B = "abc", C = DateTime.Now, D = 12.3F},
new Test { A = 2, B = "abc", C = DateTime.Today, D = 12.3F},
new Test { A = 1, B = "def", C = DateTime.Today, D = 10.1F}
}.AsQueryable();
var ordered = (from item in data
orderby item.D descending
orderby item.C
orderby item.A descending, item.B
select item).Take(20);
// note: under the "stable sort" rules, this should actually be sorted
// as {-A, B, C, -D}, since the last order by {-A,B} preserves (in the case of
// a match) the preceding sort {C}, which in turn preserves (for matches) {D}
var members = GetQueryOrder(ordered.Expression);
foreach (var item in members)
{
Console.WriteLine(item.ToString());
}
// used to investigate the tree
TypeDescriptor.AddAttributes(typeof(Expression), new[] {
new TypeConverterAttribute(typeof(ExpandableObjectConverter)) });
Application.Run(new Form
{
Controls = {
new PropertyGrid { Dock = DockStyle.Fill, SelectedObject = ordered.Expression }
}
});
}
}

How to do a "where in values" in LINQ-to-Entities 3.5

Does anybody know how to apply a "where in values" type condition using LINQ-to-Entities? I've tried the following but it doesn't work:
var values = new[] { "String1", "String2" }; // some string values
var foo = model.entitySet.Where(e => values.Contains(e.Name));
I believe this works in LINQ-to-SQL though? Any thoughts?
Update: found out how to do this. And EF will generate the appropriate SQL on the database. I'm not sure if this is for EF4 only but I got the tip from Entity Framework 4.0 Recipes
var listOfIds=GetAListOfIds();
var context=CreateEntityFrameworkObjectContext();
var results = from item in context.Items
where listOfIds.Contains(item.Category.Id)
select item;
//results contains the items with matching category Ids
This query generates the correct in clause on the server side. I haven't tested it with EF 3.5 but it does work with EF4.
NB: The values passed into the in clause are NOT parameters so make sure you validate your inputs.
It is somewhat of a shame that Contains is not supported in Linq to Entities.
IN and JOIN are not the same operator (Filtering by IN never changes the cardinality of the query).
Contains is not supported in EF at this time.
FYI:
If you are using ESql you are able to use in operation.
I don't have VS 2008 With me but code should be something like following:
var ids = "12, 34, 35";
using (context = new Entites())
{
var selectedProducts = context.CreateQuery<Products>(
String.Format("select value p from [Entities].Products as p
where p.productId in {{{0}}}", ids)).ToList();
...
}
For the cases when you want to use expressions when querying your data, you can use the following extension method (adapted after http://social.msdn.microsoft.com/forums/en-US/adodotnetentityframework/thread/095745fe-dcf0-4142-b684-b7e4a1ab59f0/):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Data.Objects;
namespace Sample {
public static class Extensions {
public static IQueryable<T> ExtWhereIn<T, TValue>(this ObjectQuery<T> query,
Expression<Func<T, TValue>> valueSelector,
IEnumerable<TValue> values) {
return query.Where(BuildContainsExpression<T, TValue>(valueSelector, values));
}
public static IQueryable<T> ExtWhereIn<T, TValue>(this IQueryable<T> query,
Expression<Func<T, TValue>> valueSelector,
IEnumerable<TValue> values) {
return query.Where(BuildContainsExpression<T, TValue>(valueSelector, values));
}
private static Expression<Func<TElement, bool>> BuildContainsExpression<TElement, TValue>(
Expression<Func<TElement, TValue>> valueSelector, IEnumerable<TValue> values) {
if (null == valueSelector) { throw new ArgumentNullException("valueSelector"); }
if (null == values) { throw new ArgumentNullException("values"); }
ParameterExpression p = valueSelector.Parameters.Single();
// p => valueSelector(p) == values[0] || valueSelector(p) == ...
if (!values.Any()) {
return e => false;
}
var equals = values.Select(value => (Expression)Expression.Equal(valueSelector.Body, Expression.Constant(value, typeof(TValue))));
var body = equals.Aggregate<Expression>((accumulate, equal) => Expression.Or(accumulate, equal));
return Expression.Lambda<Func<TElement, bool>>(body, p);
}
}
class Program {
static void Main(string[] args) {
List<int> fullList = new List<int>();
for (int i = 0; i < 20; i++) {
fullList.Add(i);
}
List<int> filter = new List<int>();
filter.Add(2);
filter.Add(5);
filter.Add(10);
List<int> results = fullList.AsQueryable().ExtWhereIn<int, int>(item => item, filter).ToList();
foreach (int result in results) {
Console.WriteLine(result);
}
}
}
}
Using the extensions is really easy (as you can see in the sample). To use it on a database object, assuming you are filtering a table called "Product" by more than one id, you could do something like that:
class Product {
public int Id { get; set; }
/// ... other properties
}
List<Product> GetProducts(List<int> productIds) {
using (MyEntities context = new MyEntities()) {
return context.Products.ExtWhereIn<Product, int>(product => product.Id, productIds).ToList();
}
}
Using the where method doesn't alway work
var results = from p in db.Products
where p.Name == nameTextBox.Text
select p;
Yes it does translate to SQL, it generates a standard IN statement like this:
SELECT [t0].[col1]
FROM [table] [t0]
WHERE [col1] IN ( 'Value 1', 'Value 2')

Resources