c# - Expressions - how to reuse business logic? How to combine them? -


note: it's long post, please scroll bottom see questions - make easier understand problem. thanks!


i have "member" model defined follows:

public class member {     public string firstname { get; set; }     public string lastname { get; set; }     public string screenname { get; set; }      [notmapped]     public string realname     {          { return (firstname + " " + lastname).trimend(); }     }      [notmapped]     public string displayname     {                 {             return string.isnullorempty(screenname) ? realname : screenname;         }     } } 

this existing project, , model, , don't want change this. got request enable profile retrieval displayname:

public member getmemberbydisplayname(string displayname) {      var member = this.memberrepository                       .firstordefault(m => m.displayname == displayname);      return member; } 

this code not work because displayname not mapped field in database. okay, make expression then:

public member getmemberbydisplayname(string displayname) {      expression<func<member, bool>> displaynamesearchexpr = m => (                 string.isnullorempty(m.screenname)                      ? (m.name + " " + m.lastname).trimend()                      : m.screenname             ) == displayname;       var member = this.memberrepository                       .firstordefault(displaynamesearchexpr);       return member; } 

this works. problem business logic generate display name copy/pasted in 2 different places. want avoid this. not understand how this. best came following:

  public class member     {          public static expression<func<member, string>> getdisplaynameexpression()         {             return m => (                             string.isnullorempty(m.screenname)                                 ? (m.name + " " + m.lastname).trimend()                                 : m.screenname                         );         }          public static expression<func<member, bool>> filtermemberbydisplaynameexpression(string displayname)         {             return m => (                 string.isnullorempty(m.screenname)                     ? (m.name + " " + m.lastname).trimend()                     : m.screenname             ) == displayname;         }          private static readonly func<member, string> getdisplaynameexpressioncompiled = getdisplaynameexpression().compile();          [notmapped]         public string displayname         {                         {                 return getdisplaynameexpressioncompiled(this);             }         }          [notmapped]         public string realname         {              { return (firstname + " " + lastname).trimend(); }         }     } 

questions:

(1) how reuse getdisplaynameexpression() inside filtermemberbydisplaynameexpression()? tried expression.invoke:

public static expression<func<member, bool>> filtermemberbydisplaynameexpression(string displayname) {     expression<func<string, bool>> e0 = s => s == displayname;     var e1 = getdisplaynameexpression();      var combinedexpression = expression.lambda<func<member, bool>>(            expression.invoke(e0, e1.body), e1.parameters);      return combinedexpression; } 

but following error provider:

the linq expression node type 'invoke' not supported in linq entities.

(2) approach use expression.compile() inside displayname property? issues it?

(3) how move realname logic inside getdisplaynameexpression()? think have create expression , compiled expression, not understand how call realnameexpression inside getdisplaynameexpression().

thank you.

i can fix expression generator, , can compose getdisplaynameexpression (so 1 , 3)

public class member {     public string screenname { get; set; }     public string name { get; set; }     public string firstname { get; set; }     public string lastname { get; set; }      public static expression<func<member, string>> getrealnameexpression()     {         return m => (m.name + " " + m.lastname).trimend();     }      public static expression<func<member, string>> getdisplaynameexpression()     {         var isnullorempty = typeof(string).getmethod("isnullorempty", bindingflags.static | bindingflags.public, null, new[] { typeof(string) }, null);          var e0 = getrealnameexpression();         var par1 = e0.parameters[0];          // done in way, refactoring correctly rename m.screenname         // have used similar trick string.isnullorempty,         // have been useless, because name , signature won't         // ever change.         expression<func<member, string>> e1 = m => m.screenname;          var screenname = (memberexpression)e1.body;         var prop = expression.property(par1, (propertyinfo)screenname.member);         var condition = expression.condition(expression.call(null, isnullorempty, prop), e0.body, prop);          var combinedexpression = expression.lambda<func<member, string>>(condition, par1);         return combinedexpression;     }      private static readonly func<member, string> getdisplaynameexpressioncompiled = getdisplaynameexpression().compile();      private static readonly func<member, string> getrealnameexpressioncompiled = getrealnameexpression().compile();      public string displayname     {                 {             return getdisplaynameexpressioncompiled(this);         }     }      public string realname     {                 {             return getrealnameexpressioncompiled(this);         }     }      public static expression<func<member, bool>> filtermemberbydisplaynameexpression(string displayname)     {         var e0 = getdisplaynameexpression();         var par1 = e0.parameters[0];          var combinedexpression = expression.lambda<func<member, bool>>(             expression.equal(e0.body, expression.constant(displayname)), par1);          return combinedexpression;     } 

note how reuse same parameter of getdisplaynameexpression expression e1.parameters[0] (put in par1) don't have rewrite expression (otherwise have needed use expression rewriter).

we use trick because had single expression handle, had attach new code. totally different (we have needed expression rewriter) case of trying combine 2 expressions (for example getrealnameexpression() + " " + getdisplaynameexpression(), both require parameter member, parameters separate... https://stackoverflow.com/a/5431309/613130 work...

for 2, don't see problem. correctly using static readonly. please, @ getdisplaynameexpression , think "is better business code duplication pay or that?"

generic solution

now... quite sure doable... , in fact is doable: expression "expander" "expands" "special properties" expression(s) "automagically".

public static class queryableex {     private static readonly concurrentdictionary<type, dictionary<propertyinfo, lambdaexpression>> expressions = new concurrentdictionary<type, dictionary<propertyinfo, lambdaexpression>>();      public static iqueryable<t> expand<t>(this iqueryable<t> query)     {         var visitor = new queryablevisitor();         expression expression2 = visitor.visit(query.expression);          return query.expression != expression2 ? query.provider.createquery<t>(expression2) : query;     }      private static dictionary<propertyinfo, lambdaexpression> get(type type)     {         dictionary<propertyinfo, lambdaexpression> dict;          if (expressions.trygetvalue(type, out dict))         {             return dict;         }          var props = type.getproperties(bindingflags.public | bindingflags.instance);          dict = new dictionary<propertyinfo, lambdaexpression>();          foreach (var prop in props)         {             var exp = type.getmember(prop.name + "expression", bindingflags.public | bindingflags.nonpublic | bindingflags.static).where(p => p.membertype == membertypes.field || p.membertype == membertypes.property).singleordefault();              if (exp == null)             {                 continue;             }              if (!typeof(lambdaexpression).isassignablefrom(exp.membertype == membertypes.field ? ((fieldinfo)exp).fieldtype : ((propertyinfo)exp).propertytype))             {                 continue;             }              var lambda = (lambdaexpression)(exp.membertype == membertypes.field ? ((fieldinfo)exp).getvalue(null) : ((propertyinfo)exp).getvalue(null, null));              if (prop.propertytype != lambda.returntype)             {                 throw new exception(string.format("mismatched return type of expression of {0}.{1}, {0}.{2}", type.name, prop.name, exp.name));             }              dict[prop] = lambda;         }          // try save memory, removing empty dictionaries         if (dict.count == 0)         {             dict = null;         }          // there no problem if multiple threads generate "versions"         // of dict @ same time. equivalent, worst         // case cpu cycles wasted.         dict = expressions.getoradd(type, dict);          return dict;     }      private class singleparameterreplacer : expressionvisitor     {         public readonly parameterexpression from;         public readonly expression to;          public singleparameterreplacer(parameterexpression from, expression to)         {             this.from = from;             this.to = to;         }          protected override expression visitparameter(parameterexpression node)         {             return node != this.from ? base.visitparameter(node) : this.visit(this.to);         }     }      private class queryablevisitor : expressionvisitor     {         protected static readonly assembly mscorlib = typeof(int).assembly;         protected static readonly assembly core = typeof(iqueryable).assembly;          // used check recursion         protected readonly list<memberinfo> membersbeingvisited = new list<memberinfo>();          protected override expression visitmember(memberexpression node)         {             var declaringtype = node.member.declaringtype;             var assembly = declaringtype.assembly;              if (assembly != mscorlib && assembly != core && node.member.membertype == membertypes.property)             {                 var dict = queryableex.get(declaringtype);                  lambdaexpression lambda;                  if (dict != null && dict.trygetvalue((propertyinfo)node.member, out lambda))                 {                     // anti recursion check                     if (this.membersbeingvisited.contains(node.member))                     {                         throw new exception(string.format("recursively visited member. chain: {0}", string.join("->", this.membersbeingvisited.concat(new[] { node.member }).select(p => p.declaringtype.name + "." + p.name))));                     }                      this.membersbeingvisited.add(node.member);                      // replace parameters of expression "our" reference                     var body = new singleparameterreplacer(lambda.parameters[0], node.expression).visit(lambda.body);                      expression exp = this.visit(body);                      this.membersbeingvisited.removeat(this.membersbeingvisited.count - 1);                      return exp;                 }             }              return base.visitmember(node);         }     } } 
  • how work? magic, reflection, fairy dust...
  • does support properties referencing other properties? yes
  • what need?

it needs every "special" property of name foo has corresponding static field/static property named fooexpression returns expression<func<class, something>>

it needs query "transformed" through extension method expand() @ point before materialization/enumeration. so:

public class member {     // can private/protected/internal     public static readonly expression<func<member, string>> realnameexpression =         m => (m.name + " " + m.lastname).trimend();      // here referencing "special" property, , works!     public static readonly expression<func<member, string>> displaynameexpression =         m => string.isnullorempty(m.screenname) ? m.realname : m.screenname;      public string realname     {                  {              // return real name want, reusing             // expression through compiled readonly              // realnameexpressioncompiled had done         }       }      public string displayname     {                 {         }     } }  // note use of .expand(); var res = (from p in ctx.member            p.realname == "something" || p.realname.contains("anything") ||                 p.displayname == "foo"           select new { p.realname, p.displayname, p.name }).expand();  // can use res normally. 
  • limits 1: 1 problem methods single(expression), first(expression), any(expression) , similar, don't return iqueryable. change using first where(expression).expand().single()

  • limit 2: "special" properties can't reference in cycles. if uses b, b can't use a, , tricks using ternary expressions won't make work.


Comments

Popular posts from this blog

Unable to remove the www from url on https using .htaccess -