Reputation: 24572
I have this code here. I think it's not very efficient and I would like to know if there is a way that I make it so some of the .Where clauses are added at the start or the end rather than repeated three times. I would appreciate if anyone can give me any advice and show how I could do this:
List<Phrase> phrases;
switch (options.PhraseTypeSelectId)
{
case 0:
phrases = await db.Phrases
.Where(w => ((w.CategoryId == options.CategorySelectId || options.CategorySelectId == null) ||
(w.ChapterId == options.ChapterSelectId || options.ChapterSelectId == null)))
.Where(w => (w.EnglishAscii >= es1 && w.EnglishAscii <= ee1) || (w.EnglishAscii >= es2 && w.EnglishAscii <= ee2))
.Where(w => (w.RomajiAscii >= rs1 && w.RomajiAscii <= re1) || (w.RomajiAscii >= rs2 && w.RomajiAscii <= ee2))
.Where(w => (options.CreatedBy == 0 || w.CreatedBy == options.CreatedBy))
.Where(w => (options.ModifiedBy == 0 || w.ModifiedBy == options.ModifiedBy))
.Where(w => (options.JLPT == 0 || w.JLPT == options.JLPT))
.AsNoTracking()
.ToListAsync();
return Ok(phrases);
case 1:
phrases = await db.Phrases
.Where(w => ((w.CategoryId == options.CategorySelectId || options.CategorySelectId == null)))
.Where(w => (w.EnglishAscii >= es1 && w.EnglishAscii <= ee1) || (w.EnglishAscii >= es2 && w.EnglishAscii <= ee2))
.Where(w => (w.RomajiAscii >= rs1 && w.RomajiAscii <= re1) || (w.RomajiAscii >= rs2 && w.RomajiAscii <= ee2))
.Where(w => (options.CreatedBy == 0 || w.CreatedBy == options.CreatedBy))
.Where(w => (options.ModifiedBy == 0 || w.ModifiedBy == options.ModifiedBy))
.Where(w => (options.JLPT == 0 || w.JLPT == options.JLPT))
.AsNoTracking()
.ToListAsync();
return Ok(phrases);
case 2:
phrases = await db.Phrases
.Where(w => ((w.ChapterId == options.ChapterSelectId || options.ChapterSelectId == null)))
.Where(w => (w.EnglishAscii >= es1 && w.EnglishAscii <= ee1) || (w.EnglishAscii >= es2 && w.EnglishAscii <= ee2))
.Where(w => (w.RomajiAscii >= rs1 && w.RomajiAscii <= re1) || (w.RomajiAscii >= rs2 && w.RomajiAscii <= ee2))
.Where(w => (options.CreatedBy == 0 || w.CreatedBy == options.CreatedBy))
.Where(w => (options.ModifiedBy == 0 || w.ModifiedBy == options.ModifiedBy))
.Where(w => (options.JLPT == 0 || w.JLPT == options.JLPT))
.AsNoTracking()
.ToListAsync();
return Ok(phrases);
}
return BadRequest();
I would like to include the switch statement as I tried without this and it became very confusing with the .Where clauses related to the PhraseTypeSelectId
Upvotes: 1
Views: 139
Reputation: 205749
If I understand correctly, the question is how to build a dynamic filter. To do that, you can start with basic IQueryable<T>
and apply parameter based conditional Where
like this:
var query = db.Phrases.AsQueryable();
bool categoryFilter = options.CategorySelectId != null && options.PhraseTypeSelectId != 2;
bool chapterFilter = options.ChapterSelectId != null && options.PhraseTypeSelectId != 1;
if (categoryFilter && chapterFilter) query = query
.Where(w => w.CategoryId == options.CategorySelectId || w.ChapterId == options.ChapterSelectId);
else if (categoryFilter) query = query
.Where(w => w.CategoryId == options.CategorySelectId);
else if (chapterFilter) query = query
.Where(w => w.ChapterId == options.ChapterSelectId);
query = query
.Where(w => (w.EnglishAscii >= es1 && w.EnglishAscii <= ee1) || (w.EnglishAscii >= es2 && w.EnglishAscii <= ee2))
.Where(w => (w.RomajiAscii >= rs1 && w.RomajiAscii <= re1) || (w.RomajiAscii >= rs2 && w.RomajiAscii <= ee2));
if (options.CreatedBy != 0) query = query
.Where(w => w.CreatedBy == options.CreatedBy);
if (options.ModifiedBy != 0)
query = query.Where(w => w.ModifiedBy == options.ModifiedBy);
if (options.JLPT != 0)
query = query.Where(w => w.JLPT == options.JLPT);
var phrases = await query
.AsNoTracking()
.ToListAsync();
return Ok(phrases);
Upvotes: 2
Reputation: 64628
This should also work (details may be wrong, just conceptional):
// common filters here:
var filtered = db.Phrases
.Where(w => (w.EnglishAscii >= es1 && w.EnglishAscii <= ee1) || (w.EnglishAscii >= es2 && w.EnglishAscii <= ee2))
.Where(w => (w.RomajiAscii >= rs1 && w.RomajiAscii <= re1) || (w.RomajiAscii >= rs2 && w.RomajiAscii <= ee2))
.Where(w => (options.CreatedBy == 0 || w.CreatedBy == options.CreatedBy))
.Where(w => (options.ModifiedBy == 0 || w.ModifiedBy == options.ModifiedBy))
.Where(w => (options.JLPT == 0 || w.JLPT == options.JLPT));
// specific filters
switch (options.PhraseTypeSelectId)
{
case 0:
filtered = filtered
.Where(w => ((w.CategoryId == options.CategorySelectId || options.CategorySelectId == null) ||
(w.ChapterId == options.ChapterSelectId || options.ChapterSelectId == null)))
break;
case 1:
filtered = filtered
.Where(w => ((w.CategoryId == options.CategorySelectId || options.CategorySelectId == null)))
break;
case 2:
// ???
break;
default:
return BadRequest();
}
var phrases = async filtered
.AsNoTracking()
.ToListAsync();
return Ok(phrases);
Upvotes: 1
Reputation: 9969
LINQ queries on an IQueryable
or an IEnumerable
aren't evaluated until you actually iterate the results - by looping over it, or calling ToList()
or suchlike.
Therefore, you can build things in stages, for example:
var exp = dbcontext.Logs.Where(x => x.Code == 4);
which won't execute any code on the database yet. Then you can later on say:
var logs = await exp.Where(x => x.Module == "AUTH").ToListAsync();
and that will execute the query, because of the ToListAsync()
.
The massive advantage of this is being able to build up queries in stages with sensible methods (instead of slicing strings of SQL around), and trust that Entity Framework or whatever LINQ provider you're using will turn it into a reasonable query (which it does most of the time).
So for your enormous example, I'm not going to attempt to rewrite it here, but you can certainly do something in this form:
var query = await db.Phrases;
// common where clauses
switch (options.PhraseTypeSelectId) {
case 0:
query = query.Where(w => /* appropriate restriction for 0 */);
break;
// other cases
}
// more conditional where clauses perhaps
// projection clauses like Select()
// finally, execute the query
List<Phrases> phrases = await query.ToListAsync();
Upvotes: 1
Reputation: 713
You could use the extension method .AsQueryable()
this will return the former part of a linq statment as a queryable expression, allowing further manipulation and transformer if needed.
for example
var query =db.Phrases.where(phraseTypePredicate).AsQueryable();
switch(options.PhraseTypeSelectId)
{
case 1:
query = query.where(case1Predicate);
break;
case 2:
query = query.where(case2Predicate);
break;
...
}
var results = query.ToList(); // or someother way of enumerating the expression
Upvotes: 3
Reputation: 275
Since I couldn't compile the example, I'm not 100% sure that this will compile either, but you should get the gist of it.
You can create specific expressions for the different PhraseTypeSelecteId
and use it in the .Where()
method later.
Expression<Func<Phrase, bool>> phraseTypePredicate = null;
switch (options.PhraseTypeSelectId)
{
case 0:
phraseTypePredicate =
w => ((w.CategoryId == options.CategorySelectId || options.CategorySelectId == null) ||
(w.ChapterId == options.ChapterSelectId || options.ChapterSelectId == null));
break;
case 1:
phraseTypePredicate =
w => ((w.CategoryId == options.CategorySelectId || options.CategorySelectId == null));
break;
case 2:
phraseTypePredicate =
w => ((w.ChapterId == options.ChapterSelectId || options.ChapterSelectId == null));
break;
}
if (phraseTypePredicate != null)
{
List<Phrase> phrases = phrases = await db.Phrases
.Where(phraseTypePredicate)
.Where(w => (w.EnglishAscii >= es1 && w.EnglishAscii <= ee1) || (w.EnglishAscii >= es2 && w.EnglishAscii <= ee2))
.Where(w => (w.RomajiAscii >= rs1 && w.RomajiAscii <= re1) || (w.RomajiAscii >= rs2 && w.RomajiAscii <= ee2))
.Where(w => (options.CreatedBy == 0 || w.CreatedBy == options.CreatedBy))
.Where(w => (options.ModifiedBy == 0 || w.ModifiedBy == options.ModifiedBy))
.Where(w => (options.JLPT == 0 || w.JLPT == options.JLPT))
.AsNoTracking()
.ToListAsync();
return Ok(phrases);
}
return BadRequest();
Upvotes: 3