sql - C# Generic .Contains() method implementing SqlFunctions.StringConvert in Entity Framework -
i have generic method dynamically creates query in entity framework. use search function on data table headers.
the function works if entity property type/sql data type string. because of .contains() extensions.
the problem comes in when data type other string. these data types don't have .contains() extension.
i able use method across data types, , have found possibly use sqlfunctions.stringconvert. know has no option integer, , have convert integer based properties double.
i unsure how implement sqlfunctions.stringconvert generically, please see below method (you see have excluded data types have no .contains() extension):
public static iqueryable<t> filter<t>(this iqueryable<t> query, list<searchfilterdto> filters) t : baseentity { if (filters != null && filters.count > 0 && !filters.any(f => string.isnullorempty(f.filter))) { expression filterexpression = null; parameterexpression parameter = expression.parameter(query.elementtype, "item"); filterexpression = filters.select(f => { expression selector = parameter; expression pred = expression.constant(f.filter); foreach (var member in f.column.split('.')) { propertyinfo mi = selector.type.getproperty(member, bindingflags.ignorecase | bindingflags.public | bindingflags.instance); if (mi != null) { selector = expression.property(selector, mi); if (selector.type == typeof(guid) || selector.type == typeof(guid?) || selector.type == typeof(datetime) || selector.type == typeof(datetime?) || selector.type == typeof(int) || selector.type == typeof(int?) ) { return null; } } else { return null; } } expression containsmethod = expression.call(selector, "contains", null, pred); return containsmethod; }).where(r => r != null).aggregate(expression.and); lambdaexpression = expression.lambda(filterexpression, parameter); methodinfo wherecall = (typeof(queryable).getmethods().first(mi => mi.name == "where" && mi.getparameters().length == 2).makegenericmethod(query.elementtype)); methodcallexpression call = expression.call(wherecall, new expression[] { query.expression, }); return query.provider.createquery<t>(call); } return query; }
the function works if entity property type/sql data type string. because of .contains() extensions.
i mention contains in case not extension, regular string.contains method.
i able use method across data types
this not idea since non string values can have different string representations, it's not quite clear you'll searching for.
but let assume want anyway.
and have found possibly use sqlfunctions.stringconvert
there 2 drawbacks - first, sqlfunctions sqlserver specific (not dbfunctions instance), , second, stringconvert works double , decimal. imo better choice using object.tostring method supported in ef (at least in lastest ef6).
i'm going provide solution based on object.tostring(). before doing that, let me give hints when working expressions. anytime want build expression using system.linq.expressions , don't know how, can build similar sample typed expression , examine inside debugger locals/watch window. instance:
public class foo { public int bar { get; set; } } expression<func<foo, bool>> e = item => sqlfunctions.stringconvert((decimal?)item.bar).contains("1"); you can put break point , start expanding e members, members etc. , you'll see how expression has been built compiler, need find respective expression methods.
finally, here solution itself. i've included little tricks allow avoiding working directly reflection , string method names possible:
public static class queryableutils { static expression<func<t, tresult>> expr<t, tresult>(expression<func<t, tresult>> source) { return source; } static methodinfo getmethod(this lambdaexpression source) { return ((methodcallexpression)source.body).method; } static readonly methodinfo object_tostring = expr((object x) => x.tostring()).getmethod(); static readonly methodinfo string_contains = expr((string x) => x.contains("y")).getmethod(); public static iqueryable<t> filter<t>(this iqueryable<t> query, list<searchfilterdto> filters) // t : baseentity { if (filters != null && filters.count > 0 && !filters.any(f => string.isnullorempty(f.filter))) { var item = expression.parameter(query.elementtype, "item"); var body = filters.select(f => { // process member path , build final value selector expression value = item; foreach (var membername in f.column.split('.')) { var member = item.type.getproperty(membername, bindingflags.ignorecase | bindingflags.public | bindingflags.instance) ?? (memberinfo)item.type.getfield(membername, bindingflags.ignorecase | bindingflags.public | bindingflags.instance); if (member == null) return null; // should throw error? value = expression.makememberaccess(value, member); } // note: "safe" skipping invalid arguments not practice. // without requirement, above block // var value = f.column.split('.').aggregate((expression)item, expression.propertyorfield); // convert value string if needed if (value.type != typeof(string)) { // here can use different conversions based on value.type // i'll use object.tostring() value = expression.call(value, object_tostring); } // build , return call string.contains method return (expression)expression.call(value, string_contains, expression.constant(f.filter)); }) .where(r => r != null) .aggregate(expression.andalso); var predicate = expression.lambda<func<t, bool>>(body, item); query = query.where(predicate); } return query; } }
Comments
Post a Comment