Alternative for multiple IF/CASE statements - c#

Is there a way to avoid multiple IF/CASE statements in C#?
In my app I will end up with 8+ fields used to create a Linq query where every expression can be null or != null so it will give me 64 scenarios.
I'm not providing any code samples because I can do it using IF/CASE and simplify it as much as I can.
If You are familiar with some useful approaches to that problem I will appreciate any advice.
Code Sample (it only includes two delegates but I'll have to add more to filter data)
Repository
public virtual IEnumerable<T> Get(Expression<Func<T, bool>> filter = null, Expression<Func<T, bool>> filter1 = null)
{
IQueryable<T> query = dbSet;
if (filter != null)
{
query = query.Where(filter);
return query.ToList();
}
if (filter1 != null)
{
query = query.Where(filter1);
return query.ToList();
}
else
{
return query.ToList();
}
}
Controller
public ActionResult Index(string searchFullName, string searchExtension)
{
var extensionList = new List<string>();
var extensions = from n in unitofwork.DomainRepository.Get()
select n.Extension;
extensionList.AddRange(extensions.Distinct());
ViewBag.searchExtension = new SelectList(extensionList);
if (!String.IsNullOrEmpty(searchFullName) && !String.IsNullOrEmpty(searchExtension))
{
return View(unitofwork.DomainRepository.Get(n => n.Extension == searchExtension && n.Name.Contains(searchFullName)));
}
else if (!String.IsNullOrEmpty(searchExtension))
{
return View(unitofwork.DomainRepository.Get(n => n.Extension == searchExtension));
}
else if (String.IsNullOrEmpty(searchFullName) && String.IsNullOrEmpty(searchExtension))
{
return View(unitofwork.DomainRepository.Get());
}
else if (!String.IsNullOrEmpty(searchFullName) && String.IsNullOrEmpty(searchExtension))
{
return View(unitofwork.DomainRepository.Get(n => n.Name.Contains(searchFullName)));
}
else
{
return View(unitofwork.DomainRepository.Get());
}
}

Yes it is possible, using Linq's powerful Aggregate method (a version of the fold function from functional programming).
public virtual IEnumerable<T> FilterOnAll(params Expression<Predicate<T> filters)
{
return filters.Aggregate(dbSet, (acc, element) => acc.Where(element));
}
public virtual IEnumerable<T> FilterOnAny(params Expression<Predicate<T> filters)
{
Expression<Predicate<T>> alwaysTrue = _ => true;
var compositeFilter = filters.Aggregate(alwaysTrue, (acc, element) => acc.Or(element));
return dbSet.Where(compositeFilter);
}
You can then compose these two builders to create pretty much any logical condition you want from within your controller.
Good luck.

What you can do is an or statement in your where clause
where (variable1 == null || b.data = variable1)
and just do that for all 8 if im understanding your problem if its null it passes over it as true else it checks against the data.

Your entire if .. elseif .. else block can be replaced by:
return View(unitofwork.DomainRepository.Get(n =>
(string.IsNullOrEmpty(searchFullName) || n.Name.Contains(searchFullName))
&& (string.IsNullOrEmpty(searchExtension) || n.Extension == searchExtension)));

Related

Use WhereIf for multiple condition in c#

Hi can someone help me how best we can use whereif in LINQ, here I have a code which works fine, but I want to convert this query with WhereIf.
public async Task LoadQuery(IEnumerable<string> codes)
{
var query = _dBContext.QueryTable.Where(x => !x.InActive).AsQueryable();
if (codes!= null && codes.Any())
query = query.Where(x => codes.Contains(x.FirstCode) || query.Contains(x.SecondCode));
else
query = query.Where(x => !x.HasException.HasValue);
var data = query.ToList();
}
I have tried it with WhereIF ienumerable but not succeed. Here is the link which I followed.
https://extensionmethod.net/csharp/ienumerable-t/whereif
WhereIf isn't really suitable for your case, for 2 reasons:
You're calling two different functions on your if-else, while WhereIf is built to accept a single function (predicate) to be executed if some condition is satisfied.
WhereIf is an extension method for IEnumerable<TSource>, while your'e trying to use it as an extension method for IQueryable<TSource>.
If you insist, you'd have to define an extension method for IQueryable<TSource>, and in doing so, just define it as WhereIfElse:
public static class ExtensionMethods
{
public static IQueryable<TSource> WhereIfElse<TSource>(this IQueryable<TSource> source, bool condition, Func<TSource, bool> predicateIf, Func<TSource, bool> predicateElse)
{
if (condition)
return source.Where(predicateIf).AsQueryable();
else
return source.Where(predicateElse).AsQueryable();
}
}
So, let's say that query's type is IQueryable<Item> (replace Item with your actual type):
public async Task<List<Item>> LoadQuery(IEnumerable<string> codes)
{
var query = _dBContext.QueryTable.Where(x => !x.InActive).AsQueryable();
query = query.WhereIfElse(
// condition
codes != null && codes.Any(),
// predicateIf
(Item x) => codes.Contains(x.FirstCode) || codes.Contains(x.SecondCode),
// predicateElse
(Item x) => !x.HasException.HasValue
);
var data = query.ToList();
return data;
}
P.S. note I changed your return value, though there still isn't an await.
bool condition = codes!= null && codes.Any();
var data = _dBContext.QueryTable
.WhereIf(condition, a=> codes.Contains(a.FirstCode) || codes.Contains(a.SecondCode))
.WhereIf(!condition, a=> !a.HasException.HasValue && !a.InActive).ToList();

Use a List of sort expression to sort a queryable

I am building a repository and would like to be able to pass a list of sorts to a function to receive the entities back in the correct order and possibly paged.
I found out that linq will correctly sort if you use reverse the list of sorts and use Orderby for each sort expression..
foreach (var s in sorts.Reverse())
{
query = query.OrderBy(s);
}
However while testing the addition of the sort direction I found that it only takes the first sort direction and seems to apply that direction on each sort.
private void applySorts(ref IQueryable<TEntity> query, Dictionary<Expression<Func<TEntity, dynamic>>, string> sorts)
{
if (sorts != null)
{
foreach (var s in sorts.Reverse())
{
Expression<Func<TEntity, dynamic>> expr = s.Key;
string dir = s.Value;
if (dir == "d")
{
query = query.OrderByDescending(expr);
}
else
{
query = query.OrderBy(expr);
}
}
}
}
I originally tried to use OrderBy on the first sort and then switch to ThenBy, but this is not possible because ThenBy requires a type of IOrderedQueryable.
I would like to note the use of dictionary may not be the best, if you have any better ideas please share. However, I just wanted to get it running and see how things go.
I am using C#, Linq, and Entity Framework.
Thanks in advance for your time.
Update: Unfortunately I have found that this does not support sorting numbers. Error(Unable to cast the type 'System.Int32' to type 'System.Object'.)
Grammatically, all you need is a temp variable of type IOrderedQueryable.
I would try something like:
private void applySorts(ref IQueryable<TEntity> query, Dictionary<Expression<Func<TEntity, dynamic>>, string> sorts)
{
if (sorts != null)
{
IOrderedQueryable<TEntity> tempQuery = null;
bool isFirst = true;
foreach (var s in sorts.Reverse())
{
Expression<Func<TEntity, dynamic>> expr = s.Key;
string dir = s.Value;
if (first)
{
first = false;
if (dir == "d")
{
tempQuery = query.OrderByDescending(expr);
}
else
{
tempQuery = query.OrderBy(expr);
}
}
else
{
if (dir == "d")
{
tempQuery = tempQuery.ThenByDescending(expr);
}
else
{
tempQuery = tempQuery.ThenBy(expr);
}
}
}
query = tempQuery;
}
}
Edit:
The key to the above solution is that an IOrderedQueryable is an IQueryable.
I haven't tried it myself. However, I would emphasis that this is only a grammatical solution.
You can use ThenBy() method to chain your filters like this:
bool first = true;
foreach(var s in sorts)
{
query = first ? query.OrderBy(s) : query.ThenBy(s);
}
// It will became like query.OrderBy(a => a.A).ThenBy(a => a.B).ThenBy(a => a.C)...
You can also use sorting by multiple columns in a LINQ query if you know the properties on which you will sort:
var result = (from row in list
orderby row.A, row.B, row.C
select row).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.

Speed up EF5 Code First query

Following on from previous questions using EF5 Code First, SQL Server 2008 R2 db, Generic Repository and Unit Of Work patterns
I have the following function
public static void MoveGraphicPosition(int queueId, int graphicId, bool moveUp)
{
using (var unitOfWork = new GraphicUnitOfWork(ConnGraphics, false))
{
var sourceGraphic = unitOfWork.GraphicRepository.FindSingle(g => g.Id == graphicId);
if (sourceGraphic == null) return;
var startPosition = sourceGraphic.QueuePosition;
Graphic targetGraphic;
if (moveUp)
{
targetGraphic =
unitOfWork.PlayoutQueueRepository.FindSingle(q => q.Id == queueId, q => q.Graphics)
.Graphics.Where(g => g.QueuePosition < startPosition)
.OrderByDescending(g => g.QueuePosition)
.Take(1).FirstOrDefault();
}
else
{
targetGraphic =
unitOfWork.PlayoutQueueRepository.FindSingle(q=> q.Id == queueId, q => q.Graphics)
.Graphics.Where(g => g.QueuePosition > startPosition)
.OrderBy(g => g.QueuePosition)
.Take(1).FirstOrDefault();
}
// Swop the positions
if (targetGraphic == null) return;
sourceGraphic.QueuePosition = targetGraphic.QueuePosition;
targetGraphic.QueuePosition = startPosition;
unitOfWork.GraphicRepository.Update(sourceGraphic);
unitOfWork.GraphicRepository.Update(targetGraphic);
// Save to database
unitOfWork.Save();
}
}
Running this method via a Web Api call it takes about 2 seconds to run, I am bit puzzled as to why it takes this long, was expecting less than a second, is there any advice as to speed this up.
All we are trying to do is change queue positions of two graphic objects - swop queue positions around - our current one with the next one in the queue based on position.
Not sure whether this is EF5 or my LINQ query being inefficient.
FindSingle on the repository looks like this
public T FindSingle(Expression<Func<T, bool>> predicate = null, params Expression<Func<T, object>>[] includes)
{
var set = FindIncluding(includes);
return (predicate == null) ? set.FirstOrDefault() : set.FirstOrDefault(predicate);
}
public IQueryable<T> FindIncluding(params Expression<Func<T, object>>[] includeProperties)
{
IQueryable<T> set = _context.GetEntitySet<T>();
if (includeProperties != null)
{
foreach (var include in includeProperties)
{
set = set.Include(include);
}
}
return set.AsQueryable();
}
I have a feeling might have to change this to take two objects in and then just do the update but this may be masking a problem which we will have to address at a later date as this should be straightforward.
It was the notification service we have setup on the save call (so other services/apps know what has been changed on the database) - this was causing an exception which increased the delay, once we put in the correct IP, the delay was around 0.7ms which is what I was expecting

Linq to Sql any keyword search query

I have a case in my application where the user can search for a list of terms. The search needs to make three passes in the following order:
One for an exact match of what they entered. Done, easy.
One where all the words (individually) match. Done, also easy.
One where any of the words match...how?
Essentially, how do I, in Linq to Sql, tell it to do this:
select * from stuff s where s.Title like '%blah%' || s.Title like '%woo&' || s.Title like '%fghwgads%' || s.Title like...
And so on?
This might be a tough one... I think you'd have to write your own operator.
(Update: Yep, I tested it, it works.)
public static class QueryExtensions
{
public static IQueryable<TEntity> LikeAny<TEntity>(
this IQueryable<TEntity> query,
Expression<Func<TEntity, string>> selector,
IEnumerable<string> values)
{
if (selector == null)
{
throw new ArgumentNullException("selector");
}
if (values == null)
{
throw new ArgumentNullException("values");
}
if (!values.Any())
{
return query;
}
var p = selector.Parameters.Single();
var conditions = values.Select(v =>
(Expression)Expression.Call(typeof(SqlMethods), "Like", null,
selector.Body, Expression.Constant("%" + v + "%")));
var body = conditions.Aggregate((acc, c) => Expression.Or(acc, c));
return query.Where(Expression.Lambda<Func<TEntity, bool>>(body, p));
}
}
Then you could call this with:
string[] terms = new string[] { "blah", "woo", "fghwgads" };
var results = stuff.LikeAny(s => s.Title, terms);
P.S. You'll need to add the System.Linq.Expressions and System.Data.Linq.SqlClient namespaces to your namespaces for the QueryExtensions class.

Categories