How to create lambda expressions for every property of a class - c#

I have a following problem. I have to iterate over all the properties of the class to configure some builder. A class has a lot of properties, so the code is cumbersome. It looks like this:
var b = builder<MyTypeWith1000Properties>
.WithProperty(x=>x.Property1)
.WithProperty(x=>x.Property2)
...
.WithProperty(x=>x.Property1000);
The code is repeated in many places for many differend types, not only MyTypeWith1000Properties. I was thinking about creating some extension, like this:
var b = builder<MyTypeWith1000Properties>
.WithAllProperties();
and then in the WithAllProperties I could iterate over type properties using Reflection, like this:
public static IDataExtractor<T> WithAllProperties(this IDataExtractor<T> extractor)
{
var properties = typeof(T).GetProperties();
foreach (var property in properties)
{
extractor = extractor.WithProperty(/*the problem is here/*);
}
return extractor;
}
How to convert the property variable in the loop to a corresponding expression
Expression<Func<TRow, TValue>> propertyExpression
as this is what WithProperty expects

Update - Setting the correct parameter value in Expression.Lambda>
You could try something like this
public static class BuilderExtension
{
public static IDataExtractor<T> WithAllProperties<T>(this IDataExtractor<T> extractor)
{
var properties = typeof(T).GetProperties();
foreach (var propertyInfo in properties)
{
var parameter = Expression.Parameter(typeof(T), "x");
var property = Expression.Property(parameter, propertyInfo);
var lambda = Expression.Lambda<Func<T, object>>(property,parameter);
extractor = extractor.WithProperty(lambda);
}
return extractor;
}
}
Suppose you have the following class structures
public class MyTypeWith100Properties
{
public string Property1 { get; set; }
public string Property2 { get; set; }
public string Property3 { get; set; }
public string Property4 { get; set; }
}
public interface IDataExtractor<T>
{
IDataExtractor<T> WithProperty(Expression<Func<T, object>> expr);
}
public class DataExtractor<T> : IDataExtractor<T>
{
public List<Expression<Func<T, object>>> Expressions { get; private set; }
public DataExtractor() {
Expressions = new List<Expression<Func<T, object>>>();
}
public IDataExtractor<T> WithProperty(Expression<Func<T, object>> expr)
{
Expressions.Add(expr);
return this;
}
}
Then if you run this
var builder = new DataExtractor<MyTypeWith100Properties>();
var b = builder.WithAllProperties<MyTypeWith100Properties>()
as DataExtractor<MyTypeWith100Properties>;
var instance = new MyTypeWith100Properties() {
Property1 = "This is property 1",
Property2 = "This is property 2",
Property3 = "This is property 3",
Property4 = "This is property 4"
};
foreach (var current in b.Expressions)
{
var compiled = current.Compile();
var result = compiled.Invoke(instance);
Console.WriteLine(result);
}
Your output will be
This is property 1
This is property 2
This is property 3
This is property 4

WithProperty is a generic method that takes a type parameter implied by the result type of the property member access expression, which is what TValue represents in its declaration. Since you want to use reflection to generate the lambdas, you have to do the WithProperty call dynamically as well so you can call the one with the proper type (e.g. WithProperty<String> for a String property).
Here is an extension method that generates a lambda that consists of all the chained WithProperty calls for all the properties in a class, and then compiles and calls the lambda on the IDataExtractor. I chained all the calls together and then compiled because there might be some overhead for compiling, so I didn't want to compile and call code for each property individually.
public static class IDataExtractorExt {
public static IDataExtractor<TRow> WithAllProperties<TRow>(this IDataExtractor<TRow> extractor) {
var p = Expression.Parameter(typeof(IDataExtractor<TRow>), "p"); // final lambda parameter
Expression ansBody = p; // start with p => p
var withPropGenericMI = typeof(IDataExtractor<TRow>).GetMethod("WithProperty"); // lookup WithProperty<> generic method
var properties = typeof(TRow).GetProperties();
foreach (var property in properties) {
var withPropMI = withPropGenericMI.MakeGenericMethod(property.PropertyType); // instantiate generic WithProperty<> to property type
var pxp = Expression.Parameter(typeof(TRow), "x"); // property accessor lambda parameter
var pxb = Expression.PropertyOrField(pxp, property.Name); // property accessor expression x.property
Expression propExpr = Expression.Lambda(pxb, pxp); // x => x.property
ansBody = Expression.Call(ansBody, withPropMI, propExpr); // build up p => p.WithProperty(x => x.property)...
}
return ((IDataExtractor<TRow>)(Expression.Lambda(ansBody, p).Compile().DynamicInvoke(extractor)));
}
}

Related

Building expression for all properties of class

Not a lot of experience with Expressions, and having a hard time understanding the bigger picture.
I have a class which defines a large amount of properties.
Instead of doing a lot of dumb type work, I am trying to use reflection/expressions to evaluate these properties for me.
A short sample of the class:
[Function(Name = "sensors")]
internal class Sensors
{
[CabinetDoubleSensor(SensorType = SensorType.Temperature, Precision = 3)]
[JsonProperty(PropertyName = "IO_PCW_FL_SPR")]
public JsonSensor<double> IOPcwFlSpr { get; set; } = new JsonSensor<double>();
[CabinetDoubleSensor(SensorType = SensorType.Temperature, Precision = 3)]
[JsonProperty(PropertyName = "IO_PCW_RL_SPR")]
public JsonSensor<double> IOPcwRlSpr { get; set; } = new JsonSensor<double>();
// 100+ sensor definitions below
}
I want to evaluate all properties and store them in a list as follows:
public IEnumerable<ISensor> UpdateSensors(Json.Sensors jsonUpdate)
{
// To test if it works, but clearly, I do NOT want to list all sensor evaluations here!
UpdateSensor(() => jsonUpdate.IOPcwFlSpr);
return SensorMap.Values.ToList();
}
private void UpdateSensor(Expression<Func<JsonSensor<double>>> propertySelector)
{
if (propertySelector.Body is MemberExpression expression)
{
var compiledExpression = propertySelector.Compile();
var jsonSensor = compiledExpression.Invoke();
var name = expression.Member.Name;
var cabinetSensor = SensorMap[name];
cabinetSensor.Value = jsonSensor.Value.ToString($"F{cabinetSensor.Precision}");
}
}
So then the part where I am stuck. As said, I don't want to call UpdateSensor(() => jsonUpdate.SensorName 100+ times. So I am trying to find a way to construct that lambda expression myself.
private static readonly List<PropertyInfo> Properties;
static SensorFactory()
{
Properties = typeof(Json.Sensors).GetProperties().ToList();
}
public IEnumerable<ISensor> Test(Json.Sensors jsonUpdate)
{
foreach (var property in Properties)
{
var getterMethodInfo = property.GetGetMethod();
var parameterExpression = Expression.Parameter(jsonUpdate.GetType());
var getterCall = Expression.Call(parameterExpression, getterMethodInfo);
UnaryExpression castToObject = Expression.Convert(getterCall, typeof(JsonSensor<double>));
var lambda = (Expression<Func<JsonSensor<double>>>)Expression.Lambda(castToObject, parameterExpression);
UpdateSensor(lambda);
}
// not relevant
return null;
}
The cast is illegal:
System.InvalidCastException: 'Unable to cast object of type
'System.Linq.Expressions.Expression1[System.Func2[Asml.Mbi.FlowAndTemperature.Peripherals.Cabinet.Json.Sensors,Asml.Mbi.FlowAndTemperature.Peripherals.Cabinet.Json.JsonSensor1[System.Double]]]'
to type
'System.Linq.Expressions.Expression1[System.Func1[Asml.Mbi.FlowAndTemperature.Peripherals.Cabinet.Json.JsonSensor1[System.Double]]]'.'
I think (/hope) I am close, but I don't know how to get the Expression<Func<JsonSensor<double>>> as return value.
Actually there are few problems in your code.
Exception itself is thrown, because you provide one parameter to Lambda method, and this way it will produce Func<T1, T2>. Func<T> doesn't accept any parameter, so you should call Expression.Lambda(castToObject).
Regardless, you should probably change it to Func<Sensors, JsonSensor<double>>, otherwise you'll need to wrap jsonUpdate as constant inside lambda.
Here is example of adjusted UpdateSensor and Test methods:
private static void UpdateSensor(Sensors jsonUpdate, Expression<Func<Sensors, JsonSensor<double>>> propertySelector)
{
if (propertySelector.Body is MemberExpression expression)
{
var compiledExpression = propertySelector.Compile();
// Signature was changed and jsonUpdate is not compiled into lambda; we need to pass reference
var jsonSensor = compiledExpression.Invoke(jsonUpdate);
var name = expression.Member.Name;
var cabinetSensor = SensorMap[name];
cabinetSensor.Value = jsonSensor.Value.ToString($"F{cabinetSensor.Precision}");
}
}
public IEnumerable<Sensor> Test(Sensors jsonUpdate)
{
foreach (var property in Properties)
{
var parameterExpression = Expression.Parameter(jsonUpdate.GetType());
// You don't need call or GetMethod, you need to access Property
var propertyCall = Expression.Property(parameterExpression, property);
// Cast is redundant, and if you add it UpdateSensor will do nothing
// UnaryExpression castToObject = Expression.Convert(propertyCall, typeof(JsonSensor<double>));
var lambda = Expression.Lambda<Func<Sensors, JsonSensor<double>>>(propertyCall, parameterExpression);
UpdateSensor(jsonUpdate, lambda);
}
// not relevant
return null;
}

Expression pointing to property magically has a Convert

I'm writing a dynamic filter for my controllers, where I White list expressions to provide an interface to filter. I have a metadata service which stores the white listed properties in an Dictionary like so:
public class PropertyMetadata<TEntity>
{
public Expression<Func<TEntity, object>> Expression;
public PropertyType PropertyType;
}
public class EntityMetadataService<TEntity> : IEntityMetadataService<TEntity>
{
public Dictionary<string, PropertyMetadata<TEntity>> PropertyMap = new Dictionary<string, PropertyMetadata<TEntity>>();
//Unrelavent information Omitted
}
I'm registering my metadata dictionary like so:
public static Dictionary<string, PropertyMetadata<ServiceRequest>> ServiceRequestMap
{
get
{
return new Dictionary<string, PropertyMetadata<ServiceRequest>> {
{ nameof(ServiceRequest.Id) , new PropertyMetadata<ServiceRequest> { PropertyType = PropertyType.Integer, Expression = x => x.Id } },
{ nameof(ServiceRequest.ConversationId) , new PropertyMetadata<ServiceRequest> { PropertyType = PropertyType.Integer, Expression = x => x.ConversationId } }
};
}
}
public class ServiceRequest
{
public int Id { get; set; }
public int ConversationId { get; set; }
}
When I am dynamically building my query, my property now magically has transformed to have a convert:
var binaryExpression = expressionBuilder(propertyMetadata.Expression.Body, constant);
My property Expression body now contains a convert for some reason
Is there something that I'm missing about these expressions, when I use a string property there is no convert there so why is it appearing for my Integer Properties?
In case anyone was wondering, the reason for this is because string types are already objects, while Int are value types and need to be casted to object
Since my expression is:
public Expression<Func<TEntity, object>> Expression;
When I Initialize a PropertyMap Expression to a string property
Expression = x => x.Name;
The representation would remain while Assigning it to a int property creates an cast automatically
Expression = x => x.Id
is really doing something more like this
Expression = x => (object)x.Id

Iterate through the properties of a lambda expression?

How can I iterate through an expression and change the property names based on a custom attribute that I decorated them with?
I use the following code to get the custom attribute of a property, but it works for a simple expression with one property:
var comparison = predicate.Body as BinaryExpression;
var member = (comparison.Left.NodeType == ExpressionType.Convert ?
((UnaryExpression)comparison.Left).Operand :
comparison.Left) as MemberExpression;
var value = comparison.Right as ConstantExpression;
var attribute = Attribute.GetCustomAttribute(member.Member, typeof(MyAttribute)) as MyAttribute;
var columnName = attribute.Name ?? member.Member.Name;
var columnValue = value.Value;
EDIT
Deriving from ExpressionVisitor, I can change the property name by overriding the method VisitMember.
Is this the only place where a property name is used to build the expression?
You can implement a System.Linq.Expressions.ExpressionVisitor to rewrite MemberExpression with the new mapped property. And yes VisitMember is the only place you have to implement this remapping, this is one of the advantages to expression trees and visitors. The only weird case you have to deal with is if the property's type is different to the type of the mapped property.
void Main()
{
var data = new List<TestClass>();
data.Add(new TestClass()
{
FirstName = "First",
LastName = "Last",
});
var q = data.AsQueryable().Select(x => x.FirstName);
var vistor = new MyRewriter();
var newExpression = vistor.Visit(q.Expression);
var output = newExpression.ToString();
//System.Collections.Generic.List`1[UserQuery+TestClass].Select(x => x.LastName)
}
class TestClass
{
[MyAttribute(nameof(LastName))]
public string FirstName { get; set; }
public string LastName { get; set; }
}
class MyAttribute : Attribute
{
public string MapTo { get; }
public MyAttribute(string mapTo)
{
MapTo = mapTo;
}
}
class MyRewriter : ExpressionVisitor
{
protected override Expression VisitMember(System.Linq.Expressions.MemberExpression node)
{
var att = node.Member.GetCustomAttribute<MyAttribute>();
if (att != null)
{
var newMember = node.Expression.Type.GetProperty(att.MapTo);
if (newMember != null)
{
return Expression.Property(
Visit(node.Expression), // Its very important to remember to visit the inner expression
newMember);
}
}
return base.VisitMember(node);
}
}
You can run this in LinqPad to test it. This code assumes that you are mapping to a property.

Generic method to filter a list object

I am trying to create a generic method that takes three parameters.
1) List collection
2) String PropertyName
3) String FilterString
The idea is we pass a collection of Objects, the name of the property of the object
and a Filter Criteria and it returns back a list of Objects where the property contains
the FilterString.
Also, the PropertyName is optional so if its not supplied I would like to return
all objects that contain the FilterString in any property.
Any pointers on this would be really helpful.
I am trying to have a method signature like this:
public static List FilterList(List collection, String FilterString, String Property = "")
This way I can call this method from anywhere and pass it any List and it would return me a filtered list.
You could do what you want using LINQ, as such,
var collection = ...
var filteredCollection =
collection.Where(item => item.Property == "something").ToList();
Otherwise, you could try Reflection,
public List<T> Filter<T>(
List<T> collection,
string property,
string filterValue)
{
var filteredCollection = new List<T>();
foreach (var item in collection)
{
// To check multiple properties use,
// item.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)
var propertyInfo =
item.GetType()
.GetProperty(property, BindingFlags.Public | BindingFlags.Instance);
if (propertyInfo == null)
throw new NotSupportedException("property given does not exists");
var propertyValue = propertyInfo.GetValue(item, null);
if (propertyValue == filterValue)
filteredCollection.Add(item);
}
return filteredCollection;
}
The problem with this solution is that changes to the name of the property or misspellings result in a runtime error, rather than a compilation error as would be using an actual property expression where the name is hard-typed.
Also, do note that based on the binding flags, this will work only on public, non-static properties. You can modify such behavior by passing different flags.
You can use a combination of reflection and dynamic expressions for this. I've put together a sample that might look a bit long at the first look. However, it matches your requirements and addresses these by
Using reflection to find the properties that are of type string and match the property name - if provided.
Creating an expression that calls string.Contains on all properties that have been identified. If several properties have been identified, the calls to string.Contains are combined by Or-expressions. This filter expression is compiled and handed to the Where extension method as a parameter. The provided list is filtered using the expression.
Follow this link to run the sample.
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Linq;
using System.Linq.Expressions;
public class Test
{
public static IEnumerable<T> SelectItems<T>(IEnumerable<T> items, string propName, string value)
{
IEnumerable<PropertyInfo> props;
if (!string.IsNullOrEmpty(propName))
props = new PropertyInfo[] { typeof(T).GetProperty(propName) };
else
props = typeof(T).GetProperties();
props = props.Where(x => x != null && x.PropertyType == typeof(string));
Expression lastExpr = null;
ParameterExpression paramExpr = Expression.Parameter(typeof(T), "x");
ConstantExpression valueExpr = Expression.Constant(value);
foreach(var prop in props)
{
var propExpr = GetPropertyExpression(prop, paramExpr, valueExpr);
if (lastExpr == null)
lastExpr = propExpr;
else
lastExpr = Expression.MakeBinary(ExpressionType.Or, lastExpr, propExpr);
}
if (lastExpr == null)
return new T[] {};
var filterExpr = Expression.Lambda(lastExpr, paramExpr);
return items.Where<T>((Func<T, bool>) filterExpr.Compile());
}
private static Expression GetPropertyExpression(PropertyInfo prop, ParameterExpression paramExpr, ConstantExpression valueExpr)
{
var memberAcc = Expression.MakeMemberAccess(paramExpr, prop);
var containsMember = typeof(string).GetMethod("Contains");
return Expression.Call(memberAcc, containsMember, valueExpr);
}
class TestClass
{
public string SomeProp { get; set; }
public string SomeOtherProp { get; set; }
}
public static void Main()
{
var data = new TestClass[] {
new TestClass() { SomeProp = "AAA", SomeOtherProp = "BBB" },
new TestClass() { SomeProp = "BBB", SomeOtherProp = "CCC" },
new TestClass() { SomeProp = "CCC", SomeOtherProp = "AAA" },
};
var result = SelectItems(data, "", "A");
foreach(var item in result)
Console.WriteLine(item.SomeProp);
}
}
In contrast to a completely reflection based approach, this one assembles the filter expression only once and compiles it, so that I'd expect a (small) performance improvement.
You should use Dynamic LINQ, for example, given SomeClass:
public class SomeClass
{
public int SomeField { get; set; }
}
List<SomeClass> list = new List<SomeClass>() { new SomeClass() { SomeField = 2 } };
and then:
var temp = list.AsQueryable().Where("SomeField == 1").Select("it");
var result= temp .Cast<SomeClass>().ToList();
So your function would be even simpler, with property name and filter merged into one parameter:
public List<T> Filter<T>(List<T> list, string filter)
{
var temp = list.AsQueryable().Where(filter).Select("it");
return temp.Cast<T>().ToList();
}
and you can provide different filters, like for example "SomeField > 4 && SomeField < 10" etc.
When using Markus solution it'll only work when all String properties are not null.
To ensure you could do this:
class TestClass
{
private string _someProp { get; set; }
public string SomeProp {
get
{
if(string.IsNullOrEmpty(_someProp)
{
_someProp = "";
}
return _someProp;
}
set
{
_someProp = value;
}
}
private string _someOtherProp { get; set; }
public string SomeOtherProp {
get
{
if(string.IsNullOrEmpty(_someOtherProp)
{
_someOtherProp = "";
}
return _someOtherProp;
}
set
{
_someOtherProp = value;
}
}
}
Since my rep is less then 50 I cannot comment;)

Can I make the following IQueryable linq statment generic

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).

Categories

Resources