Building a query (LINQ) with a sub-query - linq

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 :

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

"LINQ to Entities does not recognize the method 'Boolean ***, and this method cannot be translated into a store expression."

I am coding a MVC 5 internet application, and am getting the following error:
base = {"LINQ to Entities does not recognize the method 'Boolean IsAccountSetupForSendingFreeTrialSubscriptionExpirationEmail(CanFindLocation.Models.Account)' method, and this method cannot be translated into a store expression."}
Here are my methods:
public IEnumerable<Account> GetFreeTrialAccountsForSendDailyExpirationEmails(int minimumDaysLeftInSubscriptionForEmail)
{
IEnumerable<Account> freeTrialAccounts = genericMultipleRepository.accounts.Value.SearchFor(a => IsAccountSetupForSendingFreeTrialSubscriptionExpirationEmail(a) && ShouldDailyExpirationEmailBeSentForFreeTrialAccount(a, minimumDaysLeftInSubscriptionForEmail));
return freeTrialAccounts;
}
public bool IsAccountSetupForSendingFreeTrialSubscriptionExpirationEmail(Account account)
{
if (account.isFreeTrial && account.sendSubscriptionExpirationEmail)
{
return true;
}
else
{
return false;
}
}
public bool ShouldDailyExpirationEmailBeSentForFreeTrialAccount(Account account, int minimumDaysLeftInSubscriptionForEmail)
{
if (IsExpiresDateTimeLessThanMinimumDaysLeftInFreeTrialSubscription(account, minimumDaysLeftInSubscriptionForEmail) && !HasSubscriptionEmailBeenSentForCurrentFreeTrialEndDateTime(account))
{
return true;
}
else
{
return false;
}
}
public bool IsExpiresDateTimeLessThanMinimumDaysLeftInFreeTrialSubscription(Account account, int minimumDaysLeftInSubscriptionForEmail)
{
TimeSpan expires = account.freeTrialEndDate - DateTime.UtcNow;
return expires.Days < minimumDaysLeftInSubscriptionForEmail;
}
public bool HasSubscriptionEmailBeenSentForCurrentFreeTrialEndDateTime(Account account)
{
return account.subscriptionEndDateForExpirationEmail == account.freeTrialEndDate;
}
My Account object has the following fields:
public bool isFreeTrial { get; set; }
public bool sendSubscriptionExpirationEmail { get; set; }
public DateTime subscriptionEndDateForExpirationEmail { get; set; }
public DateTime freeTrialEndDate { get; set; }
I have done some research, and I gather that the code that I have written cannot be converted into a LINQ expression.
What do I need to do to convert my code into a valid form so that the LINQ to Entities does not perform the error?
Thanks in advance.
You cannot refactor LinqToXXX queries into methods.
For example.
IQueryable<Foo> foos = ...;
IQueryable<Foo> results = from f in foos
where f.IsBar
select f;
Results in VASTLY different IL than
IQueryable<Foo> foos = ...;
IQueryable<Foo> results = from f in foos
where FooIsBar(f)
select f;
public bool FooIsBar(Foo foo)
{
return foo.IsBar;
}
The latter will try to pass off FooIsBar to EntityFramework to "convert". Since EntityFramework does not have access to the source code at this point, it can't do the conversion of compiled IL code into SQL.
The alternative is the following...
IQueryable<Foo> foos = ...;
IQueryable<Foo> results = from f in foos.Where(FooIsBar)
select f;
public Expression<Func<Foo, bool>> FooIsBar
{
get { return foo => foo.IsBar; }
}

Linq expression over a list of derived types

I am trying to write a Linq expression that checks against property in a derived class, but the list is made up of members from a base class. Example code below. The 2nd line of the Process method starting with 'var list' does not compile, but I am not sure what syntax I should use to make it valid?
public class Manager
{
public void Process()
{
Base[] stuff = { new Derived() { Id = "1", Name = "me" } };
var list = stuff.Where<Derived>(d => d.Name == "me");
}
}
public class Base
{
public string Id { get; set; }
}
public class Derived : Base
{
public string Name { get; set; }
}
If you know the list has only Derived, you can use the Cast<T> method:
var list = stuff.Cast<Derived>().Where(d => d.Name == "me");
If there are only some Derived, you can use OfType<T>:
var list = stuff.OfType<Derived>().Where(d => d.Name == "me");
In that case, the non-Derived objects will be skipped.

Sorting a List of Class with LINQ

I have a List<MyClass> and I want to sort it by DateTime CreateDate attribute of MyClass.
Is that possible with LINQ ?
Thanks
To sort the existing list:
list.Sort((x,y) => x.CreateDate.CompareTo(y.CreateDate));
It is also possible to write a Sort extension method, allowing:
list.Sort(x => x.CreateDate);
for example:
public static class ListExt {
public static void Sort<TSource, TValue>(
this List<TSource> list,
Func<TSource, TValue> selector) {
if (list == null) throw new ArgumentNullException("list");
if (selector == null) throw new ArgumentNullException("selector");
var comparer = Comparer<TValue>.Default;
list.Sort((x,y) => comparer.Compare(selector(x), selector(y)));
}
}
You can enumerate it in sorted order:
IEnumerable<MyClass> result = list.OrderBy(element => element.CreateDate);
You can also use ToList() to convert to a new list and reassign to the original variable:
list = list.OrderBy(element => element.CreateDate).ToList();
This isn't quite the same as sorting the original list because if anyone still has a reference to the old list they won't see the new ordering. If you actually want to sort the original list then you need to use the List<T>.Sort method.
Here is a sample:
using System.Collections.Generic;
using System.Linq;
namespace Demo
{
public class Test
{
public void SortTest()
{
var myList = new List<Item> { new Item { Name = "Test", Id = 1, CreateDate = DateTime.Now.AddYears(-1) }, new Item { Name = "Other", Id = 1, CreateDate = DateTime.Now.AddYears(-2) } };
var result = myList.OrderBy(x => x.CreateDate);
}
}
public class Item
{
public string Name { get; set; }
public int Id { get; set; }
public DateTime CreateDate { get; set; }
}
}
Sure the other answers with .OrderBy() work, but wouldn't you rather make your source item inherit from IComparable and just call .Sort()?
class T {
public DateTime CreatedDate { get; set; }
}
to use:
List<T> ts = new List<T>();
ts.Add(new T { CreatedDate = DateTime.Now });
ts.Add(new T { CreatedDate = DateTime.Now });
ts.Sort((x,y) => DateTime.Compare(x.CreatedDate, y.CreatedDate));

Resources