Related
Consider we have this class :
public class Data
{
public string Field1 { get; set; }
public string Field2 { get; set; }
public string Field3 { get; set; }
public string Field4 { get; set; }
public string Field5 { get; set; }
}
How do I dynamically select for specify columns ? something like this :
var list = new List<Data>();
var result= list.Select("Field1,Field2"); // How ?
Is this the only solution => Dynamic LINQ ?
Selected fields are not known at compile time. They would be specified at runtime
You can do this by dynamically creating the lambda you pass to Select:
Func<Data,Data> CreateNewStatement( string fields )
{
// input parameter "o"
var xParameter = Expression.Parameter( typeof( Data ), "o" );
// new statement "new Data()"
var xNew = Expression.New( typeof( Data ) );
// create initializers
var bindings = fields.Split( ',' ).Select( o => o.Trim() )
.Select( o => {
// property "Field1"
var mi = typeof( Data ).GetProperty( o );
// original value "o.Field1"
var xOriginal = Expression.Property( xParameter, mi );
// set value "Field1 = o.Field1"
return Expression.Bind( mi, xOriginal );
}
);
// initialization "new Data { Field1 = o.Field1, Field2 = o.Field2 }"
var xInit = Expression.MemberInit( xNew, bindings );
// expression "o => new Data { Field1 = o.Field1, Field2 = o.Field2 }"
var lambda = Expression.Lambda<Func<Data,Data>>( xInit, xParameter );
// compile to Func<Data, Data>
return lambda.Compile();
}
Then you can use it like this:
var result = list.Select( CreateNewStatement( "Field1, Field2" ) );
In addition for Nicholas Butler and the hint in comment of Matt(that use T for type of input class), I put an improve to Nicholas answer that generate the property of entity dynamically and the function does not need to send field as parameter.
For Use add class as below:
public static class Helpers
{
public static Func<T, T> DynamicSelectGenerator<T>(string Fields = "")
{
string[] EntityFields;
if (Fields == "")
// get Properties of the T
EntityFields = typeof(T).GetProperties().Select(propertyInfo => propertyInfo.Name).ToArray();
else
EntityFields = Fields.Split(',');
// input parameter "o"
var xParameter = Expression.Parameter(typeof(T), "o");
// new statement "new Data()"
var xNew = Expression.New(typeof(T));
// create initializers
var bindings = EntityFields.Select(o => o.Trim())
.Select(o =>
{
// property "Field1"
var mi = typeof(T).GetProperty(o);
// original value "o.Field1"
var xOriginal = Expression.Property(xParameter, mi);
// set value "Field1 = o.Field1"
return Expression.Bind(mi, xOriginal);
}
);
// initialization "new Data { Field1 = o.Field1, Field2 = o.Field2 }"
var xInit = Expression.MemberInit(xNew, bindings);
// expression "o => new Data { Field1 = o.Field1, Field2 = o.Field2 }"
var lambda = Expression.Lambda<Func<T, T>>(xInit, xParameter);
// compile to Func<Data, Data>
return lambda.Compile();
}
}
The DynamicSelectGenerator method get entity with type T, this method have optional input parameter Fields that if you want to select special field from entity send as a string such as "Field1, Field2" and if you don't send anything to method, it returns all of the fields of entity, you could use this method as below:
using (AppDbContext db = new AppDbContext())
{
//select "Field1, Field2" from entity
var result = db.SampleEntity.Select(Helpers.DynamicSelectGenerator<SampleEntity>("Field1, Field2")).ToList();
//select all field from entity
var result1 = db.SampleEntity.Select(Helpers.DynamicSelectGenerator<SampleEntity>()).ToList();
}
(Assume that you have a DbContext with name AppDbContext and the context have an entity with name SampleEntity)
You must use reflection to get and set property value with it's name.
var result = new List<Data>();
var data = new Data();
var type = data.GetType();
var fieldName = "Something";
for (var i = 0; i < list.Count; i++)
{
foreach (var property in data.GetType().GetProperties())
{
if (property.Name == fieldName)
{
type.GetProperties().FirstOrDefault(n => n.Name == property.Name).SetValue(data, GetPropValue(list[i], property.Name), null);
result.Add(data);
}
}
}
And here is GetPropValue() method
public static object GetPropValue(object src, string propName)
{
return src.GetType().GetProperty(propName).GetValue(src, null);
}
Using Reflection and Expression bulid can do what you say.
Example:
var list = new List<Data>();
//bulid a expression tree to create a paramter
ParameterExpression param = Expression.Parameter(typeof(Data), "d");
//bulid expression tree:data.Field1
Expression selector = Expression.Property(param,typeof(Data).GetProperty("Field1"));
Expression pred = Expression.Lambda(selector, param);
//bulid expression tree:Select(d=>d.Field1)
Expression expr = Expression.Call(typeof(Queryable), "Select",
new Type[] { typeof(Data), typeof(string) },
Expression.Constant(list.AsQueryable()), pred);
//create dynamic query
IQueryable<string> query = list.AsQueryable().Provider.CreateQuery<string>(expr);
var result=query.ToList();
I writing the method in following line for you can work with nested fields taking advantage of Nicholas Butler and Ali.
You can use this method for dynamically creating to lambda for pass to select and also works for nested fields. You can also work with IQueryable cases.
/// <param name="Fields">
/// Format1: "Field1"
/// Format2: "Nested1.Field1"
/// Format3: "Field1:Field1Alias"
/// </param>
public static Expression<Func<T, TSelect>> DynamicSelectGenerator<T, TSelect>(params string[] Fields)
{
string[] EntityFields = Fields;
if (Fields == null || Fields.Length == 0)
// get Properties of the T
EntityFields = typeof(T).GetProperties().Select(propertyInfo => propertyInfo.Name).ToArray();
// input parameter "x"
var xParameter = Expression.Parameter(typeof(T), "x");
// new statement "new Data()"
var xNew = Expression.New(typeof(TSelect));
// create initializers
var bindings = EntityFields
.Select(x =>
{
string[] xFieldAlias = x.Split(":");
string field = xFieldAlias[0];
string[] fieldSplit = field.Split(".");
if (fieldSplit.Length > 1)
{
// original value "x.Nested.Field1"
Expression exp = xParameter;
foreach (string item in fieldSplit)
exp = Expression.PropertyOrField(exp, item);
// property "Field1"
PropertyInfo member2 = null;
if (xFieldAlias.Length > 1)
member2 = typeof(TSelect).GetProperty(xFieldAlias[1]);
else
member2 = typeof(T).GetProperty(fieldSplit[fieldSplit.Length - 1]);
// set value "Field1 = x.Nested.Field1"
var res = Expression.Bind(member2, exp);
return res;
}
// property "Field1"
var mi = typeof(T).GetProperty(field);
PropertyInfo member;
if (xFieldAlias.Length > 1)
member = typeof(TSelect).GetProperty(xFieldAlias[1]);
else member = typeof(TSelect).GetProperty(field);
// original value "x.Field1"
var xOriginal = Expression.Property(xParameter, mi);
// set value "Field1 = x.Field1"
return Expression.Bind(member, xOriginal);
}
);
// initialization "new Data { Field1 = x.Field1, Field2 = x.Field2 }"
var xInit = Expression.MemberInit(xNew, bindings);
// expression "x => new Data { Field1 = x.Field1, Field2 = x.Field2 }"
var lambda = Expression.Lambda<Func<T, TSelect>>(xInit, xParameter);
return lambda;
}
Usage:
var s = DynamicSelectGenerator<SalesTeam, SalesTeamSelect>(
"Name:SalesTeamName",
"Employee.FullName:SalesTeamExpert"
);
var res = _context.SalesTeam.Select(s);
public class SalesTeam
{
public string Name {get; set; }
public Guid EmployeeId { get; set; }
public Employee Employee { get; set; }
}
public class SalesTeamSelect
{
public string SalesTeamName {get; set; }
public string SalesTeamExpert {get; set; }
}
The OP mentioned Dynamic Linq library, so I'd like to lay out an explanation on its usage.
1. Dynamic Linq Built-In Select
Dynamic Linq has a built-in Select method, which can be used as follows:
var numbers = new List<int> { 1, 2, 3 };
var wrapped = numbers.Select(num => new { Value = num }).ToList();
// the "it" keyword functions as the lambda parameter,
// so essentialy it's like calling: numbers.Select(num => num)
var selectedNumbers = numbers.Select("it");
// the following is the equivalent of calling: wrapped.Select(num => num.Value)
var selectedValues = wrapped.Select("Value");
// the following is the equivalent of calling: numbers.Select(num => new { Value = num })
var selectedObjects = numbers.Select("new(it as Value)");
foreach (int num in selectedNumbers) Console.WriteLine(num);
foreach (int val in selectedValues) Console.WriteLine(val);
foreach (dynamic obj in selectedObjects) Console.WriteLine(obj.Value);
The Downside
There's somewhat a downside using the built-in Select:
Since it's an IQueryable - not IQueryable<T> - extension method, with IQueryable as its return type, common materialization methods - like ToList or FirstOrDefault - can't be used. This is why the above example uses foreach - it's simply the only convenient way of materializing the results.
So to make things more convenient, let's support these methods.
2. Supporting Select<T> in Dynamic Linq (to enable using ToList and alike)
To support Select<T>, it needs to be added into the Dynamic Linq file. The simple steps for doing that are explained in this answer and in my comment on it.
After doing so, it can be used in the following way:
var numbers = new List<int> { 1, 2, 3 };
var wrapped = numbers.Select(num => new { Value = num }).ToList();
// the following is the equivalent of calling: numbers.Select(num => num).ToList()
var selectedNumbers = numbers.Select<int>("it").ToList();
// the following is the equivalent of calling: wrapped.Select(num => num.Value).ToList()
var selectedValues = wrapped.Select<int>("Value").ToList();
// the following is the equivalent of calling: numbers.Select(num => new { Value = num }).ToList()
var selectedObjects = numbers.Select<object>("new(it as Value)").ToList();
The Downside
Arguably, this implementation introduces yet another kind of downside: By having to explicitly parameterize the Select<T> call (e.g., having to call Select<int>), we're losing the dynamic nature of the library.
Nevertheless, since we can now call any materialization Linq method, this usage may still be quite useful.
I simplified the amazing method DynamicSelectGenerator() created by Ali and made this extension method that overrides the LINQ Select() to take a column separated parameters to simplify the usage and for more readability:
public static IEnumerable<T> Select<T>(this IEnumerable<T> source, string parameters)
{
return source.Select(DynamicSelectGenerator<T>(parameters));
}
So instead of:
var query = list.Select(Helpers.DynamicSelectGenerator<Data>("Field1,Field2")).ToList();
Will be:
var query = list.Select("Field1,Field2").ToList();
Another approach I've used is a nested ternary operator:
string col = "Column3";
var query = table.Select(i => col == "Column1" ? i.Column1 :
col == "Column2" ? i.Column2 :
col == "Column3" ? i.Column3 :
col == "Column4" ? i.Column4 :
null);
The ternary operator requires that each field be the same type, so you'll need to call .ToString() on any non-string columns.
I have generate my own class for same purpose of usage.
github gist : https://gist.github.com/mstrYoda/663789375b0df23e2662a53bebaf2c7c
It generates dynamic select lambda for given string and also support for two level nested properties.
Example of usage is :
class Shipment {
// other fields...
public Address Sender;
public Address Recipient;
}
class Address {
public string AddressText;
public string CityName;
public string CityId;
}
// in the service method
var shipmentDtos = _context.Shipments.Where(s => request.ShipmentIdList.Contains(s.Id))
.Select(new SelectLambdaBuilder<Shipment>().CreateNewStatement(request.Fields)) // request.Fields = "Sender.CityName,Sender.CityId"
.ToList();
It compiles the lambda as below:
s => new Shipment {
Sender = new Address {
CityId = s.Sender.CityId,
CityName = s.Sender.CityName
}
}
You can also find my quesion and answer here :c# - Dynamically generate linq select with nested properties
public class SelectLambdaBuilder<T>
{
// as a performence consideration I cached already computed type-properties
private static Dictionary<Type, PropertyInfo[]> _typePropertyInfoMappings = new Dictionary<Type, PropertyInfo[]>();
private readonly Type _typeOfBaseClass = typeof(T);
private Dictionary<string, List<string>> GetFieldMapping(string fields)
{
var selectedFieldsMap = new Dictionary<string, List<string>>();
foreach (var s in fields.Split(','))
{
var nestedFields = s.Split('.').Select(f => f.Trim()).ToArray();
var nestedValue = nestedFields.Length > 1 ? nestedFields[1] : null;
if (selectedFieldsMap.Keys.Any(key => key == nestedFields[0]))
{
selectedFieldsMap[nestedFields[0]].Add(nestedValue);
}
else
{
selectedFieldsMap.Add(nestedFields[0], new List<string> { nestedValue });
}
}
return selectedFieldsMap;
}
public Func<T, T> CreateNewStatement(string fields)
{
ParameterExpression xParameter = Expression.Parameter(_typeOfBaseClass, "s");
NewExpression xNew = Expression.New(_typeOfBaseClass);
var selectFields = GetFieldMapping(fields);
var shpNestedPropertyBindings = new List<MemberAssignment>();
foreach (var keyValuePair in selectFields)
{
PropertyInfo[] propertyInfos;
if (!_typePropertyInfoMappings.TryGetValue(_typeOfBaseClass, out propertyInfos))
{
var properties = _typeOfBaseClass.GetProperties();
propertyInfos = properties;
_typePropertyInfoMappings.Add(_typeOfBaseClass, properties);
}
var propertyType = propertyInfos
.FirstOrDefault(p => p.Name.ToLowerInvariant().Equals(keyValuePair.Key.ToLowerInvariant()))
.PropertyType;
if (propertyType.IsClass)
{
PropertyInfo objClassPropInfo = _typeOfBaseClass.GetProperty(keyValuePair.Key);
MemberExpression objNestedMemberExpression = Expression.Property(xParameter, objClassPropInfo);
NewExpression innerObjNew = Expression.New(propertyType);
var nestedBindings = keyValuePair.Value.Select(v =>
{
PropertyInfo nestedObjPropInfo = propertyType.GetProperty(v);
MemberExpression nestedOrigin2 = Expression.Property(objNestedMemberExpression, nestedObjPropInfo);
var binding2 = Expression.Bind(nestedObjPropInfo, nestedOrigin2);
return binding2;
});
MemberInitExpression nestedInit = Expression.MemberInit(innerObjNew, nestedBindings);
shpNestedPropertyBindings.Add(Expression.Bind(objClassPropInfo, nestedInit));
}
else
{
Expression mbr = xParameter;
mbr = Expression.PropertyOrField(mbr, keyValuePair.Key);
PropertyInfo mi = _typeOfBaseClass.GetProperty( ((MemberExpression)mbr).Member.Name );
var xOriginal = Expression.Property(xParameter, mi);
shpNestedPropertyBindings.Add(Expression.Bind(mi, xOriginal));
}
}
var xInit = Expression.MemberInit(xNew, shpNestedPropertyBindings);
var lambda = Expression.Lambda<Func<T,T>>( xInit, xParameter );
return lambda.Compile();
}
Thank you #morio. Your comment about Expression<Func<T, T>> is exactly what I needed to make this work.
I do not know how to perform an anonymous projection which seems like what most want. I say I want Field1 and Field2 from Data and I get back something like: new { Field1 = o.Field1, Field2 = o.Field2 };
But I have a need similar to many where I want to plot x and y values, but don't know until run time which ones they are.
So rather than use an anonymous object, I create one that has the properties I want. In this case, X and Y.
Here are the source and target classes:
public class Source
{
public int PropertyA { get; set; }
public double PropertyB { get; set; }
public double PropertyC { get; set; }
}
public class Target
{
public double X { get; set; }
public double Y { get; set; }
}
And here is the code that does the mapping between the Source and the Target.
public static class SelectBuilder
{
/// <summary>
/// Creates a Func that can be used in a Linq Select statement that will map from the source items to a new target type.
/// Typical usage pattern is that you have an Entity that has many properties, but you want to dynamically set properties
/// on a smaller target type, AND, you don't know the mapping at compile time.
/// For example, you have an Entity that has a year and 10 properties. You want to have time (year) as the X axis, but
/// the user can chose any of the 10 properties to plot on the y axis. This would allow you to map one of the entity
/// properties to the Y value dynamically.
/// </summary>
/// <typeparam name="TSource">Type of the source, for example, and Entity Framework entity.</typeparam>
/// <typeparam name="TTarget">Type of the target, a projection of a smaller number of properties than the entity has.</typeparam>
/// <param name="propertyMappings">A list of named tuples that map the sourceProperty to the targetProperty.</param>
/// <returns>A func that can be used inside the Select.
/// So if
/// var select = SelectBuilder.GetSelectStatement<Source, Target>(propertyMappings), then
/// you can perform the select,
/// var results = items.Select(select);</returns>
public static Expression<Func<TSource, TTarget>> GetSelectStatement<TSource, TTarget>(IEnumerable<(string sourceProperty, string targetProperty)> propertyMappings)
{
// Get the source parameter, "source". This will allow the statement to be "X = source.SourceA".
// It needs to be of the source type, and the name is what will be used in the Select lambda.
var sourceParameter = Expression.Parameter(typeof(TSource), "source");
// Now define the ability to create a new Target type.
var newTarget = Expression.New(typeof(TTarget));
// Now develop the bindings or member assignments for each property.
var bindings = new List<MemberAssignment>();
foreach (var propertyMapping in propertyMappings)
{
var sourceMemberInfo = typeof(TSource).GetProperty(propertyMapping.sourceProperty);
var targetMemberInfo = typeof(TTarget).GetProperty(propertyMapping.targetProperty);
// This allows getting the value. Source parameter will provide the "source" part and sourceMemberInfo the property name.
// For example, "source.SourceA".
var sourceValue = Expression.Property(sourceParameter, sourceMemberInfo);
// Provide conversion in the event there is not a perfect match for the type.
// For example, if SourceA is int and the target X is double?, we need to convert from int to double?
var convertExpression = Expression.Convert(sourceValue, targetMemberInfo.PropertyType);
// Put together the target assignment, "X = Convert(source.SourcA, double?)" (TODO: How does the convert actually happen?)
var targetAssignment = Expression.Bind(targetMemberInfo, convertExpression);
bindings.Add(targetAssignment);
}
var memberInit = Expression.MemberInit(newTarget, bindings);
// Here if we map SourceA to X and SourceB to Y the lambda will be:
// {source => new Target() {X = Convert(source.SourceA, Nullable`1), Y = Convert(source.SourceB, Nullable`1)}}
var lambda = Expression.Lambda<Func<TSource, TTarget>>(memberInit, sourceParameter);
return lambda;//.Compile();
}
}
And finally a unit test that works.
[Fact(DisplayName = "GetSelectStatement works")]
public void Test2()
{
// Arrange
var source = new Source { PropertyA = 1, PropertyB = 2, PropertyC = 3 };
var expectedX = Convert.ToDouble(source.PropertyA);
var expectedY = Convert.ToDouble(source.PropertyB);
var items = new List<Source> { source }.AsQueryable();
// Let's map SourceA to X and SourceB to Y.
var propertyMappings = new List<(string sourceProperty, string targetProperty)>
{
("PropertyA", "X"), ("PropertyB", "Y")
//(nameof(Source.PropertyA), nameof(Target.X)),
//(nameof(Source.PropertyB), nameof(Target.Y))
};
// Act
var select = SelectBuilder.GetSelectStatement<Source, Target>(propertyMappings);
var actual = items.Select(select).First();
// Assert
actual.X.Should().Be(expectedX);
actual.Y.Should().Be(expectedY);
}
I've edited my previous answer since now I know how to convert from int to double. I've also made the unit test easier to understand.
I hope this helps others.
Using ExpandoObject you can build a dynamic objects or return the full object from the example below.
public object CreateShappedObject(object obj, List<string> lstFields)
{
if (!lstFields.Any())
{
return obj;
}
else
{
ExpandoObject objectToReturn = new ExpandoObject();
foreach (var field in lstFields)
{
var fieldValue = obj.GetType()
.GetProperty(field, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance)
.GetValue(obj, null);
((IDictionary<string, object>)objectToReturn).Add(field, fieldValue);
}
return objectToReturn;
}
}
The following is an example of how to use this from your controller.
http://localhost:12345/api/yourapi?fields=field1,field2
public IHttpActionResult Get(string fields = null)
{
try
{
List<string> lstFields = new List<string>();
if (fields != null)
{
lstFields = fields.ToLower().Split(',').ToList();
}
// Custom query
var result = db.data.Select(i => CreateShappedObject(new Data()
, lstFields)).ToList();
return Ok(result);
}
catch(Exception)
{
return InternalServerError();
}
}
var result = from g in list.AsEnumerable()
select new {F1 = g.Field1,F2 = g.Field2};
Consider we have this class :
public class Data
{
public string Field1 { get; set; }
public string Field2 { get; set; }
public string Field3 { get; set; }
public string Field4 { get; set; }
public string Field5 { get; set; }
}
How do I dynamically select for specify columns ? something like this :
var list = new List<Data>();
var result= list.Select("Field1,Field2"); // How ?
Is this the only solution => Dynamic LINQ ?
Selected fields are not known at compile time. They would be specified at runtime
You can do this by dynamically creating the lambda you pass to Select:
Func<Data,Data> CreateNewStatement( string fields )
{
// input parameter "o"
var xParameter = Expression.Parameter( typeof( Data ), "o" );
// new statement "new Data()"
var xNew = Expression.New( typeof( Data ) );
// create initializers
var bindings = fields.Split( ',' ).Select( o => o.Trim() )
.Select( o => {
// property "Field1"
var mi = typeof( Data ).GetProperty( o );
// original value "o.Field1"
var xOriginal = Expression.Property( xParameter, mi );
// set value "Field1 = o.Field1"
return Expression.Bind( mi, xOriginal );
}
);
// initialization "new Data { Field1 = o.Field1, Field2 = o.Field2 }"
var xInit = Expression.MemberInit( xNew, bindings );
// expression "o => new Data { Field1 = o.Field1, Field2 = o.Field2 }"
var lambda = Expression.Lambda<Func<Data,Data>>( xInit, xParameter );
// compile to Func<Data, Data>
return lambda.Compile();
}
Then you can use it like this:
var result = list.Select( CreateNewStatement( "Field1, Field2" ) );
In addition for Nicholas Butler and the hint in comment of Matt(that use T for type of input class), I put an improve to Nicholas answer that generate the property of entity dynamically and the function does not need to send field as parameter.
For Use add class as below:
public static class Helpers
{
public static Func<T, T> DynamicSelectGenerator<T>(string Fields = "")
{
string[] EntityFields;
if (Fields == "")
// get Properties of the T
EntityFields = typeof(T).GetProperties().Select(propertyInfo => propertyInfo.Name).ToArray();
else
EntityFields = Fields.Split(',');
// input parameter "o"
var xParameter = Expression.Parameter(typeof(T), "o");
// new statement "new Data()"
var xNew = Expression.New(typeof(T));
// create initializers
var bindings = EntityFields.Select(o => o.Trim())
.Select(o =>
{
// property "Field1"
var mi = typeof(T).GetProperty(o);
// original value "o.Field1"
var xOriginal = Expression.Property(xParameter, mi);
// set value "Field1 = o.Field1"
return Expression.Bind(mi, xOriginal);
}
);
// initialization "new Data { Field1 = o.Field1, Field2 = o.Field2 }"
var xInit = Expression.MemberInit(xNew, bindings);
// expression "o => new Data { Field1 = o.Field1, Field2 = o.Field2 }"
var lambda = Expression.Lambda<Func<T, T>>(xInit, xParameter);
// compile to Func<Data, Data>
return lambda.Compile();
}
}
The DynamicSelectGenerator method get entity with type T, this method have optional input parameter Fields that if you want to select special field from entity send as a string such as "Field1, Field2" and if you don't send anything to method, it returns all of the fields of entity, you could use this method as below:
using (AppDbContext db = new AppDbContext())
{
//select "Field1, Field2" from entity
var result = db.SampleEntity.Select(Helpers.DynamicSelectGenerator<SampleEntity>("Field1, Field2")).ToList();
//select all field from entity
var result1 = db.SampleEntity.Select(Helpers.DynamicSelectGenerator<SampleEntity>()).ToList();
}
(Assume that you have a DbContext with name AppDbContext and the context have an entity with name SampleEntity)
You must use reflection to get and set property value with it's name.
var result = new List<Data>();
var data = new Data();
var type = data.GetType();
var fieldName = "Something";
for (var i = 0; i < list.Count; i++)
{
foreach (var property in data.GetType().GetProperties())
{
if (property.Name == fieldName)
{
type.GetProperties().FirstOrDefault(n => n.Name == property.Name).SetValue(data, GetPropValue(list[i], property.Name), null);
result.Add(data);
}
}
}
And here is GetPropValue() method
public static object GetPropValue(object src, string propName)
{
return src.GetType().GetProperty(propName).GetValue(src, null);
}
Using Reflection and Expression bulid can do what you say.
Example:
var list = new List<Data>();
//bulid a expression tree to create a paramter
ParameterExpression param = Expression.Parameter(typeof(Data), "d");
//bulid expression tree:data.Field1
Expression selector = Expression.Property(param,typeof(Data).GetProperty("Field1"));
Expression pred = Expression.Lambda(selector, param);
//bulid expression tree:Select(d=>d.Field1)
Expression expr = Expression.Call(typeof(Queryable), "Select",
new Type[] { typeof(Data), typeof(string) },
Expression.Constant(list.AsQueryable()), pred);
//create dynamic query
IQueryable<string> query = list.AsQueryable().Provider.CreateQuery<string>(expr);
var result=query.ToList();
I writing the method in following line for you can work with nested fields taking advantage of Nicholas Butler and Ali.
You can use this method for dynamically creating to lambda for pass to select and also works for nested fields. You can also work with IQueryable cases.
/// <param name="Fields">
/// Format1: "Field1"
/// Format2: "Nested1.Field1"
/// Format3: "Field1:Field1Alias"
/// </param>
public static Expression<Func<T, TSelect>> DynamicSelectGenerator<T, TSelect>(params string[] Fields)
{
string[] EntityFields = Fields;
if (Fields == null || Fields.Length == 0)
// get Properties of the T
EntityFields = typeof(T).GetProperties().Select(propertyInfo => propertyInfo.Name).ToArray();
// input parameter "x"
var xParameter = Expression.Parameter(typeof(T), "x");
// new statement "new Data()"
var xNew = Expression.New(typeof(TSelect));
// create initializers
var bindings = EntityFields
.Select(x =>
{
string[] xFieldAlias = x.Split(":");
string field = xFieldAlias[0];
string[] fieldSplit = field.Split(".");
if (fieldSplit.Length > 1)
{
// original value "x.Nested.Field1"
Expression exp = xParameter;
foreach (string item in fieldSplit)
exp = Expression.PropertyOrField(exp, item);
// property "Field1"
PropertyInfo member2 = null;
if (xFieldAlias.Length > 1)
member2 = typeof(TSelect).GetProperty(xFieldAlias[1]);
else
member2 = typeof(T).GetProperty(fieldSplit[fieldSplit.Length - 1]);
// set value "Field1 = x.Nested.Field1"
var res = Expression.Bind(member2, exp);
return res;
}
// property "Field1"
var mi = typeof(T).GetProperty(field);
PropertyInfo member;
if (xFieldAlias.Length > 1)
member = typeof(TSelect).GetProperty(xFieldAlias[1]);
else member = typeof(TSelect).GetProperty(field);
// original value "x.Field1"
var xOriginal = Expression.Property(xParameter, mi);
// set value "Field1 = x.Field1"
return Expression.Bind(member, xOriginal);
}
);
// initialization "new Data { Field1 = x.Field1, Field2 = x.Field2 }"
var xInit = Expression.MemberInit(xNew, bindings);
// expression "x => new Data { Field1 = x.Field1, Field2 = x.Field2 }"
var lambda = Expression.Lambda<Func<T, TSelect>>(xInit, xParameter);
return lambda;
}
Usage:
var s = DynamicSelectGenerator<SalesTeam, SalesTeamSelect>(
"Name:SalesTeamName",
"Employee.FullName:SalesTeamExpert"
);
var res = _context.SalesTeam.Select(s);
public class SalesTeam
{
public string Name {get; set; }
public Guid EmployeeId { get; set; }
public Employee Employee { get; set; }
}
public class SalesTeamSelect
{
public string SalesTeamName {get; set; }
public string SalesTeamExpert {get; set; }
}
The OP mentioned Dynamic Linq library, so I'd like to lay out an explanation on its usage.
1. Dynamic Linq Built-In Select
Dynamic Linq has a built-in Select method, which can be used as follows:
var numbers = new List<int> { 1, 2, 3 };
var wrapped = numbers.Select(num => new { Value = num }).ToList();
// the "it" keyword functions as the lambda parameter,
// so essentialy it's like calling: numbers.Select(num => num)
var selectedNumbers = numbers.Select("it");
// the following is the equivalent of calling: wrapped.Select(num => num.Value)
var selectedValues = wrapped.Select("Value");
// the following is the equivalent of calling: numbers.Select(num => new { Value = num })
var selectedObjects = numbers.Select("new(it as Value)");
foreach (int num in selectedNumbers) Console.WriteLine(num);
foreach (int val in selectedValues) Console.WriteLine(val);
foreach (dynamic obj in selectedObjects) Console.WriteLine(obj.Value);
The Downside
There's somewhat a downside using the built-in Select:
Since it's an IQueryable - not IQueryable<T> - extension method, with IQueryable as its return type, common materialization methods - like ToList or FirstOrDefault - can't be used. This is why the above example uses foreach - it's simply the only convenient way of materializing the results.
So to make things more convenient, let's support these methods.
2. Supporting Select<T> in Dynamic Linq (to enable using ToList and alike)
To support Select<T>, it needs to be added into the Dynamic Linq file. The simple steps for doing that are explained in this answer and in my comment on it.
After doing so, it can be used in the following way:
var numbers = new List<int> { 1, 2, 3 };
var wrapped = numbers.Select(num => new { Value = num }).ToList();
// the following is the equivalent of calling: numbers.Select(num => num).ToList()
var selectedNumbers = numbers.Select<int>("it").ToList();
// the following is the equivalent of calling: wrapped.Select(num => num.Value).ToList()
var selectedValues = wrapped.Select<int>("Value").ToList();
// the following is the equivalent of calling: numbers.Select(num => new { Value = num }).ToList()
var selectedObjects = numbers.Select<object>("new(it as Value)").ToList();
The Downside
Arguably, this implementation introduces yet another kind of downside: By having to explicitly parameterize the Select<T> call (e.g., having to call Select<int>), we're losing the dynamic nature of the library.
Nevertheless, since we can now call any materialization Linq method, this usage may still be quite useful.
I simplified the amazing method DynamicSelectGenerator() created by Ali and made this extension method that overrides the LINQ Select() to take a column separated parameters to simplify the usage and for more readability:
public static IEnumerable<T> Select<T>(this IEnumerable<T> source, string parameters)
{
return source.Select(DynamicSelectGenerator<T>(parameters));
}
So instead of:
var query = list.Select(Helpers.DynamicSelectGenerator<Data>("Field1,Field2")).ToList();
Will be:
var query = list.Select("Field1,Field2").ToList();
Another approach I've used is a nested ternary operator:
string col = "Column3";
var query = table.Select(i => col == "Column1" ? i.Column1 :
col == "Column2" ? i.Column2 :
col == "Column3" ? i.Column3 :
col == "Column4" ? i.Column4 :
null);
The ternary operator requires that each field be the same type, so you'll need to call .ToString() on any non-string columns.
I have generate my own class for same purpose of usage.
github gist : https://gist.github.com/mstrYoda/663789375b0df23e2662a53bebaf2c7c
It generates dynamic select lambda for given string and also support for two level nested properties.
Example of usage is :
class Shipment {
// other fields...
public Address Sender;
public Address Recipient;
}
class Address {
public string AddressText;
public string CityName;
public string CityId;
}
// in the service method
var shipmentDtos = _context.Shipments.Where(s => request.ShipmentIdList.Contains(s.Id))
.Select(new SelectLambdaBuilder<Shipment>().CreateNewStatement(request.Fields)) // request.Fields = "Sender.CityName,Sender.CityId"
.ToList();
It compiles the lambda as below:
s => new Shipment {
Sender = new Address {
CityId = s.Sender.CityId,
CityName = s.Sender.CityName
}
}
You can also find my quesion and answer here :c# - Dynamically generate linq select with nested properties
public class SelectLambdaBuilder<T>
{
// as a performence consideration I cached already computed type-properties
private static Dictionary<Type, PropertyInfo[]> _typePropertyInfoMappings = new Dictionary<Type, PropertyInfo[]>();
private readonly Type _typeOfBaseClass = typeof(T);
private Dictionary<string, List<string>> GetFieldMapping(string fields)
{
var selectedFieldsMap = new Dictionary<string, List<string>>();
foreach (var s in fields.Split(','))
{
var nestedFields = s.Split('.').Select(f => f.Trim()).ToArray();
var nestedValue = nestedFields.Length > 1 ? nestedFields[1] : null;
if (selectedFieldsMap.Keys.Any(key => key == nestedFields[0]))
{
selectedFieldsMap[nestedFields[0]].Add(nestedValue);
}
else
{
selectedFieldsMap.Add(nestedFields[0], new List<string> { nestedValue });
}
}
return selectedFieldsMap;
}
public Func<T, T> CreateNewStatement(string fields)
{
ParameterExpression xParameter = Expression.Parameter(_typeOfBaseClass, "s");
NewExpression xNew = Expression.New(_typeOfBaseClass);
var selectFields = GetFieldMapping(fields);
var shpNestedPropertyBindings = new List<MemberAssignment>();
foreach (var keyValuePair in selectFields)
{
PropertyInfo[] propertyInfos;
if (!_typePropertyInfoMappings.TryGetValue(_typeOfBaseClass, out propertyInfos))
{
var properties = _typeOfBaseClass.GetProperties();
propertyInfos = properties;
_typePropertyInfoMappings.Add(_typeOfBaseClass, properties);
}
var propertyType = propertyInfos
.FirstOrDefault(p => p.Name.ToLowerInvariant().Equals(keyValuePair.Key.ToLowerInvariant()))
.PropertyType;
if (propertyType.IsClass)
{
PropertyInfo objClassPropInfo = _typeOfBaseClass.GetProperty(keyValuePair.Key);
MemberExpression objNestedMemberExpression = Expression.Property(xParameter, objClassPropInfo);
NewExpression innerObjNew = Expression.New(propertyType);
var nestedBindings = keyValuePair.Value.Select(v =>
{
PropertyInfo nestedObjPropInfo = propertyType.GetProperty(v);
MemberExpression nestedOrigin2 = Expression.Property(objNestedMemberExpression, nestedObjPropInfo);
var binding2 = Expression.Bind(nestedObjPropInfo, nestedOrigin2);
return binding2;
});
MemberInitExpression nestedInit = Expression.MemberInit(innerObjNew, nestedBindings);
shpNestedPropertyBindings.Add(Expression.Bind(objClassPropInfo, nestedInit));
}
else
{
Expression mbr = xParameter;
mbr = Expression.PropertyOrField(mbr, keyValuePair.Key);
PropertyInfo mi = _typeOfBaseClass.GetProperty( ((MemberExpression)mbr).Member.Name );
var xOriginal = Expression.Property(xParameter, mi);
shpNestedPropertyBindings.Add(Expression.Bind(mi, xOriginal));
}
}
var xInit = Expression.MemberInit(xNew, shpNestedPropertyBindings);
var lambda = Expression.Lambda<Func<T,T>>( xInit, xParameter );
return lambda.Compile();
}
Thank you #morio. Your comment about Expression<Func<T, T>> is exactly what I needed to make this work.
I do not know how to perform an anonymous projection which seems like what most want. I say I want Field1 and Field2 from Data and I get back something like: new { Field1 = o.Field1, Field2 = o.Field2 };
But I have a need similar to many where I want to plot x and y values, but don't know until run time which ones they are.
So rather than use an anonymous object, I create one that has the properties I want. In this case, X and Y.
Here are the source and target classes:
public class Source
{
public int PropertyA { get; set; }
public double PropertyB { get; set; }
public double PropertyC { get; set; }
}
public class Target
{
public double X { get; set; }
public double Y { get; set; }
}
And here is the code that does the mapping between the Source and the Target.
public static class SelectBuilder
{
/// <summary>
/// Creates a Func that can be used in a Linq Select statement that will map from the source items to a new target type.
/// Typical usage pattern is that you have an Entity that has many properties, but you want to dynamically set properties
/// on a smaller target type, AND, you don't know the mapping at compile time.
/// For example, you have an Entity that has a year and 10 properties. You want to have time (year) as the X axis, but
/// the user can chose any of the 10 properties to plot on the y axis. This would allow you to map one of the entity
/// properties to the Y value dynamically.
/// </summary>
/// <typeparam name="TSource">Type of the source, for example, and Entity Framework entity.</typeparam>
/// <typeparam name="TTarget">Type of the target, a projection of a smaller number of properties than the entity has.</typeparam>
/// <param name="propertyMappings">A list of named tuples that map the sourceProperty to the targetProperty.</param>
/// <returns>A func that can be used inside the Select.
/// So if
/// var select = SelectBuilder.GetSelectStatement<Source, Target>(propertyMappings), then
/// you can perform the select,
/// var results = items.Select(select);</returns>
public static Expression<Func<TSource, TTarget>> GetSelectStatement<TSource, TTarget>(IEnumerable<(string sourceProperty, string targetProperty)> propertyMappings)
{
// Get the source parameter, "source". This will allow the statement to be "X = source.SourceA".
// It needs to be of the source type, and the name is what will be used in the Select lambda.
var sourceParameter = Expression.Parameter(typeof(TSource), "source");
// Now define the ability to create a new Target type.
var newTarget = Expression.New(typeof(TTarget));
// Now develop the bindings or member assignments for each property.
var bindings = new List<MemberAssignment>();
foreach (var propertyMapping in propertyMappings)
{
var sourceMemberInfo = typeof(TSource).GetProperty(propertyMapping.sourceProperty);
var targetMemberInfo = typeof(TTarget).GetProperty(propertyMapping.targetProperty);
// This allows getting the value. Source parameter will provide the "source" part and sourceMemberInfo the property name.
// For example, "source.SourceA".
var sourceValue = Expression.Property(sourceParameter, sourceMemberInfo);
// Provide conversion in the event there is not a perfect match for the type.
// For example, if SourceA is int and the target X is double?, we need to convert from int to double?
var convertExpression = Expression.Convert(sourceValue, targetMemberInfo.PropertyType);
// Put together the target assignment, "X = Convert(source.SourcA, double?)" (TODO: How does the convert actually happen?)
var targetAssignment = Expression.Bind(targetMemberInfo, convertExpression);
bindings.Add(targetAssignment);
}
var memberInit = Expression.MemberInit(newTarget, bindings);
// Here if we map SourceA to X and SourceB to Y the lambda will be:
// {source => new Target() {X = Convert(source.SourceA, Nullable`1), Y = Convert(source.SourceB, Nullable`1)}}
var lambda = Expression.Lambda<Func<TSource, TTarget>>(memberInit, sourceParameter);
return lambda;//.Compile();
}
}
And finally a unit test that works.
[Fact(DisplayName = "GetSelectStatement works")]
public void Test2()
{
// Arrange
var source = new Source { PropertyA = 1, PropertyB = 2, PropertyC = 3 };
var expectedX = Convert.ToDouble(source.PropertyA);
var expectedY = Convert.ToDouble(source.PropertyB);
var items = new List<Source> { source }.AsQueryable();
// Let's map SourceA to X and SourceB to Y.
var propertyMappings = new List<(string sourceProperty, string targetProperty)>
{
("PropertyA", "X"), ("PropertyB", "Y")
//(nameof(Source.PropertyA), nameof(Target.X)),
//(nameof(Source.PropertyB), nameof(Target.Y))
};
// Act
var select = SelectBuilder.GetSelectStatement<Source, Target>(propertyMappings);
var actual = items.Select(select).First();
// Assert
actual.X.Should().Be(expectedX);
actual.Y.Should().Be(expectedY);
}
I've edited my previous answer since now I know how to convert from int to double. I've also made the unit test easier to understand.
I hope this helps others.
Using ExpandoObject you can build a dynamic objects or return the full object from the example below.
public object CreateShappedObject(object obj, List<string> lstFields)
{
if (!lstFields.Any())
{
return obj;
}
else
{
ExpandoObject objectToReturn = new ExpandoObject();
foreach (var field in lstFields)
{
var fieldValue = obj.GetType()
.GetProperty(field, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance)
.GetValue(obj, null);
((IDictionary<string, object>)objectToReturn).Add(field, fieldValue);
}
return objectToReturn;
}
}
The following is an example of how to use this from your controller.
http://localhost:12345/api/yourapi?fields=field1,field2
public IHttpActionResult Get(string fields = null)
{
try
{
List<string> lstFields = new List<string>();
if (fields != null)
{
lstFields = fields.ToLower().Split(',').ToList();
}
// Custom query
var result = db.data.Select(i => CreateShappedObject(new Data()
, lstFields)).ToList();
return Ok(result);
}
catch(Exception)
{
return InternalServerError();
}
}
var result = from g in list.AsEnumerable()
select new {F1 = g.Field1,F2 = g.Field2};
I would like to use a single, general method to retrieve an ordered list for a given string representing a property inside a lambda expression.
I know people requested this before but it didn't work for me. I tried this and it threw error:
db.Books.OrderByDescending(x => x.GetType().GetProperty("Discount").GetValue(x,null))
.Take(3);
I'm using this at the moment:
public IQueryable<Book> GetCheapestBooks()
{
return db.Books.OrderBy(x => x.Discount)
.Take(3);
}
maybe this is what you are looking for:
Dynamic Linq
With this you can write queries like:
var result = db.Books.OrderBy( "Discount" ).Take( 3 );
Simple console application.
class A
{
public int prop1 { get; set; }
public int prop2 { get; set; }
}
class Program
{
static IEnumerable<T> GenericOrderByDescending<T>(IEnumerable<T> arg, string property, int take)
{
return arg.OrderByDescending(x => x.GetType().GetProperty(property).GetValue(x, null)).Take(take);
}
static void Main(string[] args)
{
IEnumerable<A> arr = new List<A>()
{
new A(){ prop1 = 1, prop2 = 2},
new A(){prop1 = 2,prop2 =2},
new A(){prop1 = 3,prop2 =2},
new A(){prop1 = 441,prop2 =2},
new A(){prop1 = 2,prop2 =2}
};
foreach(var a1 in GenericOrderByDescending<A>(arr, "prop1", 3))
{
Console.WriteLine(a1.prop1);
}
}
}
U can pass your db.Boks.AsEnumerable() as parameter for GenericOrderByDescending<T>() method. Instead of T you should type the type of your db.Boks items. My example sorts an array of instances of class A and I've got no errors, it works fine. Did I understand you correctly?
You can try with this code
public IQueryable<Book> GetCheapestBooks()
{
db.Books.OrderBy(x => x.Discount).Take(3).AsQueryable<Book>();
}
You could create an extension method which creates the property expression:
private static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, string propertyName)
{
PropertyInfo prop = typeof(T).GetProperty(propertyName);
ParameterExpression paramExpr = Expression.Parameter(typeof(T), "obj");
MemberExpression propExpr = Expression.Property(paramExpr, prop);
Type funcType = typeof(Func<,>).MakeGenericType(typeof(T), prop.PropertyType);
Type keySelectorType = typeof(Expression<>).MakeGenericType(funcType);
LambdaExpression keySelector = Expression.Lambda(funcType, propExpr, paramExpr);
MethodInfo orderByMethod = typeof(Queryable).GetMethods().Single(m => m.Name == "OrderBy" && m.GetParameters().Length == 2).MakeGenericMethod(typeof(T), prop.PropertyType);
return (IOrderedQueryable<T>) orderByMethod.Invoke(null, new object[] { source, keySelector });
}
Given that I have an IEnumerable<T>, where T is any object, how can I select a specific property from it, given that I know the name of the one of the property names at run time as a string?
For example:
var externalIEnumerable = DataPassedFromConsumingCode(); // `IEnumerable<T>`
string knownPropertyName = "Foo";
var fooSelect = externalIEnumerable.Select(...);
In essence, I'm obviously just doing externalIEnumerable.Select(x=> x.Foo);, but I need to perform this Select at runtime, when I don't have control over when it's initially created.
--
ANSWER: Based on AlanT's answer, here's what I actually did:
public Expression<Func<TItem, object>> SelectExpression<TItem>(string fieldName)
{
var param = Expression.Parameter(typeof(TItem), "item");
var field = Expression.Property(param, fieldName);
return Expression.Lambda<Func<TItem, object>>(field,
new ParameterExpression[] { param });
}
I kept it as an Expression, because calling Compile caused the IQueryable to be Enumerated, which meant the database was hit unnecessarily. So, to use it, I just do the following:
string primaryKey = _map.GetPrimaryKeys(typeof(TOriginator)).Single();
var primaryKeyExpression = SelectExpression<TOriginator>(primaryKey);
var primaryKeyResults = query.Select(primaryKeyExpression).ToList();
It is possible to do this using an Expression
e.g.
private class Foo {
public string Bar { get; set; }
}
private IEnumerable<Foo> SomeFoos = new List<Foo>() {
new Foo{Bar = "Jan"},
new Foo{Bar = "Feb"},
new Foo{Bar = "Mar"},
new Foo{Bar = "Apr"},
};
[TestMethod]
public void GetDynamicProperty() {
var expr = SelectExpression<Foo, string>("Bar");
var propValues = SomeFoos.Select(expr);
Assert.IsTrue(new[] { "Jan", "Feb", "Mar", "Apr" }.SequenceEqual(propValues));
}
public static Func<TItem, TField> SelectExpression<TItem, TField>(string fieldName) {
var param = Expression.Parameter(typeof(TItem), "item");
var field = Expression.Property(param, fieldName);
return Expression.Lambda<Func<TItem, TField>>(field, new ParameterExpression[] { param }).Compile();
}
hth,
Alan.
The dynamic linq library allows you to specify predicates and projections on the fly and may fit your use case-
http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx
You can dynamically build an Expression<Func<T, U>>.
I want to take a class, loop through it's properties, get the property value, and call a method passing that property value in. I think I can get the property values, but what does the lambda expression's body look like? What body is used to call a method on each property?
This is what I have so far...
Action<T> CreateExpression<T>( T obj )
{
foreach( var property in typeof( T ).GetProperties() )
{
Expression value = Expression.Property( Expression.Constant( obj ), property );
var method = Expression.Call( typeof( SomeType ), "SomeMethod", null, value );
}
// What expression body can be used that will call
// all the method expressions for each property?
var body = Expression...
return Expression.Lambda<Action<T>>( body, ... ).Compile();
}
It depends on a few things.
does the method return anything? Expression in 3.5 can't do multiple separate "action" operations (a statement body), but you can cheat if you can do something with a fluent API:
SomeMethod(obj.Prop1).SomeMethod(obj.Prop2).SomeMethod(obj.Prop3);
(perhaps using generics to make it simpler)
do you have access to 4.0? In 4.0 there are additional Expression types allowing statement bodies and exactly what you ask for. I discuss some similar examples in an article here (look for Expression.Block, although this is based on a beta a while ago - it may have been renamed by now).
Alternative; since you are compiling to a delegate, consider that an Action<T> is multicast; you could build a set of simple operations, and combine them in the delegate; this would work in 3.5; for example:
using System;
using System.Linq.Expressions;
static class SomeType
{
static void SomeMethod<T>(T value)
{
Console.WriteLine(value);
}
}
class Customer
{
public int Id { get; set; }
public string Name { get; set; }
}
static class Program
{
static readonly Action<Customer> action = CreateAction<Customer>();
static void Main()
{
Customer cust = new Customer { Id = 123, Name = "Abc" };
action(cust);
}
static Action<T> CreateAction<T>()
{
Action<T> result = null;
var param = Expression.Parameter(typeof(T), "obj");
foreach (var property in typeof(T).GetProperties(
BindingFlags.Instance | BindingFlags.Public))
{
if (property.GetIndexParameters().Length > 0) continue;
var propVal = Expression.Property(param, property);
var call = Expression.Call(typeof(SomeType), "SomeMethod", new Type[] {propVal.Type}, propVal);
result += Expression.Lambda<Action<T>>(call, param).Compile();
}
return result;
}
}
I dont think it will be so easy using Expressions, in .NET 3.5 at least.
.NET 4 supports a block construct I believe.
I suggest using Reflection.Emit rather.
Here is a starting point (for fields but can be changed easily):
internal static T CreateDelegate<T>(this DynamicMethod dm) where T : class
{
return dm.CreateDelegate(typeof(T)) as T;
}
static Dictionary<Type, Func<object, Dictionary<string, object>>> fieldcache =
new Dictionary<Type, Func<object, Dictionary<string, object>>>();
static Dictionary<string, object> GetFields(object o)
{
var t = o.GetType();
Func<object, Dictionary<string, object>> getter;
if (!fieldcache.TryGetValue(t, out getter))
{
var rettype = typeof(Dictionary<string, object>);
var dm = new DynamicMethod(t.Name + ":GetFields",
rettype, new Type[] { typeof(object) }, t);
var ilgen = dm.GetILGenerator();
var instance = ilgen.DeclareLocal(t);
var dict = ilgen.DeclareLocal(rettype);
ilgen.Emit(OpCodes.Ldarg_0);
ilgen.Emit(OpCodes.Castclass, t);
ilgen.Emit(OpCodes.Stloc, instance);
ilgen.Emit(OpCodes.Newobj, rettype.GetConstructor(Type.EmptyTypes));
ilgen.Emit(OpCodes.Stloc, dict);
var add = rettype.GetMethod("Add");
foreach (var field in t.GetFields(
BindingFlags.DeclaredOnly |
BindingFlags.Instance |
BindingFlags.Public |
BindingFlags.NonPublic))
{
if (!field.FieldType.IsSubclassOf(typeof(Component)))
{
continue;
}
ilgen.Emit(OpCodes.Ldloc, dict);
ilgen.Emit(OpCodes.Ldstr, field.Name);
ilgen.Emit(OpCodes.Ldloc, instance);
ilgen.Emit(OpCodes.Ldfld, field);
ilgen.Emit(OpCodes.Castclass, typeof(object));
ilgen.Emit(OpCodes.Callvirt, add);
}
ilgen.Emit(OpCodes.Ldloc, dict);
ilgen.Emit(OpCodes.Ret);
fieldcache[t] = getter = dm.CreateDelegate<Func<object,
Dictionary<string, object>>>();
}
return getter(o);
}
Use the Block statement. The code below for example writes out the names of all properties
static void WritePropertyNames()
{
TestObject lTestObject = new TestObject();
PropertyInfo[] lProperty = typeof(TestObject).GetProperties();
List<Expression> lExpressions = new List<Expression>();
MethodInfo lMethodInfo = typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) });
lProperty.ForEach(x =>
{
ConstantExpression lConstant = Expression.Constant(x.Name);
MethodCallExpression lMethodCall = Expression.Call(lMethodInfo, lConstant);
lExpressions.Add(lMethodCall);
});
BlockExpression lBlock = Expression.Block(lExpressions);
LambdaExpression lLambda = Expression.Lambda<Action>(lBlock, null);
Action lWriteProperties = lLambda.Compile() as Action;
lWriteProperties();
}
Expression trees can only contain a single statement. To do what you are trying you would need to Expression.Lambda<>() in your loop, passing "method" as the body.
I believe this has changed in .NET Framework 4.0.
Andrew
If you're willing to have your method SomeType.SomeMethod accept an object[] then you can do something like this (note that indexers can not be handled here so we discard them):
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
namespace Test {
class SomeType {
public static void SomeMethod(object[] values) {
foreach (var value in values) {
Console.WriteLine(value);
}
}
}
class Program {
static Action<T> CreateAction<T>() {
ParameterExpression parameter = Expression.Parameter(
typeof(T),
"parameter"
);
List<Expression> properties = new List<Expression>();
foreach (var info in typeof(T).GetProperties()) {
// can not handle indexers
if(info.GetIndexParameters().Length == 0) {
Expression property = Expression.Property(parameter, info);
properties.Add(Expression.Convert(property, typeof(object)));
}
}
Expression call = Expression.Call(
typeof(SomeType).GetMethod("SomeMethod"),
Expression.NewArrayInit(typeof(object), properties)
);
return Expression.Lambda<Action<T>>(call, parameter).Compile();
}
static void Main(string[] args) {
Customer c = new Customer();
c.Name = "Alice";
c.ID = 1;
CreateAction<Customer>()(c);
}
}
class Customer {
public string Name { get; set; }
public int ID { get; set; }
}
}
Of course this will be easier in .NET 4.0 with the LoopExpression.