i trying edit search tool using linq,
what filter in clause (itemnumber == x , ( statementstatus == satusa or statementstatus == statusb ) )
but right now, like:
what filter in clause (itemnumber == x , statementstatus == satusa or statementstatus == statusb )
as , has higher operational priority on or result not want. :) please help?
using (var ctx = new mycontext()) { func<statement, bool> filter = null; if (!string.isnullorempty(request.itemnumber)) filter = new func<statement, bool>(s => s.statementdetails.any(sd => sd.itemnumber == request.itemnumber)); if (request.statusa) filter = filter == null ? new func<statement, bool>(s => s.statementstatus == statementstatustype.statusa) : filter.and(s => s.statementstatus == statementstatustype.statusa); if (request.statusb) filter = filter == null ? new func<statement, bool>(s => s.statementstatus == statementstatustype.statusb) : filter.or(s => s.statementstatus == statementstatustype.statusb); var results = ctx.statements .include("statementdetails") .include("statementdetails.entry") .where(filter) .take(100) .select(s => new statementsearchresultdto{ .... } }
that's happens not because , have higher priority or. happens in reality:
var firstfilter = ...; // itemnumber var secondfilter = ...; // statusa var firstandsecondfilter = firstfilter.and(secondfilter); // itemnumber && statusa var thirdfilter = ...; // statusb var endfilter = firstandsecondfilter.or(thirdfilter) // (itemnumber && statusa) || statusb.
the problem - wrong control flow. must that:
var filterbya = ...; var filterbyb = ...; var filterbyaorb = filterbya.or(filterbyb); var filterbynumber = ...; var endfiler = filterbynumber.and(filterbyaorb);
and code bad, not because works wrong, because it's hard write code in such style. reasons:
- this code doesn't follow dry principle. have 2 same lambdas checks
statusa
(look in ternary operator) , 2 same lambdas checksstatusb
you have long ternary operator null checks. that's bad because don't see general picture, eyes focused on syntax problems. may write , extension method andnullable funcs. this:
static func<t1, tout> andnullable<t1, tout>(this func<t1, tout> firstfunc, func<t1, tout> secondfunc) { if (firstfunc != null) { if (secondfunc != null) return firstfunc.and(secondfunc); else return firstfunc; } else { if (secondfunc != null) return secondfunc; else return null; } }
and same or. code can wroted this:
func<statement, bool> filter = null; if (request.statusa) filter = s => s.statementstatus == statementstatustype.statusa; if (request.statusb) filter = filter.ornullable(s => s.statementstatus == statementstatustype.statusb); if (!string.isnullorempty(request.itemnumber)) filter = filter.andnullable(s => s.statementdetails.any(sd => sd.itemnumber == request.itemnumber));
reads more better.
your filter global filter. writing of global filter simpler few filter conditions , number of lines small, it's more complicated understand filter. rewrite in way:
func<statement, bool> filterbystatusa = null; func<statement, bool> filterbystatusb = null; if (request.statusa) filterbystatusa = s => s.statementstatus == statementstatustype.statusa; if (request.statusb) filterbystatusb = s => s.statementstatus == statementstatustype.statusb; func<statement, bool> filterbystatuses = filterbystatusa.ornullable(filterbystatusb); func<statement, bool> filterbyitemnumber = null; if (!string.isnullorempty(request.itemnumber)) filterbyitemnumber = s => s.statementdetails.any(sd => sd.itemnumber == request.itemnumber); func<statement, bool> endfilter = filterbyitemnumber.and(filterbystatuses);
okay, have outthinked how can write filters combining them func<..>
still have problems.
what problems got, if result filter null? answer:
argumentnullexception
due documentation. must think case.what problems can got using of simple
func<...>
? well, must know difference betweenienumerable<t>
,iqueryable<t>
interfaces. in simple words, operations on ienumerable causes simple iteratation on elements (well, it's lazy, ienumerable slower iqueryable). so, example, combining of where(filter), take(100), tolist() on collection have 10000 elements bad filter , 400 elements cause iterating on 10100 elements. if wrote similar code iqueryable request of filtering send on database server , server iterate ~400 (or 1000, not 10100), if have configured indexes on database. happens in code.var results = ctx.statements // getting dbset<statement> implements interface iqueryable<statement> (and iqueryable<t> implements ienumerable<t>) .include("statementdetails") // still iqueryable<statement> .include("statementdetails.entry") // still iqueryable<statement> .where(filter) // cuz filter func<..> , there no extension methods on iqueryable accepts func<...> parameter, iqueryable<statement> casted automatically ienumerable<statement>. full collection loaded in memory , filtered. that's bad .take(100) // ienumerable<statement> .select(s => new statementsearchresultdto { .... // ienumerable<statement> -> ienumerable<statementsearchresultdto> }
okay. understand problem. so, simple right code can writed in way:
using (var ctx = new mycontext()) { results = ctx.statements .include("statementdetails") .include("statementdetails.entry") .asqueryable(); if (!string.isnullorempty(request.itemnumber)) results = results.where(s => s.statementdetails.any(sd => sd.itemnumber == request.itemnumber)); if (request.statusa) { if (request.statusb) results = results.where(s => s.statementstatus == statementstatustype.statusa || s.statementstatus == statementstatustype.statusa); else results = results.where(s => s.statementstatus == statementstatustype.statusa); } else { if (request.statusb) { results = results.where(s => s.statementstatus == statementstatustype.statusb); } else { // nothing } } results = .take(100) .select(s => new statementsearchresultdto{ .... }; // .. can results. }
yeah, totally ugly, database solves how find statements satisfy filter. therefore, request possible. must understand magic happens in code written upper. let's compare 2 examples of code:
results = results.where(s => s.statementdetails.any(sd => sd.itemnumber == request.itemnumber));
and this:
func<statement, bool> filter = s => s.statementdetails.any(sd => sd.itemnumber == request.itemnumber); results = results.where(filter);
what difference? why first more faster? answer: when compiler sees first code, examines type of results
iqueryable<t>
, ienumerable<t>
condition inside of brackets can have type func<statement, bool>
(compiled function) or expression<func<statement, bool>>
(data, can compiled in function). , compiler chooses expression
(why - dunno, chooses). after request of first object query compiled not in c# statement, in sql statement , sends server. sql server can optimize request, because of indexes existing.
well, more better way - write own expressions. there different ways write own expression, there way write not ugly syntax. problem can't invoke 1 expression - doesn't supported entity framework , can not supported orm's. so, can use predicatebuilder pete montgomery: link. , write 2 simple extensions on expressions suitable us.
public static expression<func<t, bool>> ornullable<t>(this expression<func<t, bool>> first, expression<func<t, bool>> second) { if (first != null && second != null) return first.compose(second, expression.orelse); if (first != null) return second; if (second != null) }
and same and. , can write our filter:
{ expression<func<statement, bool>> filterbystatusa = null; expression<func<statement, bool>> filterbystatusb = null; if (request.statusa) filterbystatusa = s => s.statementstatus == statementstatustype.statusa; if (request.statusb) filterbystatusb = s => s.statementstatus == statementstatustype.statusb; expression<func<statement, bool>> filterbystatuses = filterbystatusa.ornullable(filterbystatusb); expression<func<statement, bool>> filterbyitemnumber = null; if (!string.isnullorempty(request.itemnumber)) filterbyitemnumber = s => s.statementdetails.any(sd => sd.itemnumber == request.itemnumber); expression<func<statement, bool>> endfilter = filterbyitemnumber.and(filterbystatuses); requests = ...; if (endfilter != null) requests = requests.where(endfilter); }
you can got problem, because class expressionvisitor
in predicatebuilder
in .net < 4.0 sealed. can write own expressionvisitor or copy this article.
Comments
Post a Comment