Conditional Linq - c#

I have two conditions, they may be both set or just one of them. If both set to a value a linq where statement shall be triggered having AND as boolean operator else the linq where statement shall include only the filter based on the set condition.
For example:
both conditions set: linq.Where(condition1 && condition2)...
only condition1 set: linq.Where(condition1)..
only condition2 set: linq.Where(condition2)..
This is not allowed: linq.Where(condition1 || condition2) because the filtering shall be very accurate
Now my problem is how do I write ONE linq statement that considers all those requirements? Thanks in advance
EDIT:
Predicates might look like this: x => x > 10 or x => x == "Hello" or x => x + 2 == 3.. any expression that fits inside where statement.
I get the conditions/predicates, i do not create them myself
With predicates i mean those predicates in math and not the class in c#.
there is not much code to post since i do not know how to do this in ONE linq statement
The result has to be ONE linq statement handling all this.
I am new I do not know how to write this that why i was hoping you could help me out.
If you dont like it dont downvote it just pass on, somebody will help me out.
EDIT2:
I mean this. Not more, not less:
method<T> filterList(ienumerable<T> linq, Predicate<T> p1, Predicate<T> p2)
{
return linq.Where(only p1 or only p2 or p1 && p2)
}

This is the beauty of monads - you don't have to do everything in one step.
// bool? flag1
// bool? flag2
// IQueryable<something> collection
if(flag1.HasValue)
collection = collection.Where(x => x.Flag1 == flag1);
if(flag2.HasValue)
collection = collection.Where(x => x.Flag2 == flag2);
//...
Edit: if you want to do it in a single LINQ statement you'll need to do some trickery by invoking predicates:
public IEnumerable<T> FilterList<T>(IEnumerable<T> collection, Predicate<T> p1, Predicate<T> p2)
{
return collection.Where(x =>
(p1 == null || p1.Invoke(x)) &&
(p2 == null || p2.Invoke(x))
);
}

you don't say what to do if both are unset, I assume in this case no filtering at all
I assume by 'has no value' you mean for example p1 is null.
I'm not 100% sure what you mean by ONE linq statement but I would assume at least one of the below would meet your needs.
in all cases I'm saying basically [replace a null predicate with an always true predicate and and the predicates]
Solution 1
return linq.Where(x => p1 != null ? p1(x) : true).Where(x => p2 != null ? p2(x) : true);
Solution 2
var p1a = p1 ?? (x => true);
var p2a = p2 ?? (x => true);
return linq.Where(x => p1a(x) && p2a(x));
Solution 3
return linq.Where(x => (p1 ?? (y=>true))(x) && (p2 ?? (z=>true))(x));

If you use the third party PredicateBuilder, you can iteratively build or filters as well as and.
example
var predicate = PredicateBuilder.False<Product>();
foreach (string keyword in keywords)
{
string temp = keyword;
predicate = predicate.Or (p => p.Description.Contains (temp));
}
return dataContext.Products.Where (predicate);

Related

Ternary operator add or not where conditions in linq

I have this query:
if(!string.IsNullOrEmpty(code) && !string.IsNullOrEmpty(name))
{
return this.context.Table.Where(
x => EF.Functions.Contains(x.Code, $"\"{code}\"")
&& EF.Functions.Contains(x.Name, $"\"{name}\""));
}
else if(string.IsNullOrEmpty(code) && !string.IsNullOrEmpty(name))
{
return this.context.Table.Where(x => EF.Functions.Contains(x.Name, $"\"{name}\""));
}
else
{
return this.context.Table.Where(x => EF.Functions.Contains(x.Code, $"\"{code}\""));
}
I tried to do it again using the ternary operators then check if the string is not null or empty and if so add the where clause or not
I tried such a thing but obviously expects that after the "?" there is the alternative of ":"
return this.context.Table.Where(
x => !string.IsNullOrEmpty(code)
? EF.Functions.Contains(x.Code, $"\"{code}\"")
&& !string.IsNullOrEmpty(name)
? EF.Functions.Contains(x.Name, $"\"{name}\""));
Since unlike the example in my case I have to check 8 different input parameters that if not passed must not be used in the where for the controls, I wanted to avoid filling the code of many if cases and rewriting the query n times for the different combinations, is there a way or should I resign myself?
You can just return true for any you don't want to check for like below
!string.IsNullOrEmpty(code) ? EF.Functions.Contains(x.Code, $"\"{code}\"") : true;
This means if the string is null or empty then it will return true which should provide the behaviour you're expecting.
Do not use ternary operators for combining query. EF will create non optimal SQL.
Usually such task is done in the following way:
var query = this.context.Table.AsQueryable();
if (!string.IsNullOrEmpty(code))
{
query = query.Where(
x => EF.Functions.Contains(x.Code, $"\"{code}\""));
}
if (!string.IsNullOrEmpty(name))
{
query = query.Where(
x => EF.Functions.Contains(x.Name, $"\"{name}\""));
}
var result = query.ToList();

How do I Take everything in a collection using Linq?

I've got a method which can accept an optional int? value as a number of items to Take from a collection. I want to return all items if a null value is passed. Right now I have to duplicate my query to accomplish this
if(take == null)
{
x = db.WalkingDeadEps.Where(x => x.BicyclesCouldHaveSavedLives == true).ToList()
}
else
{
x = db.WalkingDeadEps.Where(x => x.BicyclesCouldHaveSavedLives == true).Take(take).ToList()
}
Is there a simpler way? Something like this?
.Take(take != null ? take : "all")
with Linq you have the option to store your query in variables. it will not be executed until you call ToList or equivalent methods on it.
var query = db.WalkingDeadEps.Where(x => x.BicyclesCouldHaveSavedLives == true);
x = take.HasValue ? query.Take(take.Value).ToList() : query.ToList();

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();
}

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 }

LINQ WHERE clause using if statements

I am using c#.net
I have two textboxes which if !empty need to be part of a WHERE clause within a LINQ query.
Here is my code
var result = from a in xxxx select a;
if(!string.IsNullOrEmpty(personName))
{
return result.Where(a >= a.forename.Contains(personName) || a.surname.Contains(personName)
}
else if(!string.IsNullOrEmpty(dateFrom))
{
return result.Where(a >= a.appStartDateTime >= dateFrom.Date)
}
else if(!string.IsNullOrEmpty(personName) && !string.IsNullOrEmpty(dateFrom))
{
return result.Where(a >= a.forename.Contains(personName) || a.surname.Contains(personName) && a.appStartDateTime >= dateFrom.Date);
}
I thought this would work but it doesn't like the .Where and I cant access the 'a' for example a.forename (The name 'a' does not exist in the current context)
What am I going wrong, or can this not actually be done?
Thanks in advance for any help.
Clare
Instead of this:
result.Where(a.forename.Contains(personName))
Try this:
result.Where(a => a.forename.Contains(personName))
You appear to be missing the Lambda operator (=>).
try this
var result = from a in xxxx select a
where (string.IsNullOrEmpty(personName) || a.forename.Contains(personName)
|| a.surname.Contains(personName))
&& (string.IsNullOrEmpty(dateFrom)
|| a.appStartDateTime >= DateTime.Parse(dateFrom).Date);
dateFrom appears to be a string so you have to parse it to get a date time.
This logic should work but I have not tested it. I could be wrong.
You seem to only care if the forename or surname contain personName if the personName is not null or empty. So you can rewrite it to return things if the personName is null or empty or the fore or sur name contains person name. Since the || operator is short circuiting it will not check Contains if the personName is null or empty.
You can also combine the predicates and make the logic shorter and easier to read:
var result = from a in xxxx select a;
if (!string.IsNullOrEmpty(personName))
result = result.Where(a => a.forename.Contains(personName) || a.surname.Contains(personName)
if (!string.IsNullOrEmpty(dateFrom))
result = result.Where(a >= a.appStartDateTime >= dateFrom.Date)
return result;
This method of combining predicates works well with 'AND' conditions. If you need to 'OR' conditions, it's a little bit more involved. Thankfully, Joe Albahari has created PredicateBuilder (as part of LINQKit) that helps with this greatly.
Hope this helps.
..or with just one exit point:
var result = from a in xxxx select a;
Func<string, bool> func = null;
if(!string.IsNullOrEmpty(personName))
{
func = (a) => {a.forename.Contains(personName) || a.surname.Contains(personName)};
}
else if(!string.IsNullOrEmpty(dateFrom))
{
func = (a) => {a.appStartDateTime >= dateFrom.Date};
}
else if(!string.IsNullOrEmpty(personName) && !string.IsNullOrEmpty(dateFrom))
{
func = (a) => {a.forename.Contains(personName) || a.surname.Contains(personName) && a.appStartDateTime >= dateFrom.Date;};
}
return result.Where(func);

Categories