LINQ and optional parameters - c#

I'm designing a web service to be consumed by an MVC app (pretty simple stuff), but I need a single method in the web service that takes up to four optional parameters (i.e. catId, brandId, lowestPrice and highestPrice).
How do I write the Linq query so that it essentially executes
databaseObject.Products.Where(p=> (p.Category == ANY if catId==null, else catId))
I hope that makes sense.

The parameters of the method could accept null values and the Where restriction could be evaluated for each not null parameter:
IQueryable<Product> q = databaseObject.Products;
if (catId != null)
{
q = q.Where(p => p.Category == catId);
}
if (brandId != null)
{
q = q.Where(p => p.Brand == brandId);
}
// etc. the other parameters
var result = q.ToList();

If this is Linq To SQL:
databaseObject.Products.Where(p=> (catId == null || p.Category == catId) );
Linq To SQL, efficiently writes the SQL sent to backend without a where clause if CatId is null. You can have multiple of these constructs, and only those with a nonNull value is included in the where construct.

databaseObject.Products.Where(p=> ((catId==null) || (p.Category == catId)))
For your other 3 optional parameters, you can AND them in, doing your entire search in one linq statement.

Something along the lines of the following should do the trick:
IEnumerable<Product> GetProducts(int? categoryId)
{
var result = databaseObject.Products.Where(product => ProductCategoryPredicate(product, categoryId)
}
/// <summary>
/// Returns true if the passed in categoryId is NULL
/// OR if it's not null, if the passed in categoryId matches that
/// of the passed in product
///
/// This method will get called once for each product returned from
/// databaseObject.Products</summary>
bool ProductCategoryPredicate(Product product, int? categoryId)
{
if (categoryId.HasValue)
{
return product.Category == categoryId.Value;
}
else
{
return true;
}
}
This could/can be simplified into a single line LINQ statement (see below) but I've written it long-hand above for clarity:
IEnumerable<Product> GetProducts(int? categoryId)
{
var result = databaseObject.Products.Where(product => !categoryId.HasValue || product.Category == categoryId.Value);
}

Related

Linq with null parameters not working

I don't understand why this query is not working, can you please help me?
public static IEnumerable<SelectListItem> MyList(int? id, string name="")
{
var list =db.Entity
.Where(p=>
(name==null? p.Name !=null : p.Name==name) &&
(id.hasValue || p.Id==id)
.Select(n=>new SelectListItem()
{
Text=n.Name,
Value=n.Id.ToString()
}).ToList();
return list;
}
I want to have the full list when both parameters are null!! but I get an empty list when both parameters are null.
The snippet code is from a big method which contain several query like this one.
If I understand you correctly you do not want to perform filtering when the value is null. Then you should write:
.Where(p=>
(name == null || p.Name == name) &&
(id == null || p.Id == id)
And you should change the signature of the function to set the default value for parameter name to null rather than empty string.
public static IEnumerable<SelectListItem> MyList(int? id, string name = null)
Per the comments, there are two problems:
Firstly, your condition for id isn't quite right. Secondly, your default argument for name is an empty string, not null.
However, there is room for more improvements: by embedding your name == null (and same for id) in the query, your query will be constructed in a way that translates the null check to SQL. That's good if there's a chance of name changing its value after the query has constructed, but there's no chance of that happening here. You can instead dynamically construct your query:
I commented out the p.Name != null check. If your names are possibly null, then according to the question, you don't want them filtered out when name == null.
public static IEnumerable<SelectListItem> MyList(int? id, string name=null)
{
IQueryable<Entity> query = db.Entity;
if (id != null)
query = query.Where(p => p.Id == id);
if (name != null)
query = query.Where(p => p.Name == name);
//else
// query = query.Where(p => p.Name != null);
return query.Select(...).ToList();
}

Construct expression for where through lambdas

The situation
I have a method that takes in a POCO. This POCO is like the following
private class SearchCriteria
{
public string name get;set;
public string age get;set;
public string talent get;set;
..
....
}
The method basically has a query to the db , that uses the above criteria.
public void query(SearchCriteria crit)
{
if(crit.name!=null && crit.age!=null && crit.talent !=null)
{
dbContext.Students.Where(c=>c.name ==crit.name && c.age==crit.age...)
}
else if(crit.name !=null && crit.age!=null)
{
}
else if(....
{
}
As you can see there is a definite problem above , where in, in case of large number of criteria, I will have to write a lot of if-elses to drop out specific arguments from the where clause .
The possible solution ?
I am actually new to the lambda expressions world but I believe we must be having a facility which would allow us to do something like below.
dbContext.Students.Where(processCriteria(searchCriteriaPOCO)).
Can you folks lead me to the proper direction ?. Thanks
Get a queryable and then keep adding where clauses to it. That way you only need to test each possible criteria the once and also only generate the number of where clauses that are absolutely needed.
IQueryable<Student> q = dbContext.Students.AsQueryable();
if (crit.name != null)
q = q.Where(c => c.name == crit.name);
if (crit.age != null)
q = q.Where(c => c.age== crit.age);
Let me start by saying that this answer uses the same basic idea as #PhilWright's answer. It just wraps it up in an extension method that applies this pattern for you, and allows you to have a syntax that reads nice.
public static class SearchExtensions
{
public static IQueryable<Student> Query(this SearchCriteria criteria, IQueryable<Student> studentQuery)
{
return studentQuery
.Match(criteria.name, (student) => student.name == criteria.name)
.Match(criteria.age, (student) => student.age == criteria.age)
.Match(criteria.talent, (student) => student.talent == criteria.talent);
// add expressions for other fields if needed.
}
private static IQueryable<Student> Match<T>(
this IQueryable<Student> studentQuery,
T criterionValue,
Expression<Func<Student, bool>> whereClause) where T : class
{
// only use the expression if the criterion value is non-null.
return criterionValue == null ? studentQuery : studentQuery.Where(whereClause);
}
}
You can then use it in your code like this:
var criteria = new SearchCriteria() {
name = "Alex",
talent = "Nosepicking"
};
var results = criteria.Query(dbContext.Students);
Maybe I'm missing something, as the code example is not the clearest I've seen, but for your specific example, I would think the following should be fine:
dbContext.Students.Where(c => (crit.name == null || crit.name == c.name) &&
(crit.age == null || crit.age == c.age) &&
(crit.talent == null || crit.talent == c.talent));
No need to chain a bunch of if statements.
For more complicated scenarios, you might prefer something like PredicateBuilder
You can use a pattern like this:
dbContext.Students.Where(c=>(crit.name == null || c.name ==crit.name) && ...)
A search criterion which is null will give a subexpression which is always true.

Reusing existing linq expressions in a compiled query

Consider the following code which provides two methods: One to return an IQueryable, and one which leverages a compiled query to very efficient return the location matching a specific ID:
public IQueryable<Location> GetAllLocations()
{
return from location in Context.Location
where location.DeletedDate == null
&& location.Field1 = false
&& location.Field2 = true
&& location.Field3 > 5
select new LocationDto
{
Id = location.Id,
Name = location.Name
}
}
private static Func<MyDataContext, int, Location> _getByIdCompiled;
public Location GetLocationById(int locationId)
{
if (_getByIdCompiled == null) // precompile the query if needed
{
_getByIdCompiled = CompiledQuery.Compile<MyDataContext, int, Location>((context, id) =>
(from location in Context.Location
where location.DeletedDate == null
&& location.Field1 = false
&& location.Field2 = true
&& location.Field3 > 5
&& location.Id == id
select new LocationDto {
Id = location.Id,
Name = location.Name
})).First());
}
// Context is a public property on the repository all of this lives in
return _getByIdCompiled(Context, locationId);
}
This is a pretty big simplification of the actual code, but I think it gets the idea accross, and it works fine. The next thing I want to do is refactor the code, so that the common bit of the expression can be reused, since it will be used in many other types of compiled queries. In other words, this expression:
from location in Context.Location
where location.DeletedDate == null
&& location.Field1 = false
&& location.Field2 = true
&& location.Field3 > 5
select new LocationDto
{
Id = location.Id,
Name = location.Name
};
How can I somehow capture this in a variable or function and reuse it in multiple compiled queries? My attempts so far have led to errors complaining about things not being translatable to SQL, Member access not allowed, etc.
Update: Another potentially better way I could have asked this question is as follows:
Consider the two compiled queries below:
_getByIdCompiled = CompiledQuery.Compile<MyDataContext, int, LocationDto>((context, id) =>
(from location in Context.Location // here
where location.DeletedDate == null // here
&& location.Field1 = false // here
&& location.Field2 = true // here
&& location.Field3 > 5 // here
&& location.Id == id
select new LocationDto { // here
Id = location.Id, // here
Name = location.Name
})).First()); // here
_getByNameCompiled = CompiledQuery.Compile<MyDataContext, int, LocationDto>((context, name) =>
(from location in Context.Location // here
where location.DeletedDate == null // here
&& location.Field1 = false // here
&& location.Field2 = true // here
&& location.Field3 > 5 // here
&& location.Name == name
select new LocationDto { // here
Id = location.Id, // here
Name = location.Name // here
})).First()); // here
All of the lines marked // here are duplicate very un-dry pieces of code. (In my code base, this actually 30+ lines of code.) How do I factor it out and make it reusable?
So, this whole thing is somewhat odd in that the Compile method needs to not only see the Expression objects passed to each query operator (Where, Select, etc.) as something it can understand, but it needs to see the whole query, including the use of all of the operators, as something it can comprehend as Expression objects. This pretty much removes more traditional query composition as an option.
This is going to get a tad messy; more so than I would really like, but I don't see a whole lot of great alternatives.
What we're going to do is create a method to construct our queries. It's going to accept a filter as a parameter, and that filter will be an Expression that represents a filter for some object.
Next we're going to define a lambda that will look almost exactly like what you would pass to Compile, but with an extra parameter. That extra parameter will be of the same type as our filter, and it will represent that actual filter. We'll use that parameter, instead of the filter, throughout the lambda. We'll then use a UseIn method to replace all instances of that third parameter in our new lambda with the filter expression that we are providing to the method.
Here is the method to construct the query:
private static Expression<Func<MyDataContext, int, IQueryable<LocationDto>>>
ConstructQuery(Expression<Func<Location, bool>> filter)
{
return filter.UseIn((MyDataContext context, int id,
Expression<Func<Location, bool>> predicate) =>
from location in context.Location.Where(predicate)
where location.DeletedDate == null
&& location.Field1 == false
&& location.Field2 == true
&& location.Field3 > 5
&& location.Id == id
select new LocationDto
{
Id = location.Id,
Name = location.Name
});
}
Here is the UseIn method:
public static Expression<Func<T3, T4, T5>>
UseIn<T1, T2, T3, T4, T5>(
this Expression<Func<T1, T2>> first,
Expression<Func<T3, T4, Expression<Func<T1, T2>>, T5>> second)
{
return Expression.Lambda<Func<T3, T4, T5>>(
second.Body.Replace(second.Parameters[2], first),
second.Parameters[0],
second.Parameters[1]);
}
(The typing is a mess here, and I can't figure out how to give the generic types meaningful names.)
The following method is used to replace all instances of one expression with another:
public static Expression Replace(this Expression expression,
Expression searchEx, Expression replaceEx)
{
return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}
internal class ReplaceVisitor : ExpressionVisitor
{
private readonly Expression from, to;
public ReplaceVisitor(Expression from, Expression to)
{
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node)
{
return node == from ? to : base.Visit(node);
}
}
Now that we've gotten through this gory mess, comes the easy part. ConstructQuery should be able to be modified at this point to represent your real query, without much difficulty.
To call this method we simply need to provide whatever filter we want applied to this alteration of the query, such as:
var constructedQuery = ConstructQuery(location => location.Id == locationId);
Linq statements have to end in either select or group clause so you can't cut out part of the query and store it elsewhere, but if you will always be filtering by the same four conditions, you can use the lambda syntax instead, and then add any additional where clauses in new queries.
And as pointed out by #Servy, you need to call First() or FirstOrDefault() to get a single element from the query result.
IQueryable<Location> GetAllLocations()
{
return Context.Location.Where( x => x.DeletedDate == null
&& x.Field1 == false
&& x.Field2 == true
&& x.Field3 > 5
).Select( x => x );
}
Location GetLocationById( int id )
{
return ( from x in GetAllLocations()
where x.Id == id
select x ).FirstOrDefault();
}
//or with lambda syntax
Location GetLocationById( int id )
{
return GetAllLocations()
.Where( x => x.Id == id )
.Select( x => x )
.FirstOrDefault();
}

Linq return string array

/// <summary>
/// Returns list of popular searches
/// </summary>
public static string[] getPopularSearches(int SectionID, int MaxToFetch)
{
using (MainContext db = new MainContext())
{
return (from c in db.tblSearches where c.SectionID == SectionID && c.Featured select new[] { c.Term });
}
}
I looked at other questions but they seem to be slightly different, I get the error:
Cannot implicitly convert type 'System.Linq.IQueryable<string[]>' to 'string[]'
I know this is probably simple, could someone point out what's wrong here please?
Sure - you're trying to return from a method declared to return a string[], but you're returning a query - which isn't a string in itself. The simplest way of converting a query to an array is to call the ToArray extension method.
However, as you're already selecting a string array for every element in the query, that would actually return string[][]. I suspect you really want to select a single string per query element, and then convert the whole thing into an array, i.e. code like this:
public static string[] GetPopularSearches(int sectionID, int maxToFetch)
{
using (MainContext db = new MainContext())
{
var query = from c in db.tblSearches
where c.SectionID == sectionID && c.Featured
select c.Term;
return query.Take(maxToFetch)
.ToArray();
}
}
Note that:
I've renamed the method and parameters to match .NET naming conventions
I've added a call to Take in order to use the maxToFetch parameter
You are attempting to return an unmaterialized query. The query is only evaluated when it is enumerated. Luckily for you, the ToArray method take the pain out of enumerating and storing. Simply adding it to the end of your query should fix everything.
return (
from c in db.tblSearches
where c.SectionID == SectionID && c.Featured
select new[] { c.Term }
).ToArray();
EDIT
Looking in more detail, perhaps:
return (
from c in db.tblSearches
where c.SectionID == SectionID && c.Featured
select new[] { c.Term }
).SelectMany(x => x).ToArray();
to flatten the results of your query, or even (less redundantly):
return (
from c in db.tblSearches
where c.SectionID == SectionID && c.Featured
select c.Term
).ToArray();
Add .ToArray() at the end of the return statement.

Custom function in Entity Framework query sometimes translates properly, sometimes doesn't

I have this function:
public static IQueryable<Article> WhereArticleIsLive(this IQueryable<Article> q)
{
return q.Where(x =>
x != null
&& DateTime.UtcNow >= x.PublishTime
&& x.IsPublished
&& !x.IsDeleted);
}
And it works just fine in this query:
from a in Articles.WhereArticleIsLive()
where a.Id == 5
select new { a.Title }
But it doesn't work in this only slightly more complex query:
from s in Series
from a in Articles.WhereArticleIsLive()
where s.Id == a.SeriesId
select new { s, a }
I get this error message:
NotSupportedException: LINQ to Entities does not recognize the method 'System.Linq.IQueryable1[TheFraser.Data.Articles.Article] WhereArticleIsLive(System.Linq.IQueryable1[TheFraser.Data.Articles.Article])' method, and this method cannot be translated into a store expression.
Any idea why? Is there another way to consolidate query parameters like this?
Thanks in advance.
EDIT: corrections by Craig.
I'm leaving this here, because I think it's a valuable tool: Use linqkit! But not for solving this question though :-)
Instead of returning IQueryable, use Expression to factor out predicates. E.g. you could define the following static method on Article:
public static Expression<Func<Article,bool>> IsLive()
{
return x =>
x != null
&& DateTime.UtcNow >= x.PublishTime
&& x.IsPublished
&& !x.IsDeleted
}
Then, ensure to store a reference to this expression when building your query, something along the lines of (not tested):
var isLive = Article.IsLive();
from s in Series
from a in Articles.Where(isLive)
where s.Id == a.SeriesId
select new { s, a }

Categories