I want to reflect model classes of a MVC project by its name which I have in a string variable refName. Currently I am using switch case to use those class as template i.e <T>. How can we do this part better, so that if a new class comes in then I don't want to insert a new case in this switch statement.
Basically, what I want to do is to collect data from cosmos db into a specific template class format according to a field gRefType. Here's what I have done:
IEnumerable<Object> itemsRefDetail;
switch (refName)
{
case "AMCaseStatus":
itemsRefDetail = await DocumentDBRepository<AMCaseStatus>.GetItemRefDetailAsync(p => p.GrefType == refName && p.Tag == tag, collectionId);
break;
case "AMcaseSubStatus":
itemsRefDetail = await DocumentDBRepository<AMcaseSubStatus>.GetItemRefDetailAsync(p => p.GrefType == refName && p.Tag == tag, collectionId);
break;
case "AMRole":
itemsRefDetail = await DocumentDBRepository<AMRole>.GetItemRefDetailAsync(p => p.GrefType == refName && p.Tag == tag, collectionId);
break;
}
As you can see in the above code, Template class used in each case is as same as case value. All the classes have both the properties(GrefType and Tag) in common.
Here is the DocumentDbRepository class:
public static class DocumentDBRepository<T> where T : class
{
public static async Task<IEnumerable<T>> GetItemRefDetailAsync(Expression<Func<T, bool>> predicate, string collectionId)
{
IDocumentQuery<T> query = client.CreateDocumentQuery<T>(
UriFactory.CreateDocumentCollectionUri(DatabaseId, collectionId))
.Where(predicate)
.AsDocumentQuery();
FeedResponse<T> privilegeQueryResponse = await query.ExecuteNextAsync<T>();
return privilegeQueryResponse;
}
}
I made an example of how to reflect generic types, The only thing you should do is, wrap up the GrefType and Tag into a baseclass/interface. This might give you a startup:
The BaseType is the basetype of your data, which contains the fields you can use in your lambda function.
Data is your data service (client.CreateDocumentQuery), in this case some test data.
DocumentDBRepository contains the static SelectData method, which executes the lambda function passed as parameter.
MyLookupThing is derived from the BaseType, used in reflection.
TestReflection does the reflection and executes it.
Here's the code:
// The basetype of an item (which contains de RefName etc)
public class BaseType
{
public string RefName { get; set; }
public string Tag { get; set; }
}
// static T service with some testdata (mock)
public static class Data<T> where T : BaseType
{
public static List<T> MyData { get; } = new List<T>();
static Data()
{
// create an item
var item = Activator.CreateInstance<T>();
item.RefName = "Test";
item.Tag = "Bla";
MyData.Add(item);
var item2 = Activator.CreateInstance<T>();
item2.RefName = "SomethingElse";
item2.Tag = "TagThing";
MyData.Add(item2);
var item3 = Activator.CreateInstance<T>();
item3.RefName = "Test2";
item3.Tag = "Bla2";
MyData.Add(item3);
}
}
// the generic class which uses the basetype as generic
public static class DocumentDBRepository<T> where T : BaseType
{
public static IEnumerable<T> SelectData(Func<T, bool> predicate)
{
// some static test data
return Data<T>.MyData.Where(predicate);
}
}
// your derived class from BaseType
public class MyLookupThing : BaseType
{
}
class TestReflection
{
public TestReflection()
{
// you can create more classes derived from BaseType
var typeStr = "TestRef.MyLookupThing";
// resolve the type:
var lookupType = (from ass in AppDomain.CurrentDomain.GetAssemblies()
from t in ass.GetTypes()
where t.FullName == typeStr
select t).First();
// get the type of the generic class
var myType = typeof(DocumentDBRepository<>);
// create a generic type
var myGenericType = myType.MakeGenericType(lookupType);
var method = myGenericType.GetMethod("SelectData", BindingFlags.Static | BindingFlags.Public);
// Create the function (with the BaseType)
var func = new Func<BaseType, bool>(item => item.RefName.StartsWith("Test"));
// invoke the method of the generic class
IEnumerable<BaseType> result = (IEnumerable<BaseType>)method.Invoke(null, new object[] { func });
// show the results
foreach (var item in result)
Console.WriteLine(item.RefName);
Console.ReadKey();
}
}
Which will give:
Test
Test2
as result
Here is how you can create this expression that limits the query to the type:
internal static Expression<Func<TEntity, bool>> CreateTypeSpesificExpression<TEntity>(string refName) where TEntity : class
{
var parameter = Expression.Parameter(typeof(IAmObject));
var member = Expression.Property(parameter, nameof(IAmObject.GrefType));
var contant = Expression.Constant(refName);
var body = Expression.Equal(member, contant);
var extra = Expression.Lambda<Func<TEntity, bool>>(body, parameter);
return extra;
}
This is all assuming that your AM classes have the same interface.
Once you do that you can simple add this in your where clause.
Your query should look like this:
IEnumerable<Object> itemsRefDetail = await DocumentDBRepository<AMCaseStatus>.GetItemRefDetailAsync(p => CreateTypeSpesificExpression(refName) && p.Tag == tag, collectionId);
Also something worth pointing out is that Cosmonaut already has native support for type based collection sharing.
Related
I found this interesting article Reflection Performance - Create Delegate (Properties C#)
the described approach works great for properties. So I tried to to make it work for Methods, too, but without success.
Classes / Properties / Methods
public class bmecatContent
{
private bmecatHeader header;
private bmecatCatalog catalog;
private List<bmecatFieldValue> fieldValueList;
public bmecatContent()
{
header = new bmecatHeader();
catalog = new bmecatCatalog();
}
public string DeclarationVersion { get; set; }
public string DeclarationEncoding { get; set; }
public string BmecatVersion { get; set; }
public bmecatHeader Header
{ get { return header; } }
public bmecatCatalog Catalog
{ get { return catalog; } }
}
public class bmecatCatalog
{
private List<bmecatCatalogGroupSystem> catalogGroupSystem;
private List<bmecatClassificationSystem> classificationSystem;
private List<bmecatProduct> products;
private List<bmecatProductToCataloggroupMap> productToCataloggroupMap;
public bmecatCatalog()
{
catalogGroupSystem = new List<bmecatCatalogGroupSystem>();
classificationSystem = new List<bmecatClassificationSystem>();
products = new List<bmecatProduct>();
productToCataloggroupMap = new List<bmecatProductToCataloggroupMap>();
}
public List<bmecatClassificationSystem> Classification_System
{ get { return classificationSystem; } }
public List<bmecatCatalogGroupSystem> Catalog_Group_System
{ get { return catalogGroupSystem; } }
public List<bmecatProduct> Products
{ get { return products; } }
public List<bmecatProductToCataloggroupMap> Product_To_Cataloggroup_Map
{ get { return productToCataloggroupMap; } }
public bmecatProduct GetProductByInernationalPid(string Pid)
{
// linq
var query = from prodItem in products
from innerList in prodItem.Product_Details.International_PID
where innerList.PID == Pid
select prodItem;
return query.FirstOrDefault();
}
}
my current Approach looks like:
// Properties
public static Func<object, object> BuildGetAccessor(MethodInfo method)
{
var obj = Expression.Parameter(typeof(object), "o");
Expression<Func<object, object>> expr =
Expression.Lambda<Func<object, object>>(
Expression.Convert(
Expression.Call(
Expression.Convert(obj, method.DeclaringType),
method),
typeof(object)),
obj);
return expr.Compile();
}
// Methods (with string Parameter)
public static Func<object, string, object> BuildMethodAccessor(MethodInfo method)
{
var obj = Expression.Parameter(typeof(object), "o");
var strParam = Expression.Parameter(typeof(string), "strParam");
//var param = method.GetParameters().Select(p => Expression.Parameter(p.ParameterType, p.Name)).FirstOrDefault();
var param = Expression.Convert(strParam, method.GetParameters().First().ParameterType);
Expression<Func<object, string, object>> expr =
Expression.Lambda<Func<object, string, object>>(
Expression.Convert(Expression.Call(Expression.Convert(obj, method.DeclaringType), method, param),
typeof(object)),
obj);
return expr.Compile();
}
this code generates messages, that for the lambda-declaration a wrong number of Parameters was used.
thx a lot for your help!
// Update
this is my "work in progress" part when it Comes to creating & using the delegates:
bmecatParser parser = new bmecatParser();
// parser contains Property BmecatContent of type bmecatContent
// BmecatContent contains all properties and Methods I Need to Access at runtime
// e.g. BmecatContent.Catalog, BmecatContent.Catalog.GetProductByInernationalPid(string Pid)
// gets instance of main-class
var property = parser.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance).Single(obj => obj.Name == "BmecatContent");
var access = Extensions.BuildGetAccessor(property.GetGetMethod());
var resultBmecatContent = access(parser);
// gets instance of class that holds method
property = resultBmecatContent.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance).Single(obj => obj.Name == "Catalog");
access = Extensions.BuildGetAccessor(property.GetGetMethod());
var resultCatalog = access(resultBmecatContent);
// here I try to get value from method that has 1 Parameter (string)
var method = resultCatalog.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance).Single(obj => obj.Name == "GetProductByInernationalPid");
var accessProd = Extensions.BuildMethodAccessor(method);
var resultProduct = accessProd(resultCatalog, "4317784548366");
the idea behind this is to parse given classes + properties structure, where user provides propertynames / methodnames within mappinginstructions.
Check this fiddle for the error: https://dotnetfiddle.net/tlz4Qg
I have two classes like this:
public class ParentType{
private ParentType(){}
public int Id { get; protected set; }
public SubType Sub { get; protected set; }
}
public class SubType{
private SubType(){}
public int Id { get; protected set; }
}
I am going to transform a multilevel anonymous expression to a multilevel non-anonymous expression. To achieve this I have an expression like the below-mentioned one:
x => new
{
x.Id,
Sub = new
{
x.Sub.Id
}
}
To achieve that goal, I have transformed it to an expression like this:
x => new ParentType()
{
Id = x.Id,
Sub = new SubType()
{
Id = x.Sub.Id
},
}
But when I call Compile() method, I get the following error:
Variable 'x.Sub' of type 'SubType' referenced from scope '' but it is not defined
Here is my visitor class:
public class ReturnTypeVisitor<TIn, TOut> : ExpressionVisitor
{
private readonly Type funcToReplace;
private ParameterExpression currentParameter;
private ParameterExpression defaultParameter;
private Type currentType;
public ReturnTypeVisitor() => funcToReplace = typeof(Func<,>).MakeGenericType(typeof(TIn), typeof(object));
protected override Expression VisitNew(NewExpression node)
{
if (!node.Type.IsAnonymousType())
return base.VisitNew(node);
if (currentType == null)
currentType = typeof(TOut);
var ctor = currentType.GetPrivateConstructor();
if (ctor == null)
return base.VisitNew(node);
NewExpression expr = Expression.New(ctor);
IEnumerable<MemberBinding> bindings = node.Members.Select(x =>
{
var mi = currentType.GetProperty(x.Name);
//if the type is anonymous then I need to transform its body
if (((PropertyInfo)x).PropertyType.IsAnonymousType())
{
//This section is became unnecessary complex!
//
var property = (PropertyInfo)x;
var parentType = currentType;
var parentParameter = currentParameter;
currentType = currentType.GetProperty(property.Name).PropertyType;
currentParameter = Expression.Parameter(currentType, currentParameter.Name + "." + property.Name);
//I pass the inner anonymous expression to VisitNew and make the non-anonymous expression from it
var xOriginal = VisitNew(node.Arguments.FirstOrDefault(a => a.Type == property.PropertyType) as NewExpression);
currentType = parentType;
currentParameter = parentParameter;
return (MemberBinding)Expression.Bind(mi, xOriginal);
}
else//if type is not anonymous then simple find the property and make the memberbinding
{
var xOriginal = Expression.PropertyOrField(currentParameter, x.Name);
return (MemberBinding)Expression.Bind(mi, xOriginal);
}
});
return Expression.MemberInit(expr, bindings);
}
protected override Expression VisitLambda<T>(Expression<T> node)
{
if (typeof(T) != funcToReplace)
return base.VisitLambda(node);
defaultParameter = node.Parameters.First();
currentParameter = defaultParameter;
var body = Visit(node.Body);
return Expression.Lambda<Func<TIn, TOut>>(body, currentParameter);
}
}
And use it like this:
public static Expression<Func<Tin, Tout>> Transform<Tin, Tout>(this Expression<Func<Tin, object>> source)
{
var visitor = new ReturnTypeVisitor<Tin, Tout>();
var result = (Expression<Func<Tin, Tout>>)visitor.Visit(source);
return result;// result.Compile() throw the aforementioned error
}
Here is the extension methods used inside my Visitor class:
public static ConstructorInfo GetPrivateConstructor(this Type type) =>
type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
// this hack taken from https://stackoverflow.com/a/2483054/4685428
// and https://stackoverflow.com/a/1650895/4685428
public static bool IsAnonymousType(this Type type)
{
var markedWithAttribute = type.GetCustomAttributes(typeof(CompilerGeneratedAttribute), inherit: false).Any();
var typeName = type.Name;
return markedWithAttribute
&& (typeName.StartsWith("<>") || type.Name.StartsWith("VB$"))
&& typeName.Contains("AnonymousType");
}
Update
Here is the .Net Fiddle link for the problem: https://dotnetfiddle.net/tlz4Qg
Update
I have removed the extra codes that seems to be out of the problem scope.
The cause of the problem in question is the line
currentParameter = Expression.Parameter(currentType, currentParameter.Name + "." + property.Name);
inside VisitNew method.
With your sample, it creates a new parameter called "x.Sub", so if we mark the parameters with {}, the actual result is
Sub = new SubType()
{
Id = {x.Sub}.Id
},
rather than expected
Sub = new SubType()
{
Id = {x}.Sub.Id
},
In general you should not create new ParameterExpressions except when remapping lambda expressions. And all newly created parameters should be passed to Expression.Lambda call, otherwise they will be considered "not defined".
Also please note that the visitor code has some assumptions which doesn't hold in general. For instance
var xOriginal = Expression.PropertyOrField(currentParameter, x.Name);
won't work inside nested new, because there you need access to a member of the x parameter like x.Sub.Id rather than x.Id. Which is basically the corersonding expression from NewExpression.Arguments.
Processing nested lambda expressions or collection type members and LINQ methods with expression visitors requires much more state control. While converting simple nested anonymous new expression like in the sample does not even need a ExpressionVisitor, because it could easily be achieved with simple recursive method like this:
public static Expression<Func<Tin, Tout>> Transform<Tin, Tout>(this Expression<Func<Tin, object>> source)
{
return Expression.Lambda<Func<Tin, Tout>>(
Transform(source.Body, typeof(Tout)),
source.Parameters);
}
static Expression Transform(Expression source, Type type)
{
if (source.Type != type && source is NewExpression newExpr && newExpr.Members.Count > 0)
{
return Expression.MemberInit(Expression.New(type), newExpr.Members
.Select(m => type.GetProperty(m.Name))
.Zip(newExpr.Arguments, (m, e) => Expression.Bind(m, Transform(e, m.PropertyType))));
}
return source;
}
My repository returns entities derived from a common base class
class BaseClass
{
public int Id { get; set; }
}
class MyClass : BaseClass
{
public string Name { get; set; }
}
class MyOtherClass : BaseClass
{
...
}
in a function like this:
IQueryable<T> GetEntities<T>() where T : BaseClass
I added a method to register additional filters for specific entities as lambdas using Expression<Func<T,bool>> like this:
RegisterFilter<MyClass>(t => t.Name == "Test" );
that will be applied whenever GetEntities is called with MyClass in the type argument.
Question
How can I create an expression dynamically at runtime that wraps a type cast around the filter?
in my specific case GetEntities is called on an IQueryable<MyClass> using the BaseClass as type argument and event tough I know that the filter for MyClass needs to applied I did not find a way to do so:
IQueryable<BaseClass> src =
(new List<MyClass>
{
new MyClass { Id = 1, Name = "asdf" },
new MyClass { Id = 2, Name = "Test" }
})
.AsQueryable();
Expression<Func<MyClass, bool>> filter = o => o.Name == "Test";
// does not work (of course)
src.Where(filter);
Failed attempts
Obviously I could cast the collection back before calling Where but my attempts to do this at runtime did not work:
// HACK: don't look
var genericCast = typeof(Queryable).GetMethod("Cast").MakeGenericMethod(entityType);
var genericWhere = typeof(Queryable).GetMethods().Single(mi => mi.Name == "Where" && mi.GetParameters()[1].ParameterType.GenericTypeArguments[0].Name == "Func`2");
q = (IQueryable<T>)genericCast.Invoke(q, new object[] { genericWhere.Invoke(q, new object[] { filterExp }) });
System.InvalidOperationException: 'Late bound operations cannot be performed on types or methods for which ContainsGenericParameters is true.'
since this is also very ugly I tried to wrap my filter in cast like this:
LambdaExpression filterExp = (LambdaExpression)filter;
var call = filterExp.Compile();
Expression<Func<T, bool>> wrap = o => (bool)call.DynamicInvoke(Convert.ChangeType(o, entityType));
This works but prevents the filter to be generated into a store expression so it will be done in memory which is also undesirable.
I feel like the solution to this should be trivial but I can't seem to get it right so any help would be appreciated very much.
You can:
// Using SimpleExpressionReplacer from https://stackoverflow.com/a/29467969/613130
public static Expression<Func<BaseClass, bool>> Filter<TDerived>(Expression<Func<TDerived, bool>> test)
where TDerived : BaseClass
{
// x =>
var par = Expression.Parameter(typeof(BaseClass), "x");
// x is TDerived
var istest = Expression.TypeIs(par, typeof(TDerived));
// (TDerived)x
var convert = Expression.Convert(par, typeof(TDerived));
// If test is p => p.something == xxx, replace all the p with ((TDerived)x)
var test2 = new SimpleExpressionReplacer(test.Parameters[0], convert).Visit(test.Body);
// x is TDerived && test (modified to ((TDerived)x) instead of p)
var and = Expression.AndAlso(istest, test2);
// x => x is TDerived && test (modified to ((TDerived)x) instead of p)
var test3 = Expression.Lambda<Func<BaseClass, bool>>(and, par);
return test3;
}
Note that I'm using the SimpleExpressionReplacer I wrote in another answer.
Given a test like:
var exp = Filter<MyClass>(p => p.Name == "Test");
the resulting expression is:
x => x is MyClass && ((MyClass)x).Name == "Test"
I have the following class
public class MyClass
{
public bool Delete(Product product)
{
// some code.
}
}
Now I have a helper class that looks like this
public class Helper<T, TResult>
{
public Type Type;
public string Method;
public Type[] ArgTypes;
public object[] ArgValues;
public Helper(Expression<Func<T, TResult>> expression)
{
var body = (System.Linq.Expressions.MethodCallExpression)expression.Body;
this.Type = typeof(T);
this.Method = body.Method.Name;
this.ArgTypes = body.Arguments.Select(x => x.Type).ToArray();
this.ArgValues = ???
}
}
The idea ist to use this code from somewhere:
// I am returning a helper somewhere
public Helper<T> GetMethod<T>()
{
var product = GetProduct(1);
return new Helper<MyClass>(x => x.Delete(product));
}
// some other class decides, when to execute the helper
// Invoker already exists and is responsible for executing the method
// that is the main reason I don't just comile and execute my Expression
public bool ExecuteMethod<T>(Helper<T> helper)
{
var instance = new MyClass();
var Invoker = new Invoker(helper.Type, helper.Method, helper.ArgTypes, helper.ArgValues);
return (bool)Invoker.Invoke(instance);
}
The point where I am stuck is how to extract the arguments from the expression itself.
I found this way
((ConstantExpression)((MemberExpression)body.Arguments[0]).Expression).Value
which seems to be an object type with a field "product" but I believe there must be a simpler solution.
Any suggestions.
Update
Just to clarify, I modified my code according to what I want to achive. In my real word application I already have a class that does the same but without an expression tree:
var helper = new Helper(typeof(MyClass), "Delete",
new Type[] { typeof(Product) }, new object[] {product}));
The main reason for my Helper<T> is to have Compile-Time checking if the method signature is valid.
Update 2
This is my current implementation, is there a better way to acces the values, without using reflection?
public Helper(Expression<Func<T, TResult>> expression)
{
var body = (System.Linq.Expressions.MethodCallExpression)expression.Body;
this.Type = typeof(T);
this.Method = body.Method.Name;
this.ArgTypes = body.Arguments.Select(x => x.Type).ToArray();
var values = new List<object>();
foreach(var arg in body.Arguments)
{
values.Add(
(((ConstantExpression)exp.Expression).Value).GetType()
.GetField(exp.Member.Name)
.GetValue(((ConstantExpression)exp.Expression).Value);
);
}
this.ArgValues = values.ToArray();
}
This method works pretty well. It returns the argument types and values for an Expression>
private static KeyValuePair<Type, object>[] ResolveArgs<T>(Expression<Func<T, object>> expression)
{
var body = (System.Linq.Expressions.MethodCallExpression)expression.Body;
var values = new List<KeyValuePair<Type, object>>();
foreach (var argument in body.Arguments)
{
var exp = ResolveMemberExpression(argument);
var type = argument.Type;
var value = GetValue(exp);
values.Add(new KeyValuePair<Type, object>(type, value));
}
return values.ToArray();
}
public static MemberExpression ResolveMemberExpression(Expression expression)
{
if (expression is MemberExpression)
{
return (MemberExpression)expression;
}
else if (expression is UnaryExpression)
{
// if casting is involved, Expression is not x => x.FieldName but x => Convert(x.Fieldname)
return (MemberExpression)((UnaryExpression)expression).Operand;
}
else
{
throw new NotSupportedException(expression.ToString());
}
}
private static object GetValue(MemberExpression exp)
{
// expression is ConstantExpression or FieldExpression
if (exp.Expression is ConstantExpression)
{
return (((ConstantExpression)exp.Expression).Value)
.GetType()
.GetField(exp.Member.Name)
.GetValue(((ConstantExpression)exp.Expression).Value);
}
else if (exp.Expression is MemberExpression)
{
return GetValue((MemberExpression)exp.Expression);
}
else
{
throw new NotImplementedException();
}
}
You can compile the argument expression and then invoke it to calculate the value:
var values = new List<object>();
foreach(var arg in body.Arguments)
{
var value = Expression.Lambda(argument).Compile().DynamicInvoke();
values.Add(value);
}
this.ArgValues = values.ToArray();
Here is an example of creation of a delegate using a lambda. The object instance is encapsulated into the delegate using a C# feature called closure.
MyClass instance = new MyClass();
//This following line cannot be changed to var declaration
//since C# can't infer the type.
Func<Product, bool> deleteDelegate = p => instance.Delete(p);
Product product = new Product();
bool deleted = deleteDelegate(product);
Alternatively you are trying to create a Helper that automagically Currys.
public class Helper<T>
where T : new()
{
public TResult Execute<TResult>(Func<T, TResult> methodLambda)
{
var instance = new T();
return methodLamda(instance);
}
}
public void Main()
{
var helper = new Helper<MyClass>();
var product = new Product();
helper.Execute(x => x.Delete(product));
}
However I have to say this problem looks suspiciously like the creation of a Helper class to handle the lifetime of a WCF proxy....You know...just say...in which case this ISN'T how I would approach this...simply because this approach leaks WCF specific code into your domain.
Is there a way I can make the following db query builder generic?
private IQueryable<Foo> ByName(IQueryable<Foo> dbQuery, Query query)
{
string[] searchTerms = query.Data.Replace(" ","").ToLower().Split(',');
if (query.Exclude)
{
return dbQuery.Where(x => searchTerms.All(
y => y != x.Name.Replace(" ", "").ToLower()));
}
return dbQuery.Where(x => searchTerms.Any(
y => y == x.Name.Replace(" ", "").ToLower()));
}
I've got the same function for lots of different properties of Foo. ByCounty, ByTown, ByStreet etc etc.
I've written some functions that return linq before like the following
public Expression<Func<Foo, bool>> FoosAreWithinDistanceFromGeocode(
double distance, Geocode geocode)
{
double distanceSquare = distance * distance;
return foo => ( SqlFunctions.Square((double)(
foo.Address.Geocode.Easting - geocode.Easting)) +
SqlFunctions.Square((double)(fooAddress.Geocode.Northing -
geocode.Northing)) ) <= distanceSquare;
}
But I can't seem to find if the Linq-to-SQL stuff can't use generics or if it's possible to pass properties as generics and that kind of thing.
EDIT: I have this working generically for a single search term.
Where [query.Data == "Foo1"]
return dbQuery.Where(SearchMatch("Name", query.Data));
public Expression<Func<Foo, bool>> SearchMatch(string propertyName, string searchTerm)
{
var foo = Expression.Parameter(typeof(Foo), "foo");
var prop = Expression.Property(foo, propertyName);
var search = Expression.Constant(searchTerm);
var equal = Expression.Equal(prop, search);
return Expression.Lambda<Func<Foo, bool>>(equal, foo);
}
Anyone have any ideas how to make it work for an array of strings?
You need to define an interface that exposes the properties that you want to access, like so:
public interface IHaveName
{
string Name { get; }
}
Then, on your classes, you would implement the interface:
public class Foo : IHaveName
If you're using the classes generated from a DBML file, these classes are marked with the partial keyword so implementing the interface is as simple as creating a new file, and inserting:
public partial class Foo : IHaveName
Since the property is already declared as public in the other .cs file generated from the .dbml file, the interface is implemented implicitly.
Finally, you would rewrite your ByName method to take a generic type parameter with a constraint that it implement your interface IHaveName:
private IQueryable<T> ByName<T>(IQueryable<T> dbQuery, Query query)
where T : IHaveName
{
// Everything else is the same.
For your other properties (and methods which use them), you could aggregate them together into one interface, or separate them out, depending on your needs.
Based on your edit, if you want to create an expression dynamically, you don't have to give up compile-time safety:
public Expression<Func<Foo, bool>> SearchMatch(
Expression<Func<Foo, string>> property, string searchTerm)
{
var foo = Expression.Parameter(typeof(Foo), "foo");
// Get the property info from the property expression.
var prop = Expression.Property(foo,
(property.Body as MemberExpression).Member as PropertyInfo);
var search = Expression.Constant(searchTerm);
var equal = Expression.Equal(prop, search);
return Expression.Lambda<Func<Foo, bool>>(equal, foo);
}
Which you then call like so:
var expression = SearchMatch(f => f.Name, "searchTerm");
This ensures that the properties that you are passing to SearchMatch actually exist on Foo. Note if you wanted to make this generic for other scalar property types, you would do the following:
public Expression<Func<Foo, bool>> SearchMatch<T>(
Expression<Func<Foo, T>> property, T searchTerm)
{
var foo = Expression.Parameter(typeof(Foo), "foo");
// Get the property info from the property expression.
var prop = Expression.Property(foo,
(property.Body as MemberExpression).Member as PropertyInfo);
var search = Expression.Constant(searchTerm);
var equal = Expression.Equal(prop, search);
return Expression.Lambda<Func<Foo, bool>>(equal, foo);
}
If I understood what you are trying to achieve reflection might help you. At least if you play nice. Here's a simplified but working example
internal class Program
{
private class Data
{
public string Name { get; set; }
public string Address { get; set; }
public override string ToString()
{
return String.Format("My name is {0} and I'm living at {1}", Name, Address);
}
}
static Expression<Func<Data,bool>> BuildExpression(PropertyInfo prop, IQueryable<string> restrict)
{
return (data) => !restrict.Any(elem => elem == prop.GetValue(data, null));
}
static IQueryable<Data> FilterData(IQueryable<Data> input, Expression<Func<Data, bool>> filter)
{
return input.Where(filter);
}
public static void Main (string[] args)
{
List<Data> list = new List<Data>()
{
new Data {Name = "John", Address = "1st Street"},
new Data {Name = "Mary",Address = "2nd Street"},
new Data {Name = "Carl", Address = "3rd Street"}
};
var filterByNameExpression = BuildExpression(typeof (Data).GetProperty("Name"),
(new List<string> {"John", "Carl"}).AsQueryable());
var filterByAddressExpression = BuildExpression(typeof(Data).GetProperty("Address"),
(new List<string> { "2nd Street"}).AsQueryable());
IQueryable<Data> filetedByName = FilterData(list.AsQueryable(), filterByNameExpression);
IQueryable<Data> filetedByAddress = FilterData(list.AsQueryable(), filterByAddressExpression);
Console.WriteLine("Filtered by name");
foreach (var d in filetedByName)
{
Console.WriteLine(d);
}
Console.WriteLine("Filtered by address");
foreach (var d in filetedByAddress)
{
Console.WriteLine(d);
}
Console.ReadLine();
}
Hovewer, I'\m almost sure it won't work with LINQ-to-SQL. One way to workaround it is to materialize the IQueryable before passing it to such filtering methods (e.g. by calling ToList on them).