.net - ORing LINQ Query, built using expression trees -
so, rather complicated.
i have set of rules in collection, rule contains these 3 properties.
field, op, , info (all strings)
so rule might "state", "eq", "ca"
my general rules rules anded together. however, caveat that, if have same field value, ored. allows "state", "eq", "ca", or "state", "eq", "tx" , "firstname", "eq", "john".
the issue current way of applying rules won't work, because keeps building linq look using each rule create more , more explicit.
var result = rules.aggregate(_repository.all, (current, rule) => current.extendquery(rule))
extendquery
extension method wrote, uses expressiontrees, generate new query, applies current rule passed in query. (effectively anding them together)
now wouldn't hard me modify .aggregate
line grouping rules field, , generate unique query each field, how "or" them instead of "and"?
and each of queries, how "and" them together? union?
extendquery looks this
public static iqueryable<t> extendquery<t>(this iqueryable<t> query, queryablerequestmessage.whereclause.rule rule) t : class { var parameter = expression.parameter(typeof(t), "x"); look property = expression.property(parameter, rule.field); var type = property.type; constantexpression constant; if (type.isenum) { var enumeration = enum.parse(type, rule.data); var intvalue = (int)enumeration; constant = expression.constant(intvalue); type = typeof(int); //add "id" convention, because enum back upwards lacking @ point in entity framework property = expression.property(parameter, rule.field + "id"); } else if(type == typeof(datetime)) { constant = expression.constant(datetime.parseexact(rule.data, "dd/mm/yyyy", cultureinfo.currentculture)); } else if (type.isgenerictype && type.getgenerictypedefinition() == typeof(nullable<>)) { //this convert rule.data basetype, not nullable type (because won't work) var converter = typedescriptor.getconverter(type); var value = converter.convertfrom(rule.data); constant = expression.constant(value); //we alter type of property converted it's base of operations type //this because expression.greaterthanorequal can't compare decimal nullable<decimal> var basetype = type.gettypeofnullable(); property = expression.convert(property, basetype); } else { constant = expression.constant(convert.changetype(rule.data, type)); } switch (rule.op) { case "eq": //equals case "ne": //notequals { var status = rule.op.equals("eq") ? expression.equal(property, constant) : expression.notequal(property, constant); var lambda = expression.lambda(condition, parameter); var phone call = expression.call(typeof(queryable), "where", new[] { query.elementtype }, query.expression, lambda); query = query.provider.createquery<t>(call); break; } case "lt": //less query = type == typeof (string) ? queryexpressionstring(query, expression.lessthan, type, property, constant, parameter) : queryexpression(query, expression.lessthan, property, constant, parameter); break; case "le": //less or equal query = type == typeof (string) ? queryexpressionstring(query, expression.lessthanorequal, type, property, constant, parameter) : queryexpression(query, expression.lessthanorequal, property, constant, parameter); break; case "gt": //greater query = type == typeof (string) ? queryexpressionstring(query, expression.greaterthan, type, property, constant, parameter) : queryexpression(query, expression.greaterthan, property, constant, parameter); break; case "ge": //greater or equal query = type == typeof (string) ? queryexpressionstring(query, expression.greaterthanorequal, type, property, constant, parameter) : queryexpression(query, expression.greaterthanorequal, property, constant, parameter); break; case "bw": //begins case "bn": //does not begin query = querymethod(query, rule, type, "startswith", property, constant, "bw", parameter); break; case "ew": //ends case "en": //does not end query = querymethod(query, rule, type, "endswith", property, constant, "cn", parameter); break; case "cn": //contains case "nc": //does not contain query = querymethod(query, rule, type, "contains", property, constant, "cn", parameter); break; case "nu": //todo: null case "nn": //todo: not null break; } homecoming query; } private static iqueryable<t> queryexpression<t>( iqueryable<t> query, func<expression, expression, binaryexpression> expression, look property, look value, parameterexpression parameter ) t : class { var status = expression(property, value); var lambda = expression.lambda(condition, parameter); var phone call = expression.call(typeof(queryable), "where", new[] { query.elementtype }, query.expression, lambda); query = query.provider.createquery<t>(call); homecoming query; } private static iqueryable<t> queryexpressionstring<t>( iqueryable<t> query, func<expression, expression, binaryexpression> expression, type type, look property, look value, parameterexpression parameter) { var containsmethod = type.getmethod("compareto", new[] { type }); var callcontains = expression.call(property, containsmethod, value); var phone call = expression(callcontains, expression.constant(0, typeof(int))); homecoming query.where(expression.lambda<func<t, bool>>(call, parameter)); } private static iqueryable<t> querymethod<t>( iqueryable<t> query, queryablerequestmessage.whereclause.rule rule, type type, string methodname, look property, look value, string op, parameterexpression parameter ) t : class { var containsmethod = type.getmethod(methodname, new[] { type }); var phone call = expression.call(property, containsmethod, value); var look = rule.op.equals(op) ? expression.lambda<func<t, bool>>(call, parameter) : expression.lambda<func<t, bool>>(expression.isfalse(call), parameter); query = query.where(expression); homecoming query; }
so quite easy actually.
now code generates 1 each rule , need 1 little bit complicated status modifications code in order:
private static look getcomparisonexpression(this rule rule, parameterexpression parameter) { look property = expression.property(parameter, rule.field); constantexpression constant = expression.constant(4); /* code generates constant , other stuff */ switch (rule.op) { case "eq": //equals case "ne": //notequals { var status = rule.op.equals("eq") ? expression.equal(property, constant) : expression.notequal(property, constant); homecoming condition; } default: throw new notimplementedexception(); } }
this snippet of needed original code. method not wrap query generate comparing look on given parameter whatever in rule
.
now starting statement generates query:
var result = rules.generate(_repository.all);
generate method groups rules property name field
, each grouping generates and also
(this &&
operator) status expression:
(group1comparision) && (group2comparison) && on public static iqueryable<t> generate<t>(this ienumerable<rule> rules, iqueryable<t> query) t : class { if (rules.count() == 0) homecoming query; var groups = rules.groupby(x => x.field).toarray(); var parameter = expression.parameter(typeof(t)); var comparing = groups.first().getcomparisonforgroup(parameter); foreach (var grouping in groups.skip(1)) { var othercomparions = group.getcomparisonforgroup(parameter); comparing = expression.andalso(comparison, othercomparions); } var lambda = expression.lambda(comparison, parameter); var phone call = expression.call(typeof(queryable), "where", new[] { query.elementtype }, query.expression, lambda); homecoming query.provider.createquery<t>(call); }
note grouping property name renders original order of rules irrelevant.
the lastly thing create comparing groups ||
operator:
public static look getcomparisonforgroup(this ienumerable<rule> group, parameterexpression parameter) { var comparing = group.select((rule) => rule.getcomparisonexpression(parameter)).toarray(); homecoming comparison.skip(1).aggregate(comparison.first(), (left, right) => expression.orelse(left, right)); }
so no external library necessary, given rules list:
var rules = new rule[] { new rule{ field = "a", info = "4", op="ne"}, new rule{ field = "b", info = "4", op="eq"}, new rule{ field = "a", info = "4", op="eq"}, new rule{ field = "c", info = "4", op="ne"}, new rule{ field = "a", info = "4", op="eq"}, new rule{ field = "c", info = "4", op="eq"}, };
i generated such status introduced single where
phone call query:
($var1.a != 4 || $var1.a == 4 || $var1.a == 4) && $var1.b == 4 && ($var1.c != 4 || $var1.c == 4)
.net linq expression-trees
No comments:
Post a Comment