c# - Rewriting Linq Select to a new subpath -
i trying dynamically build linq queries. want able reuse linq expressions in other linq expressions. example:
public class dummy { public string test { get; set; } public sub sub { get; set; } } public class sub { public static expression<func<sub, string>> converter { { return x => x.id + ": " + x.text; } } public int id { get; set; } public string text { get; set; } }
when writing converter dummy class, converter should able reuse sub.converter. thi spurpose have written dynamicselect<> extension method:
var result = query .where(x=>x.sub != null) .dynamicselect(x => new result()) .select(x => x.subtext, x => x.sub,sub.converter) .apply() .tolist();
dynamicselect creates new selectionbuilder, select part takes first input targetproperty (result.subtext), second property input property want convert , third input converter sub property. .apply call returns builtup expression tree.
i managed working simpler usecase (without subpath):
var result = query.dynamicselect(x => new result()) .select(x => x.resolvedtest, x => x.inner == null ? x.test : x.inner.test) .select(x => x.subtext, x => x.sub == null ? null : x.sub.text) .apply() .tolist();
but how rebase expression subpath?
code far:
public static class selectbuilder { public static linqdynamicconverter<tinput,toutput> dynamicselect<tinput,toutput>( iqueryable<tinput> data, expression<func<tinput, toutput>> initialconverter) toutput : new() { return new linqdynamicconverter<tinput,toutput>(data, initialconverter); } } public class linqdynamicconverter<tinput,toutput> toutput: new() { #region inner classes private class memberassignmentvisitor : expressionvisitor { private idictionary<memberinfo, memberassignment> singlepropertytobinding { get; set; } public memberassignmentvisitor(idictionary<memberinfo, memberassignment> singlepropertytobinding) { singlepropertytobinding = singlepropertytobinding; } protected override memberassignment visitmemberassignment(memberassignment node) { singlepropertytobinding[node.member] = node; return base.visitmemberassignment(node); } } private class memberinfovisitor : expressionvisitor { internal memberinfo singleproperty { get; private set; } public memberinfovisitor() { } protected override expression visitmember(memberexpression node) { singleproperty = node.member; return base.visitmember(node); } } #endregion #region properties private iqueryable<tinput> data { get;set; } private expression<func<tinput, toutput>> initialconverter { get;set;} private idictionary<memberinfo, memberassignment> singlepropertytobinding { get; set; } #endregion #region constructor internal linqdynamicconverter(iqueryable<tinput> data, expression<func<tinput, toutput>> initialconverter) { data = data; initialconverter = x => new toutput(); // start clean plate var replace = initialconverter.replace(initialconverter.parameters[0], initialconverter.parameters[0]); singlepropertytobinding = new dictionary<memberinfo, memberassignment>(); memberassignmentvisitor v = new memberassignmentvisitor(singlepropertytobinding); v.visit(initialconverter); } #endregion public linqdynamicconverter<tinput,toutput> select<tproperty,tconverted>( expression<func<toutput, tconverted>> initializedoutputproperty, expression<func<tinput, tproperty>> subpath, expression<func<tproperty, tconverted>> subselect) { //???? return this; } // 1 works public linqdynamicconverter<tinput,toutput> select<tproperty>( expression<func<toutput, tproperty>> initializedoutputproperty, expression<func<tinput, tproperty>> subselect) { var miv = new memberinfovisitor(); miv.visit(initializedoutputproperty); var mi = miv.singleproperty; var param = initialconverter.parameters[0]; expression<func<tinput, tproperty>> replace = (expression<func<tinput, tproperty>>)subselect.replace(subselect.parameters[0], param); var bind = expression.bind(mi, replace.body); singlepropertytobinding[mi] = bind; return this; } public iqueryable<toutput> apply() { var converter = expression.lambda<func<tinput, toutput>>( expression.memberinit((newexpression)initialconverter.body, singlepropertytobinding.values), initialconverter.parameters[0]); return data.select(converter); } }
found solution. needed write new expression visitor added member acces(es):
/// <summary> /// rebinds full expression tree new single property /// example: x => x.sub subpath. visitor starts visiting new /// expression x => x.text. result should x => x.sub.text. /// traversing member accesses starts rightmost side working toward parameterexpression. /// when reach parameterexpression have inject whole subpath including parameter of subpath /// </summary> /// <typeparam name="tconverted"></typeparam> /// <typeparam name="tproperty"></typeparam> private class linqrebindvisitor<tconverted, tproperty> : expressionvisitor { public expression<func<tinput, tconverted>> resultexpression { get; private set; } private parameterexpression subpathparameter { get; set; } private expression subpathbody { get; set; } private bool initialmode { get; set; } public linqrebindvisitor(expression<func<tinput, tproperty>> subpath) { subpathparameter = subpath.parameters[0]; subpathbody = subpath.body; initialmode = true; } protected override expression visitmember(memberexpression node) { // note cannot overwrite visitparameter because method must return parameterexpression // whenever detect our next expression parameterexpression, inject subtree if (node.expression parameterexpression && node.expression != subpathparameter) { var expr = visit(subpathbody); return expression.makememberaccess(expr, node.member); } return base.visitmember(node); } protected override expression visitlambda<t>(expression<t> node) { bool initialmode = initialmode; initialmode = false; expression<t> expr = (expression<t>)base.visitlambda<t>(node); if (initialmode) { resultexpression = expression.lambda<func<tinput, tconverted>>(expr.body,subpathparameter); } return expr; } }
the single property select method rather trivial:
public linqdynamicconverter<tinput,toutput> select<tproperty,tconverted>( expression<func<toutput, tconverted>> initializedoutputproperty, expression<func<tinput, tproperty>> subpath, expression<func<tproperty, tconverted>> subselect) { linqrebindvisitor<tconverted, tproperty> rebindvisitor = new linqrebindvisitor<tconverted, tproperty>(subpath); rebindvisitor.visit(subselect); var result = rebindvisitor.resultexpression; return property<tconverted>(initializedoutputproperty, result); }
Comments
Post a Comment