c# - Creating a composite condition using anonymous filter method -


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:

  1. this code doesn't follow dry principle. have 2 same lambdas checks statusa (look in ternary operator) , 2 same lambdas checks statusb
  2. 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.

  3. 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.

  1. what problems got, if result filter null? answer: argumentnullexception due documentation. must think case.

  2. what problems can got using of simple func<...>? well, must know difference between ienumerable<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