c# - How to get the where clause from IQueryable defined as interface -
class program { static void main(string[] args) { var c = new sampleclass<classstring>(); c.classstrings.add(new classstring{ name1 = "1", name2 = "1"}); c.classstrings.add(new classstring{ name1 = "2", name2 = "2"}); var result = c.query<classstring>().where(s => s.name1.equals("2")); console.writeline(result); console.readline(); } } public class classstring { public string name1 { get; set; } public string name2 { get; set; } } public interface isampleq { iqueryable<t> query<t>() t: class , new(); } public class sampleclass<x> : isampleq { public list<x> classstrings { get; private set; } public sampleclass() { classstrings = new list<x>(); } public iqueryable<t> query<t>() t : class, new() { //get expression here. return new enumerablequery<t>((ienumerable<t>) classstrings); } }
i looked solution1, solution2 , solution3 seems not applicable question. since clause defined outside , interface of class. how expression inside query method? since no variable being pass thru.
the purpose, want retrieve , injected destination (which dbcontext iqueryable). because have common interface isampleq.
added new sample codes same scenario:
internal class program { private static void main(string[] args) { var oracledbcontext = new oracledbcontext(); var result = oracledbcontext.query<person>().where(person => person.name.equals("username")); console.writeline(); console.readline(); } } public interface igenericquery { iqueryable<t> query<t>() t : class , new(); } public class oracledbcontext : igenericquery { public oracledbcontext() { //will hold oracle operations here. brevity, //query exposed. } public iqueryable<t> query<t>() t : class, new() { //get predicate here. since defined outside of //class. want retrieve since iqueryable<t> generic both class //oracledbcontext , mssqldbcontext. want re-inject or add //new expression before calling. // //for eg. //oracledbcontext.query<t>(where clause here) return null; } } public class mssqldbcontext : igenericquery { public mssqldbcontext() { //will hold mssql operations here. brevity, //query exposed. } public iqueryable<t> query<t>() t : class, new() { //get predicate here. return null; } } public class person { public int id { get; set; } public int name { get; set; } }
it quite complex... now... queryable.where()
works way:
public static iqueryable<tsource> where<tsource>(this iqueryable<tsource> source, expression<func<tsource, bool>> predicate) { return source.provider.createquery<tsource>(expression.call(null, ...
so queryable.where
calls source.provider.createquery()
retuns new iqueryable<>
. if want able "see" where()
while being added (and manipulate it), must "be" iqueryable<>.provider
, , have createquery()
, must create class implements iqueryprovider
(and class implements iqueryable<t>
).
another way (much simpler) have simple query "converter": method accepts iqueryable<>
, returns manipulated iqueryable<>
:
var result = c.query<classstring>().where(s => s.name1.equals("2")).fixmyquery();
as said, full route quite long:
namespace utilities { using system; using system.collections; using system.collections.generic; using system.collections.objectmodel; using system.data.entity; using system.data.entity.infrastructure; using system.linq; using system.linq.expressions; using system.reflection; using system.threading; using system.threading.tasks; public class proxydbcontext : dbcontext { protected static readonly methodinfo proxifysetsmethod = typeof(proxydbcontext).getmethod("proxifysets", bindingflags.instance | bindingflags.nonpublic); protected static class proxydbcontexsetter<tcontext> tcontext : proxydbcontext { public static readonly action<tcontext> = x => { }; static proxydbcontexsetter() { var properties = typeof(tcontext).getproperties(bindingflags.instance | bindingflags.public | bindingflags.flattenhierarchy); parameterexpression context = expression.parameter(typeof(tcontext), "context"); fieldinfo manipulatorfield = typeof(proxydbcontext).getfield("manipulator", bindingflags.instance | bindingflags.public); expression manipulator = expression.field(context, manipulatorfield); var sets = new list<expression>(); foreach (propertyinfo property in properties) { if (property.getmethod == null) { continue; } methodinfo setmethod = property.setmethod; if (setmethod != null && !setmethod.ispublic) { continue; } type type = property.propertytype; type entitytype = getidbsettypeargument(type); if (entitytype == null) { continue; } if (!type.isassignablefrom(typeof(dbset<>).makegenerictype(entitytype))) { continue; } type dbsettype = typeof(dbset<>).makegenerictype(entitytype); constructorinfo constructor = typeof(proxydbset<>) .makegenerictype(entitytype) .getconstructor(new[] { dbsettype, typeof(func<bool, expression, expression>) }); memberexpression property2 = expression.property(context, property); binaryexpression assign = expression.assign(property2, expression.new(constructor, expression.convert(property2, dbsettype), manipulator)); sets.add(assign); } expression<action<tcontext>> lambda = expression.lambda<action<tcontext>>(expression.block(sets), context); = lambda.compile(); } // gets t of idbsetlt;t> private static type getidbsettypeargument(type type) { ienumerable<type> interfaces = type.isinterface ? new[] { type }.concat(type.getinterfaces()) : type.getinterfaces(); type argument = (from x in interfaces x.isgenerictype let gt = x.getgenerictypedefinition() gt == typeof(idbset<>) select x.getgenericarguments()[0]).singleordefault(); return argument; } } public readonly func<bool, expression, expression> manipulator; /// <summary> /// /// </summary> /// <param name="manipulator">first parameter: true execute, false createquery.</param> /// <param name="resetsets">true have dbset<tentity> , idbset<tentity> proxified</param> public proxydbcontext(func<bool, expression, expression> manipulator, bool resetsets = true) { manipulator = manipulator; if (resetsets) { proxifysetsmethod.makegenericmethod(gettype()).invoke(this, null); } } /// <summary> /// /// </summary> /// <param name="nameorconnectionstring"></param> /// <param name="manipulator">first parameter: true execute, false createquery.</param> /// <param name="resetsets">true have dbset<tentity> , idbset<tentity> proxified</param> public proxydbcontext(string nameorconnectionstring, func<bool, expression, expression> manipulator, bool resetsets = true) : base(nameorconnectionstring) { manipulator = manipulator; if (resetsets) { proxifysetsmethod.makegenericmethod(gettype()).invoke(this, null); } } protected void proxifysets<tcontext>() tcontext : proxydbcontext { proxydbcontexsetter<tcontext>.do((tcontext)this); } public override dbset<tentity> set<tentity>() { return new proxydbset<tentity>(base.set<tentity>(), manipulator); } public override dbset set(type entitytype) { dbset set = base.set(entitytype); constructorinfo constructor = typeof(proxydbsetnongeneric<>) .makegenerictype(entitytype) .getconstructor(new[] { typeof(dbset), typeof(func<bool, expression, expression>) }); return (dbset)constructor.invoke(new object[] { set, manipulator }); } } /// <summary> /// dbset, implemented internaldbset<> ef. /// </summary> /// <typeparam name="tentity"></typeparam> public class proxydbsetnongeneric<tentity> : dbset, iqueryable<tentity>, ienumerable<tentity>, idbasyncenumerable<tentity>, iqueryable, ienumerable, idbasyncenumerable tentity : class { protected readonly dbset basedbset; protected readonly iqueryable<tentity> proxyqueryable; public readonly func<bool, expression, expression> manipulator; protected readonly fieldinfo internalsetfield = typeof(dbset).getfield("_internalset", bindingflags.instance | bindingflags.nonpublic); /// <summary> /// /// </summary> /// <param name="basedbset"></param> /// <param name="manipulator">first parameter: true execute, false createquery.</param> public proxydbsetnongeneric(dbset basedbset, func<bool, expression, expression> manipulator) { basedbset = basedbset; iqueryprovider provider = ((iqueryable)basedbset).provider; proxydbprovider proxydbprovider = new proxydbprovider(provider, manipulator); proxyqueryable = proxydbprovider.createquery<tentity>(((iqueryable)basedbset).expression); manipulator = manipulator; if (internalsetfield != null) { internalsetfield.setvalue(this, internalsetfield.getvalue(basedbset)); } } /// <summary> /// /// </summary> /// <param name="basedbset"></param> /// <param name="proxyqueryable"></param> /// <param name="manipulator">first parameter: true execute, false createquery.</param> public proxydbsetnongeneric(dbset basedbset, proxyqueryable<tentity> proxyqueryable, func<bool, expression, expression> manipulator) { basedbset = basedbset; proxyqueryable = proxyqueryable; manipulator = manipulator; if (internalsetfield != null) { internalsetfield.setvalue(this, internalsetfield.getvalue(basedbset)); } } public override object add(object entity) { return basedbset.add(entity); } public override ienumerable addrange(ienumerable entities) { return basedbset.addrange(entities); } public override dbquery asnotracking() { return new proxydbsetnongeneric<tentity>(basedbset, new proxyqueryable<tentity>((proxydbprovider)proxyqueryable.provider, (iqueryable<tentity>)basedbset.asnotracking()), manipulator); } [obsolete] public override dbquery asstreaming() { #pragma warning disable 618 return new proxydbsetnongeneric<tentity>(basedbset, new proxyqueryable<tentity>((proxydbprovider)proxyqueryable.provider, (iqueryable<tentity>)basedbset.asstreaming()), manipulator); #pragma warning restore 618 } public override object attach(object entity) { return basedbset.attach(entity); } public override object create(type derivedentitytype) { return basedbset.create(derivedentitytype); } public override object create() { return basedbset.create(); } public override object find(params object[] keyvalues) { return basedbset.find(keyvalues); } public override task<object> findasync(cancellationtoken cancellationtoken, params object[] keyvalues) { return basedbset.findasync(cancellationtoken, keyvalues); } public override task<object> findasync(params object[] keyvalues) { return basedbset.findasync(keyvalues); } public override dbquery include(string path) { return new proxydbsetnongeneric<tentity>(basedbset, new proxyqueryable<tentity>((proxydbprovider)proxyqueryable.provider, (iqueryable<tentity>)basedbset.include(path)), manipulator); } public override ilist local { { return basedbset.local; } } public override object remove(object entity) { return basedbset.remove(entity); } public override ienumerable removerange(ienumerable entities) { return basedbset.removerange(entities); } public override dbsqlquery sqlquery(string sql, params object[] parameters) { return basedbset.sqlquery(sql, parameters); } ienumerator<tentity> ienumerable<tentity>.getenumerator() { return proxyqueryable.getenumerator(); } ienumerator ienumerable.getenumerator() { return ((ienumerable)proxyqueryable).getenumerator(); } type iqueryable.elementtype { { return proxyqueryable.elementtype; } } expression iqueryable.expression { { return proxyqueryable.expression; } } iqueryprovider iqueryable.provider { { return proxyqueryable.provider; } } idbasyncenumerator<tentity> idbasyncenumerable<tentity>.getasyncenumerator() { return ((idbasyncenumerable<tentity>)proxyqueryable).getasyncenumerator(); } idbasyncenumerator idbasyncenumerable.getasyncenumerator() { return ((idbasyncenumerable)proxyqueryable).getasyncenumerator(); } public override string tostring() { return proxyqueryable.tostring(); } } public class proxydbset<tentity> : dbset<tentity>, iqueryable<tentity>, ienumerable<tentity>, idbasyncenumerable<tentity>, iqueryable, ienumerable, idbasyncenumerable tentity : class { protected readonly dbset<tentity> basedbset; protected readonly iqueryable<tentity> proxyqueryable; public readonly func<bool, expression, expression> manipulator; protected readonly fieldinfo internalsetfield = typeof(dbset<tentity>).getfield("_internalset", bindingflags.instance | bindingflags.nonpublic); /// <summary> /// /// </summary> /// <param name="basedbset"></param> /// <param name="manipulator">first parameter: true execute, false createquery.</param> public proxydbset(dbset<tentity> basedbset, func<bool, expression, expression> manipulator) { basedbset = basedbset; iqueryprovider provider = ((iqueryable)basedbset).provider; proxydbprovider proxydbprovider = new proxydbprovider(provider, manipulator); proxyqueryable = proxydbprovider.createquery<tentity>(((iqueryable)basedbset).expression); manipulator = manipulator; if (internalsetfield != null) { internalsetfield.setvalue(this, internalsetfield.getvalue(basedbset)); } } /// <summary> /// /// </summary> /// <param name="basedbset"></param> /// <param name="proxyqueryable"></param> /// <param name="manipulator">first parameter: true execute, false createquery.</param> public proxydbset(dbset<tentity> basedbset, proxyqueryable<tentity> proxyqueryable, func<bool, expression, expression> manipulator) { basedbset = basedbset; proxyqueryable = proxyqueryable; manipulator = manipulator; if (internalsetfield != null) { internalsetfield.setvalue(this, internalsetfield.getvalue(basedbset)); } } public override tentity add(tentity entity) { return basedbset.add(entity); } public override ienumerable<tentity> addrange(ienumerable<tentity> entities) { return basedbset.addrange(entities); } public override dbquery<tentity> asnotracking() { return new proxydbset<tentity>(basedbset, new proxyqueryable<tentity>((proxydbprovider)proxyqueryable.provider, basedbset.asnotracking()), manipulator); } [obsolete] public override dbquery<tentity> asstreaming() { #pragma warning disable 618 return new proxydbset<tentity>(basedbset, new proxyqueryable<tentity>((proxydbprovider)proxyqueryable.provider, basedbset.asstreaming()), manipulator); #pragma warning restore 618 } public override tentity attach(tentity entity) { return basedbset.attach(entity); } public override tderivedentity create<tderivedentity>() { return basedbset.create<tderivedentity>(); } public override tentity create() { return basedbset.create(); } public override tentity find(params object[] keyvalues) { return basedbset.find(keyvalues); } public override task<tentity> findasync(cancellationtoken cancellationtoken, params object[] keyvalues) { return basedbset.findasync(cancellationtoken, keyvalues); } public override task<tentity> findasync(params object[] keyvalues) { return basedbset.findasync(keyvalues); } public override dbquery<tentity> include(string path) { return new proxydbset<tentity>(basedbset, new proxyqueryable<tentity>((proxydbprovider)proxyqueryable.provider, basedbset.include(path)), manipulator); } public override observablecollection<tentity> local { { return basedbset.local; } } public override tentity remove(tentity entity) { return basedbset.remove(entity); } public override ienumerable<tentity> removerange(ienumerable<tentity> entities) { return basedbset.removerange(entities); } public override dbsqlquery<tentity> sqlquery(string sql, params object[] parameters) { return basedbset.sqlquery(sql, parameters); } ienumerator<tentity> ienumerable<tentity>.getenumerator() { return proxyqueryable.getenumerator(); } ienumerator ienumerable.getenumerator() { return ((ienumerable)proxyqueryable).getenumerator(); } type iqueryable.elementtype { { return proxyqueryable.elementtype; } } expression iqueryable.expression { { return proxyqueryable.expression; } } iqueryprovider iqueryable.provider { { return proxyqueryable.provider; } } idbasyncenumerator<tentity> idbasyncenumerable<tentity>.getasyncenumerator() { return ((idbasyncenumerable<tentity>)proxyqueryable).getasyncenumerator(); } idbasyncenumerator idbasyncenumerable.getasyncenumerator() { return ((idbasyncenumerable)proxyqueryable).getasyncenumerator(); } public override string tostring() { return proxyqueryable.tostring(); } // note operator isn't virtual! if do: // dbset<foo> foo = new proxydbset<foo>(...) // dbset foo2 = (dbset)foo; // you'll have non-proxed dbset! public static implicit operator proxydbsetnongeneric<tentity>(proxydbset<tentity> entry) { return new proxydbsetnongeneric<tentity>((dbset)entry.basedbset, entry.manipulator); } } public class proxydbprovider : iqueryprovider, idbasyncqueryprovider { protected readonly iqueryprovider basequeryprovider; public readonly func<bool, expression, expression> manipulator; /// <summary> /// /// </summary> /// <param name="basequeryprovider"></param> /// <param name="manipulator">first parameter: true execute, false createquery.</param> public proxydbprovider(iqueryprovider basequeryprovider, func<bool, expression, expression> manipulator) { basequeryprovider = basequeryprovider; manipulator = manipulator; } public iqueryable<telement> createquery<telement>(expression expression) { expression expression2 = manipulator != null ? manipulator(false, expression) : expression; iqueryable<telement> query = basequeryprovider.createquery<telement>(expression2); iqueryprovider provider = query.provider; proxydbprovider proxy = provider == basequeryprovider ? : new proxydbprovider(provider, manipulator); return new proxyqueryable<telement>(proxy, query); } protected static readonly methodinfo createquerynongenerictogenericmethod = typeof(proxydbprovider).getmethod("createquerynongenerictogeneric", bindingflags.static | bindingflags.nonpublic); public iqueryable createquery(expression expression) { expression expression2 = manipulator != null ? manipulator(false, expression) : expression; iqueryable query = basequeryprovider.createquery(expression2); iqueryprovider provider = query.provider; proxydbprovider proxy = provider == basequeryprovider ? : new proxydbprovider(provider, manipulator); type entitytype = getiqueryabletypeargument(query.gettype()); if (entitytype == null) { return new proxyqueryable(proxy, query); } else { return (iqueryable)createquerynongenerictogenericmethod.makegenericmethod(entitytype).invoke(null, new object[] { proxy, query }); } } protected static proxyqueryable<telement> createquerynongenerictogeneric<telement>(proxydbprovider proxy, iqueryable<telement> query) { return new proxyqueryable<telement>(proxy, query); } public tresult execute<tresult>(expression expression) { expression expression2 = manipulator != null ? manipulator(true, expression) : expression; return basequeryprovider.execute<tresult>(expression2); } public object execute(expression expression) { expression expression2 = manipulator != null ? manipulator(true, expression) : expression; return basequeryprovider.execute(expression2); } // gets t of iqueryablelt;t> protected static type getiqueryabletypeargument(type type) { ienumerable<type> interfaces = type.isinterface ? new[] { type }.concat(type.getinterfaces()) : type.getinterfaces(); type argument = (from x in interfaces x.isgenerictype let gt = x.getgenerictypedefinition() gt == typeof(iqueryable<>) select x.getgenericarguments()[0]).firstordefault(); return argument; } public task<tresult> executeasync<tresult>(expression expression, cancellationtoken cancellationtoken) { var asyncqueryprovider = basequeryprovider idbasyncqueryprovider; if (asyncqueryprovider == null) { throw new notsupportedexception(); } expression expression2 = manipulator != null ? manipulator(true, expression) : expression; return asyncqueryprovider.executeasync<tresult>(expression2, cancellationtoken); } public task<object> executeasync(expression expression, cancellationtoken cancellationtoken) { var asyncqueryprovider = basequeryprovider idbasyncqueryprovider; if (asyncqueryprovider == null) { throw new notsupportedexception(); } expression expression2 = manipulator != null ? manipulator(true, expression) : expression; return asyncqueryprovider.executeasync(expression2, cancellationtoken); } } public class proxyqueryable : iorderedqueryable, iqueryable, ienumerable, idbasyncenumerable { protected readonly proxydbprovider proxydbprovider; protected readonly iqueryable basequeryable; public proxyqueryable(proxydbprovider proxydbprovider, iqueryable basequeryable) { proxydbprovider = proxydbprovider; basequeryable = basequeryable; } public ienumerator getenumerator() { return basequeryable.getenumerator(); } public type elementtype { { return basequeryable.elementtype; } } public expression expression { { return basequeryable.expression; } } public iqueryprovider provider { { return proxydbprovider; } } public override string tostring() { return basequeryable.tostring(); } idbasyncenumerator idbasyncenumerable.getasyncenumerator() { var asyncenumerator = basequeryable idbasyncenumerable; if (asyncenumerator == null) { throw new notsupportedexception(); } return asyncenumerator.getasyncenumerator(); } } public class proxyqueryable<telement> : iorderedqueryable<telement>, iqueryable<telement>, ienumerable<telement>, idbasyncenumerable<telement>, iorderedqueryable, iqueryable, ienumerable, idbasyncenumerable { protected readonly proxydbprovider proxydbprovider; protected readonly iqueryable<telement> basequeryable; public proxyqueryable(proxydbprovider proxydbprovider, iqueryable<telement> basequeryable) { proxydbprovider = proxydbprovider; basequeryable = basequeryable; } public ienumerator<telement> getenumerator() { return basequeryable.getenumerator(); } ienumerator ienumerable.getenumerator() { return ((ienumerable)basequeryable).getenumerator(); } public type elementtype { { return basequeryable.elementtype; } } public expression expression { { return basequeryable.expression; } } public iqueryprovider provider { { return proxydbprovider; } } public override string tostring() { return basequeryable.tostring(); } public idbasyncenumerator<telement> getasyncenumerator() { var asyncenumerator = basequeryable idbasyncenumerable<telement>; if (asyncenumerator == null) { throw new notsupportedexception(); } return asyncenumerator.getasyncenumerator(); } idbasyncenumerator idbasyncenumerable.getasyncenumerator() { var asyncenumerator = basequeryable idbasyncenumerable; if (asyncenumerator == null) { throw new notsupportedexception(); } return asyncenumerator.getasyncenumerator(); } } }
an example of manipulator of expression
s (this 1 transform .where(x => something)
.where(x => && something)
:
namespace { using system.linq; using system.linq.expressions; public class myexpressionmanipulator : expressionvisitor { protected override expression visitmethodcall(methodcallexpression node) { if (node.method.declaringtype == typeof(queryable) && node.method.name == "where" && node.arguments.count == 2) { // transforms .where(x => something) in // .where(x => && something) if (node.arguments[1].nodetype == expressiontype.quote) { unaryexpression argument1 = (unaryexpression)node.arguments[1]; // expression.quote if (argument1.operand.nodetype == expressiontype.lambda) { lambdaexpression argument1lambda = (lambdaexpression)argument1.operand; // important: @ each step you'll reevalute // full expression! try not replace twice // expression! // if have query like: // var res = ctx.where(x => true).where(x => true).select(x => 1) // first time you'll visit // ctx.where(x => true) // , you'll obtain // ctx.where(x => true && true) // second time you'll visit // ctx.where(x => true && true).where(x => true) // , want obtain // ctx.where(x => true && true).where(x => true && true) // , not // ctx.where(x => (true && true) && (true && true)).where(x => true && true) if (argument1lambda.body.nodetype != expressiontype.andalso) { var arguments = new expression[node.arguments.count]; node.arguments.copyto(arguments, 0); arguments[1] = expression.quote(expression.lambda(expression.andalso(argument1lambda.body, argument1lambda.body), argument1lambda.parameters)); methodcallexpression node2 = expression.call(node.object, node.method, arguments); node = node2; } } } } return base.visitmethodcall(node); } } }
now... how use it? best way derive context (in case model1) not dbcontext
proxydbcontext
, this:
public partial class model1 : proxydbcontext { public model1() : base("name=model1", manipulate) { } /// <summary> /// /// </summary> /// <param name="executing">true: returned expression executed directly, false: returned expression returned iqueryable<>.</param> /// <param name="expression"></param> /// <returns></returns> private static expression manipulate(bool executing, expression expression) { // see annotation reexecuting same visitor // multiple times in myexpressionmanipulator().visit . // executing visitor on executing == true, // , return expression; on executing == false, // have guarantee expression won't // manipulated multiple times. // written now, expression manipulated // multiple times. return new myexpressionmanipulator().visit(expression); } // tables public virtual dbset<parent> parent { get; set; } public virtual idbset<child> child { get; set; }
then transparent:
// model1: class model1 : proxydbcontext {} using (var ctx = new model1()) { // query var res = ctx.parent.where(x => x.id > 100); // query automatically manipulated manipulate method }
another way without subclassing proxydbcontext
:
// model1: class model1 : proxydbcontext {} using (var ctx = new model1()) { func<expression, expression> manipulator = new myexpressionmanipulator().visit; ctx.parent = new proxydbset<parent>(ctx.parent, manipulator); ctx.child = new proxydbset<child>(ctx.child, manipulator); // query var res = ctx.parent.where(x => x.id > 100); }
the proxydbcontext<>
replaces dbset<>
/idbset<>
present in context proxydbset<>
.
in second example action done explicitly, note taht can create method it, or create factory context (a static method returns context various dbset<>
"proxied"), or put proxification in constructor of context (because "original" initialization of dbset<>
happens in constructor of dbcontext
, , body of constructor of context executed after this), or create multiple subclasses of context, each 1 has constructor proxifies in different way...
note first method (subclassing proxydbcontext<>
) "fixes" set<>
/set
methods otherwise you'll have fix copying code of overloads of these 2 methodsd proxydbcontext<>
.
Comments
Post a Comment