I am using an expression tree as an alternative to automapper to map source properties to target properties using below code
What i am doing is, I have created static method inside static class for mapping and assigning inner child object property to outer object property
public static class PropertyMapper<TSource, TDest>
{
private static Expression<Func<TSource, Dictionary<string, MasterSection>, TDest>> _mappingExpression;
private static Func<TSource, Dictionary<string, MasterSection>, TDest> _mapper;
static PropertyMapper()
{
_mappingExpression = ProjectionMap();
_mapper = _mappingExpression.Compile();
}
public static Func<TSource, Dictionary<string, MasterSection>, TDest> Mapper => _mapper;
public static Expression<Func<TSource, Dictionary<string, MasterSection>, TDest>> ProjectionMap()
{
var sourceProperties = typeof(TSource).GetProperties().Where(p => p.CanRead);
var targetProperties= typeof(TDest).GetProperties().Where(p => p.CanWrite);
var propertyMap =
from d in targetProperties
join s in sourceProperties on new { d.Name, d.PropertyType } equals new { s.Name, s.PropertyType }
where d.Name != "SourceOfDataId" && d.Name!= "SourceOfData"
select new { Source = s, Dest = d };
var itemParam = Expression.Parameter(typeof(TSource), "item");
var memberBindings = propertyMap.Select(p => (MemberBinding)Expression.Bind(p.Dest, Expression.Property(itemParam, p.Source))).ToList();
var sourceOfDataIdProp = targetProperties.FirstOrDefault(s => s.Name == "SourceOfDataId");
if (sourceOfDataIdProp != null)
{
memberBindings.Add(Expression.Bind(sourceOfDataIdProp,Expression.Convert(Expression.Property(Expression.Property(itemParam, "SourceOfData"),"Id"),typeof(Guid?))));
}
var sourceOfDataProp = targetProperties.FirstOrDefault(s => s.Name == "SourceOfData");
if(sourceOfDataProp != null)
{
// here i would like to update `sourceOfData` object property "isApproved"
}
var newExpression = Expression.New(typeof(TDest));
var memberInitExpression = Expression.MemberInit(newExpression, memberBindings);
var projection = Expression.Lambda<Func<TSource, Dictionary<string, MasterSection>, TDest>>(memberInitExpression, itemParam, dictParam);
return projection;
}
}
and i am using the above method here in below to map the source properties to target properties
AirflowsLab = sourceMechanicalData.AirflowsLab.Select(a => PropertyMapper<LibraryLabAirflow, LibraryLabAirflow>.Mapper(a, masterSectionMappedLibrary)).ToList();
and the structure for LibraryLabAirflow is looks like as below
public class LibraryLabAirflow
{
[ForeignKey("SourceOfData")]
public Guid? SourceOfDataId { get; set; }
public virtual CodeStandardGuideline SourceOfData { get; set; }
}
Above mapping is working fine, what i am trying is now that i need to access the sourceOfData child object of target and update the property for sourceOfData and map that updated child object to source child object sourceOfData.
below is the sourceOfData object details
"SourceOfData":{
"Id": "c5bf3585-50b1-4894-8fad-0ac884343935",
"IsApproved": null, // trying to set this to true instead of null inside target object
"MasterSection": null
},
I am not sure how to access the child object property using an expression tree in this scenario and i cannot use automapper library. Could any one please let me know how to access the child object property and update and assign back to target.
What i am trying to generate an expression looks like this source.SourceOfData = target.SourceOfData but before this i need to update one of the property of target.SourceOfData
Many thanks in advance
Desired expression :
AirflowsLab = sourceMechanicalData.AirflowsLab.Where(a => a != null).Select(item => new LibraryLabAirflow()
{
SourceOfData = new CodeStandardGuideline()
{
IsApproved = true,// trying to set this through expression tree
City = item.SourceOfData.City
......
.......
}
}).ToList(),
trying like this is not working as well, 1
var sourceOfDataProp = targetProperties.FirstOrDefault(s => s.Name == "SourceOfData");
if(sourceOfDataProp != null)
{
// here need to get the sourceofdata properties
var sourceOfDataProperty = Expression.Property(Expression.Constant(sourceOfDataProp), "IsApproved");
}
Update:
i have implemented the logic inside if block of sourceOfDataProp != null but getting an error
if (sourceOfDataProp != null)
{
var targetitemParam = Expression.Parameter(typeof(TTarget), "item");
var sourceOfDataPropertiesFilter = new List<string>()
{
"IsApproved"
};
var sourceItem = Expression.Property(itemParam, typeof(TSource).GetProperty("SourceOfData"));
var sourcePropertyInfo = sourceItem.Type.GetProperties().Where(p => p.CanRead);
var targetItem = Expression.Property(targetitemParam, typeof(TTarget).GetProperty("SourceOfData"));
var targetPropertyInfo = targetItem.Type.GetProperties().Where(p => p.CanWrite);
var sourceOfDataPropertyMap = from tp in targetPropertyInfo
join sp in sourcePropertyInfo
on new { tp.Name, tp.PropertyType } equals new { sp.Name, sp.PropertyType }
where !sourceOfDataPropertiesFilter.Contains(tp.Name)
select new { Source = sp, Target = tp };
// getting error at below line type of arguments does not match
var sourceOfDataMemberBindings = sourceOfDataPropertyMap.Select(p => Expression.Bind(p.Target, Expression.PropertyOrField(targetitemParam, "SourceOfData"))).ToList();
}
I have solved this problem like as below
if (sourceOfDataProp != null)
{
var targetItemParam = Expression.Parameter(typeof(TTarget), "item");
var sourceOfDataPropertiesFilter = new List<string>()
{
"IsApproved"
};
var sourceItem = Expression.Property(itemParam, typeof(TSource).GetProperty("SourceOfData"));
var sourceOfDataSourceProperties = sourceItem.Type.GetProperties().Where(p => p.CanRead);
var targetItem = Expression.Property(targetItemParam, typeof(TTarget).GetProperty("SourceOfData"));
var sourceOfDataTargetProperties = targetItem.Type.GetProperties().Where(p => p.CanWrite);
var sourceOfDataPropertyMap = sourceOfDataTargetProperties.Join(sourceOfDataSourceProperties,
t => new { t.Name, t.PropertyType },
s => new { s.Name, s.PropertyType },
(t, s) => new { Source = s, Target = t })
.Where(t => !sourceOfDataPropertiesFilter.Contains(t.Target.Name));
var sourceOfDataMemberBindings = sourceOfDataPropertyMap.Select(p => Expression.Bind(p.Target, Expression.Property(sourceItem, p.Source))).ToList();
var sourceOfDataIsApprovedProp = sourceOfDataTargetProperties.FirstOrDefault(s => s.Name == "IsApproved");
if (sourceOfDataIsApprovedProp != null)
{
sourceOfDataMemberBindings.Add(Expression.Bind(sourceOfDataIsApprovedProp, Expression.Constant(true, typeof(bool?))));
}
var sourceOfDataExpression = Expression.New(typeof(DesignHub.Entities.CodeStandardGuideline));
var sourceOfDataMemberInitExpression = Expression.MemberInit(sourceOfDataExpression, sourceOfDataMemberBindings);
memberBindings.Add(Expression.Bind(sourceOfDataProp, sourceOfDataMemberInitExpression));
}
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};
I'm trying to construct an expression that ultimately results in a query like
SELECT p.*
FROM MyEntity p
WHERE EXISTS(SELECT *
FROM filters
WHERE (filter.type = 1
AND filter.objectid = p.id
AND filter.value = 1
OR filter.type = 1
AND filter.objectid = p.id
AND filter.value = 2))
AND EXISTS(...)
Obviously it won't look exactly like that, but that's the general idea.
I'm using PredicateBuilder to build the query based on the filters passed in, so I have something like this:
var query = context.Set<MyEntity>().AsExpandable();
var predicate = PredicateBuilder.New<MyEntity>(true);
//loop through the group filters. The filters in a group have an or relationship
foreach (FilterGroup group in filters)
{
predicate = predicate.And(
p => context.Set<FilteringValue>().AsExpandable().Any(getFilteringPredicate(p,group ))
);
}
return query.Where(predicate);
And the getFilteringPredicateMethod:
Expression<Func<FilteringValue,bool>> getFilteringPredicate(MyEntity p, FilterGroup filters) {
var fPredicate = PredicateBuilder.New<FilteringValue>(true);
foreach(var filter in filters.FilterList)
{
fPredicate= fPredicate.Or(fv => fv.objectid == p.Id && fv.Type== 1 && fv.value == filter.Value);
}
return fPredicate
}
This seems relatively simple, however I'm getting the error
variable 'p' of type 'Models.MyEntity' referenced from scope '', but it is not defined.
Is there no way to pass the product object into the getFilteringPredicate() method? MyEntity and Filter are not related in Entity Framework.
So... I think I finally got it, you want to relate two expression parameters and build up a composite query (what I mean by the informal definition of "composite" is a subquery having a reference to the main query parameter(s)):
Unfortunately, LinqKit does not support multi-parameter expressions 'AFAIK', which is something that would be a perfect match for your case:
Well, anyway... here it goes. By the way FilteringValues and MyEntities are just two DbSets, I just happen to be using LinqPad to test this out ATM (Questions?):
void Main(string[] args)
{
var entityQuery = MyEntities.AsExpandable();
var filterGroups = GetFilterGroups();
// Initialize with TRUE since no group filter implies Everything matches
var predicate = PredicateBuilder.New<MyEntity>(true);
var filteringValueQuery = FilteringValues.AsExpandable();
foreach (var g in filterGroups)
{
if (!g.FilterList.Any())
{
// If we have no filters in the group, skip
continue;
}
var expressionForGroupFilters = BuildExpressionForGroupFilters(g.FilterList);
predicate = predicate.And(entity => filteringValueQuery.Any(filteringValue => expressionForGroupFilters.Invoke(entity, filteringValue)));
}
entityQuery = entityQuery.Where(predicate);
var data = entityQuery.ToList();
data.Dump();
}
public static Expression<Func<MyEntity, FilteringValue, bool>> BuildExpressionForSingleFilter(Filter groupFilter)
{
var value = groupFilter.Value;
return (entity, filteringValue) =>
filteringValue.Type == 1
&& filteringValue.ObjectId == entity.Id
&& filteringValue.Value == value;
}
public static Expression<Func<MyEntity, FilteringValue, bool>> BuildExpressionForGroupFilters(IReadOnlyCollection<Filter> groupFilters)
{
Expression<Func<MyEntity, FilteringValue, bool>> result = null;
foreach (var groupFilter in groupFilters)
{
var expression = BuildExpressionForSingleFilter(groupFilter);
if (result == null)
{
result = expression;
continue;
}
var tempResult = result.Expand();
result = (entity, filteringValue) => tempResult.Invoke(entity, filteringValue) || expression.Invoke(entity, filteringValue);
}
return result.Expand();
}
public static FilterGroup CreateFilterGroupWithValues(params int[] values)
{
var filterList = values
.Select(x => new Filter { Value = x })
.ToList();
return new FilterGroup { FilterList = filterList };
}
public static IEnumerable<FilterGroup> GetFilterGroups()
{
return new[] {CreateFilterGroupWithValues(0, 2, 4), CreateFilterGroupWithValues(1)};
}
public class Filter
{
public int Value { get; set; }
}
public class FilterGroup
{
public FilterGroup()
{
FilterList = new List<Filter>();
}
public List<Filter> FilterList { get; set; }
}
I have an extension in my project that let's me sort an IEnumerable with a string, so that sorting can be done more dynamically.
So, if I have these models:
public MyModel
{
public int Id {get; set;}
public string RecordName {get; set;}
public ChildModel MyChildren {get; set;}
}
public ChildModel
{
public int ChildModelId {get; set;}
public string ChildName {get; set;}
public DateTime SavedDate {get; set;}
}
I could sort my list like this:
var myList = db.MyModel.Where(m => m.IsActive);
myList
.OrderBy(m => m.MyChildren
.OrderByDescending(c => c.SavedDate).FirstOrDefault().SavedDate);
or:
var myList = db.MyModel.Where(m => m.IsActive);
myList.OrderBy(m => m.MyChildren.Max(c => c.SavedDate);
But I want to be able to dynamically sort, based on user options. So I would like this:
var myList = db.MyModel.Where(m => m.IsActive);
myList.OrderByField("MyChildren.SavedDate");
The extension method I have so far looks like this:
public static class MkpExtensions
{
public static IEnumerable<T> OrderByField<T>(this IEnumerable<T> list, string sortExpression)
{
sortExpression += "";
string[] parts = sortExpression.Split(' ');
bool descending = false;
string fullProperty = "";
if (parts.Length > 0 && parts[0] != "")
{
fullProperty = parts[0];
if (parts.Length > 1)
{
descending = parts[1].ToLower().Contains("esc");
}
ParameterExpression inputParameter = Expression.Parameter(typeof(T), "p");
Expression propertyGetter = inputParameter;
foreach (string propertyPart in fullProperty.Split('.'))
{
var checkIfCollection = propertyGetter.Type.GetInterfaces()//(typeof (ICollection<>).FullName);
.Any(x => x.IsGenericType &&
(x.GetGenericTypeDefinition() == typeof(ICollection<>) || x.GetGenericTypeDefinition() == typeof(IEnumerable<>)));
if (checkIfCollection)
{
var pgType = propertyGetter.Type;
var childType = pgType.GetGenericArguments().Single();
var childProp = childType.GetProperty(propertyPart);
ParameterExpression childInParam = Expression.Parameter(childType, "c");
var propertyAccess = Expression.Property(childInParam, childProp);
var orderByExp = Expression.Lambda(propertyAccess, childInParam);
// At this point, orderByExp is c => c.ActionDate
// Now I want to build the expression tree to handle the order by
XXXXX This is where I need help.
}
else
{
// This handles a singular property. Like "MyChildren.ChildName"
// and this part does work
PropertyInfo prop = propertyGetter.Type.GetProperty(propertyPart);
if (prop == null)
throw new Exception("No property '" + fullProperty + "' in + " + propertyGetter.Type.Name + "'");
propertyGetter = Expression.Property(propertyGetter, prop);
}
}
Expression conversion = Expression.Convert(propertyGetter, typeof(object));
var getter = Expression.Lambda<Func<T, object>>(conversion, inputParameter).Compile();
if (descending)
{
// This would be like
// list.OrderByDescending(m => m.MyChildren
// .OrderByDescending(c => c.SavedDate).FirstOrDefault().SavedDate);
return list.OrderByDescending(getter);
}
else
{
// This would be like
// list.OrderBy(m => m.MyChildren
// .OrderByDescending(c => c.SavedDate).FirstOrDefault().SavedDate);
return list.OrderBy(getter);
}
}
return list;
}
}
Basically you should use the following Expression.Call overload which allows you to build an expression for calling static generic methods (what are all the LINQ extension methods).
To build the equivalent of expression like this
m => m.MyChildren.OrderByDescending(c => c.SavedDate).FirstOrDefault().SavedDate
you can use the following snippet:
// At this point, orderByExp is c => c.ActionDate
var orderByDescendingCall = Expression.Call(
typeof(Enumerable), "OrderByDescending", new Type[] { childType, orderByExp.Body.Type },
propertyGetter, orderByExp
);
var firstOrDefaultCall = Expression.Call(
typeof(Enumerable), "FirstOrDefault", new Type[] { childType },
orderByDescendingCall
);
propertyGetter = Expression.Property(firstOrDefaultCall, childProp);
But note that you'll get NRE if the collection is empty.
So you'd better build an expression like this:
m => m.MyChildren.OrderByDescending(c => c.SavedDate)
.Select(c => (DateTime?)c.SavedDate).FirstOrDefault()
with:
// At this point, orderByExp is c => c.ActionDate
var orderByDescendingCall = Expression.Call(
typeof(Enumerable), "OrderByDescending", new Type[] { childType, orderByExp.Body.Type },
propertyGetter, orderByExp
);
Expression propertySelector = propertyAccess;
// If value type property and not nullable, convert it to nullable
if (propertySelector.Type.IsValueType && Nullable.GetUnderlyingType(propertySelector.Type) == null)
propertySelector = Expression.Convert(propertySelector, typeof(Nullable<>).MakeGenericType(propertySelector.Type));
var selectCall = Expression.Call(
typeof(Enumerable), "Select", new Type[] { childType, propertySelector.Type },
orderByDescendingCall, Expression.Lambda(propertySelector, childInParam)
);
propertyGetter = Expression.Call(
typeof(Enumerable), "FirstOrDefault", new Type[] { propertySelector.Type },
selectCall
);
How to combine several similar SELECT-expressions into a single expression?
private static Expression<Func<Agency, AgencyDTO>> CombineSelectors(params Expression<Func<Agency, AgencyDTO>>[] selectors)
{
// ???
return null;
}
private void Query()
{
Expression<Func<Agency, AgencyDTO>> selector1 = x => new AgencyDTO { Name = x.Name };
Expression<Func<Agency, AgencyDTO>> selector2 = x => new AgencyDTO { Phone = x.PhoneNumber };
Expression<Func<Agency, AgencyDTO>> selector3 = x => new AgencyDTO { Location = x.Locality.Name };
Expression<Func<Agency, AgencyDTO>> selector4 = x => new AgencyDTO { EmployeeCount = x.Employees.Count() };
using (RealtyContext context = Session.CreateContext())
{
IQueryable<AgencyDTO> agencies = context.Agencies.Select(CombineSelectors(selector3, selector4));
foreach (AgencyDTO agencyDTO in agencies)
{
// do something..;
}
}
}
Not simple; you need to rewrite all the expressions - well, strictly speaking you can recycle most of one of them, but the problem is that you have different x in each (even though it looks the same), hence you need to use a visitor to replace all the parameters with the final x. Fortunately this isn't too bad in 4.0:
static void Main() {
Expression<Func<Agency, AgencyDTO>> selector1 = x => new AgencyDTO { Name = x.Name };
Expression<Func<Agency, AgencyDTO>> selector2 = x => new AgencyDTO { Phone = x.PhoneNumber };
Expression<Func<Agency, AgencyDTO>> selector3 = x => new AgencyDTO { Location = x.Locality.Name };
Expression<Func<Agency, AgencyDTO>> selector4 = x => new AgencyDTO { EmployeeCount = x.Employees.Count() };
// combine the assignments from the 4 selectors
var convert = Combine(selector1, selector2, selector3, selector4);
// sample data
var orig = new Agency
{
Name = "a",
PhoneNumber = "b",
Locality = new Location { Name = "c" },
Employees = new List<Employee> { new Employee(), new Employee() }
};
// check it
var dto = new[] { orig }.AsQueryable().Select(convert).Single();
Console.WriteLine(dto.Name); // a
Console.WriteLine(dto.Phone); // b
Console.WriteLine(dto.Location); // c
Console.WriteLine(dto.EmployeeCount); // 2
}
static Expression<Func<TSource, TDestination>> Combine<TSource, TDestination>(
params Expression<Func<TSource, TDestination>>[] selectors)
{
var zeroth = ((MemberInitExpression)selectors[0].Body);
var param = selectors[0].Parameters[0];
List<MemberBinding> bindings = new List<MemberBinding>(zeroth.Bindings.OfType<MemberAssignment>());
for (int i = 1; i < selectors.Length; i++)
{
var memberInit = (MemberInitExpression)selectors[i].Body;
var replace = new ParameterReplaceVisitor(selectors[i].Parameters[0], param);
foreach (var binding in memberInit.Bindings.OfType<MemberAssignment>())
{
bindings.Add(Expression.Bind(binding.Member,
replace.VisitAndConvert(binding.Expression, "Combine")));
}
}
return Expression.Lambda<Func<TSource, TDestination>>(
Expression.MemberInit(zeroth.NewExpression, bindings), param);
}
class ParameterReplaceVisitor : ExpressionVisitor
{
private readonly ParameterExpression from, to;
public ParameterReplaceVisitor(ParameterExpression from, ParameterExpression to)
{
this.from = from;
this.to = to;
}
protected override Expression VisitParameter(ParameterExpression node)
{
return node == from ? to : base.VisitParameter(node);
}
}
This uses the constructor from the first expression found, so you might want to sanity-check that all of the others use trivial constructors in their respective NewExpressions. I've left that for the reader, though.
Edit: In the comments, #Slaks notes that more LINQ could make this shorter. He is of course right - a bit dense for easy reading, though:
static Expression<Func<TSource, TDestination>> Combine<TSource, TDestination>(
params Expression<Func<TSource, TDestination>>[] selectors)
{
var param = Expression.Parameter(typeof(TSource), "x");
return Expression.Lambda<Func<TSource, TDestination>>(
Expression.MemberInit(
Expression.New(typeof(TDestination).GetConstructor(Type.EmptyTypes)),
from selector in selectors
let replace = new ParameterReplaceVisitor(
selector.Parameters[0], param)
from binding in ((MemberInitExpression)selector.Body).Bindings
.OfType<MemberAssignment>()
select Expression.Bind(binding.Member,
replace.VisitAndConvert(binding.Expression, "Combine")))
, param);
}
If all of the selectors will only initialize AgencyDTO objects (like your example), you can cast the expressions to NewExpression instances, then call Expression.New with the Members of the expressions.
You'll also need an ExpressionVisitor to replace the ParameterExpressions from the original expressions with a single ParameterExpression for the expression you're creating.
In case anyone else stumbles upon this with a similar use case as mine (my selects targeted different classes based on the level of detail needed):
Simplified scenario:
public class BlogSummaryViewModel
{
public string Name { get; set; }
public static Expression<Func<Data.Blog, BlogSummaryViewModel>> Map()
{
return (i => new BlogSummaryViewModel
{
Name = i.Name
});
}
}
public class BlogViewModel : BlogSummaryViewModel
{
public int PostCount { get; set; }
public static Expression<Func<Data.Blog, BlogViewModel>> Map()
{
return (i => new BlogViewModel
{
Name = i.Name,
PostCount = i.Posts.Count()
});
}
}
I adapted the solution provided by #Marc Gravell like so:
public static class ExpressionMapExtensions
{
public static Expression<Func<TSource, TTargetB>> Concat<TSource, TTargetA, TTargetB>(
this Expression<Func<TSource, TTargetA>> mapA, Expression<Func<TSource, TTargetB>> mapB)
where TTargetB : TTargetA
{
var param = Expression.Parameter(typeof(TSource), "i");
return Expression.Lambda<Func<TSource, TTargetB>>(
Expression.MemberInit(
((MemberInitExpression)mapB.Body).NewExpression,
(new LambdaExpression[] { mapA, mapB }).SelectMany(e =>
{
var bindings = ((MemberInitExpression)e.Body).Bindings.OfType<MemberAssignment>();
return bindings.Select(b =>
{
var paramReplacedExp = new ParameterReplaceVisitor(e.Parameters[0], param).VisitAndConvert(b.Expression, "Combine");
return Expression.Bind(b.Member, paramReplacedExp);
});
})),
param);
}
private class ParameterReplaceVisitor : ExpressionVisitor
{
private readonly ParameterExpression original;
private readonly ParameterExpression updated;
public ParameterReplaceVisitor(ParameterExpression original, ParameterExpression updated)
{
this.original = original;
this.updated = updated;
}
protected override Expression VisitParameter(ParameterExpression node) => node == original ? updated : base.VisitParameter(node);
}
}
The Map method of the extended class then becomes:
public static Expression<Func<Data.Blog, BlogViewModel>> Map()
{
return BlogSummaryViewModel.Map().Concat(i => new BlogViewModel
{
PostCount = i.Posts.Count()
});
}
I'm looking for a way to do following dynamically:
var q = context.Subscription
.Include("Client")
.Include("Invoices")
Where(s=>s.Client.Invoices.Count(i=>i.InvoiceID == SomeInt) > 0);
I would like to build expression dynamically for the left side:
Expression left = s => s.Client.Invoices.Count(i => i.InvoiceID == iSomeVar); //!
Expression right = Expression.Constant(0);
var binary = Expression.GreaterThan(left, right);
Thanks!
UPDATED NOTES:
Please note: The end result must be
Expression<Func<T, bool>>
Simple version:
// To give clear idea, all what I want to achieve is to determine
// whether specific record exists in reference table using known Path.
// Ultimately I want to extend following function (which works great by
// the way, but for simple operations)
static Expression CreateExpression<T>(string propertyPath,
object propertyValue,
ParameterExpression parameterExpression)
{
PropertyInfo property = typeof(T).GetProperty(propertyName);
MemberExpression left = Expression.Property(parameterExpression, property);
ConstantExpression right = Expression.Constant(0);
BinaryExpression binary = Expression.GreaterThan(left, right);
return binary;
}
// And I want to call this function and get result exactly as shown below:
Expression result =
CreateExpression<Subscription>("Client.Invoices.InvoiceID",
theID,
valueSelector.Parameters.Single());
// Where result will be:
// t => t.Client.Invoices.Count(i => i.InvoiceID == theID) > 0;
Extended version:
// 1) I'm using Silverlight 4, EF, RIA.
// 2) At the server side I have a function GetSubscriptionsByCriteria
// that looks about it:
public IQueryable<Subscription> GetSubscriptionsByCriteria(...)
{
var query = this.ObjectContext.Subscriptions.Include("Client")
.Include("Client.Invoices");
var criteria = BuildCriteria(...);
return query.Where(criteria)
}
// 3) BuildCriteria(...) function gathers Expressions and
// aggregates it into the single Expression with different
// AND/OR conditions, something like that:
public Expression<Func<Subscription, bool>> BuildCriteria(
List<SearchFilter> filters,
Expression<Func<Subscription, bool>> valueSelector)
{
List<Expression> filterExpressions = new List<Expression>();
...
Expression expr = CreateExpression<Subscription>(
sfItem.DBPropertyName,
sfItem.DBPropertyValue,
paramExpression,
sf.SearchCondition);
filterExpressions.Add(expr);
...
var filterBody =
filterExpressions.Aggregate<Expression>(
(accumulate, equal) => Expression.And(accumulate, equal));
return Expression
.Lambda<Func<Subscription, bool>>(filterBody, paramExpression);
}
// 4) Here is the simplified version of CreateExpression function:
static Expression CreateExpression<T>(string propertyName,
object propertyValue,
ParameterExpression paramExpression)
{
PropertyInfo property = typeof(T).GetProperty(propertyName);
ConstantExpression right = Expression.Constant(0);
MemberExpression left = Expression.Property(paramExpression, property);
return binary = Expression.Equals(left, right);
}
So, I hope it's clear now why do I need Expression for the left side in my original post. Trying to make this as DRY as possible.
P.S. Not to make it too confusing here is why I think I need to do ёExpression.Call(...)ё:
When I run following code and break it to see DebugView I notice this:
Expression<Func<Subscription, bool>> predicate =
t => t.Client.Invoices.Count(i => i.InvoiceID == 5) > 0;
BinaryExpression eq = (BinaryExpression)predicate.Body;
var left = eq.Left; // <-- See DEBUG VIEW
var right = eq.Right;
// DEBUG VIEW:
// Arguments: Count = 2
// [0] = {t.Client.Invoices}
// [1] = {i => (i.InvoiceID == 5)}
// DebugView: ".Call System.Linq.Enumerable.Count(
// ($t.Client).ClientInvoices,
// .Lambda#Lambda1<System.Func`2[SLApp.Web.Invoice,System.Boolean]>)
// .Lambda#Lambda1<System.Func`2[SLApp.Web.Invoice,System.Boolean]>
// (SLApp.Web.ClientInvoice $i){ $i.ClientInvoiceID == 5 }"
Here's a working program that does what I think you'd like. It defines a function that takes a path to an integer property inside a collection, and an integer value. It then checks whether or not that collection has Count > 0 of that value.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Reflection;
using System.Collections;
namespace Test_Console
{
public class Subscription
{
public int Id { get; set; }
public Client Client { get; set; }
}
public class Client
{
public ICollection<Invoice> Invoices { get; set; }
}
public class Invoice
{
public int Id { get; set; }
}
class Program
{
static void Main(string[] args)
{
var subscriptions = new[]
{
new Subscription { Id = 1, Client = new Client { Invoices = new [] {
new Invoice { Id = 1 },
new Invoice { Id = 2 },
new Invoice { Id = 5 }
} } },
new Subscription { Id = 2, Client = new Client { Invoices = new [] {
new Invoice { Id = 4 },
new Invoice { Id = 5 },
new Invoice { Id = 5 }
} } },
new Subscription { Id = 3, Client = new Client { Invoices = new Invoice[] {
} } },
};
var propertyPath = "Client.Invoices.Id";
Console.WriteLine("What Id would you like to check " + propertyPath + " for?");
var propertyValue = int.Parse(Console.ReadLine());
var whereNumberOne = makeWhere<Subscription>(propertyPath, propertyValue);
Console.WriteLine("The following Subscription objects match:");
foreach (var s in subscriptions.Where(whereNumberOne).ToList())
{
Console.WriteLine("Id: " + s.Id);
}
}
private static Func<T, bool> makeWhere<T>(string propertyPath, int propertyValue)
{
string[] navigateProperties = propertyPath.Split('.');
var currentType = typeof(T);
var functoidChain = new List<Func<object, object>>();
functoidChain.Add(x => x); // identity function starts the chain
foreach (var nextProperty in navigateProperties)
{
// must be inside loop so the closer on the functoids works properly
PropertyInfo nextPropertyInfo;
if (currentType.IsGenericType
&& currentType.GetGenericTypeDefinition().GetInterfaces().Contains(typeof(IEnumerable)))
{
nextPropertyInfo = currentType.GetGenericArguments()[0].GetProperty(nextProperty);
functoidChain.Add(x =>
((IEnumerable<object>)x)
.Count(y => (int)nextPropertyInfo.GetValue(y, null) == propertyValue)
);
}
else
{
nextPropertyInfo = currentType.GetProperty(nextProperty);
functoidChain.Add(x => nextPropertyInfo.GetValue(x, null));
}
currentType = nextPropertyInfo.PropertyType;
}
// compose the functions together
var composedFunctoidChain = functoidChain.Aggregate((f, g) => x => g(f(x)));
var leftSide = new Func<T, int>(x => (int)composedFunctoidChain(x));
return new Func<T, bool>(r => leftSide(r) > 0);
}
}
}
I think this should get you closer to what you're going for:
static Expression<Func<T, bool>> CreateAnyExpression<T, T2>(string propertyPath,
Expression<Func<T2, bool>> matchExpression)
{
var type = typeof(T);
var parameterExpression = Expression.Parameter(type, "s");
var propertyNames = propertyPath.Split('.');
Expression propBase = parameterExpression;
foreach(var propertyName in propertyNames)
{
PropertyInfo property = type.GetProperty(propertyName);
propBase = Expression.Property(propBase, property);
type = propBase.Type;
}
var itemType = type.GetGenericArguments()[0];
// .Any(...) is better than .Count(...) > 0
var anyMethod = typeof(Enumerable).GetMethods()
.Single(m => m.Name == "Any" && m.GetParameters().Length == 2)
.MakeGenericMethod(itemType);
var callToAny = Expression.Call(anyMethod, propBase, matchExpression);
return Expression.Lambda<Func<T, bool>>(callToAny, parameterExpression);
}
Calling it like this:
CreateAnyExpression<Subscription, Invoice>("Client.Invoices", i => i.InvoiceID == 1)
... yields the following Expression<Func<Subscription,bool>>:
s => s.Client.Invoices.Any(i => (i.InvoiceID == 1))
Here's a working program building Linq Expression
{(x.Children.Count(y => y.SomeID == SomeVar) > 0)}
using System;
using System.Linq;
using System.Linq.Expressions;
namespace ExpressionTree
{
class Program
{
static void Main(string[] args)
{
ParameterExpression foundX = Expression.Parameter(typeof(Parent), "x");
Guid[] guids = new Guid[1] { Guid.NewGuid() };
Expression expression = GetCountWithPredicateExpression(guids, foundX);
}
private static Expression GetCountWithPredicateExpression(Guid[] idsToFilter, ParameterExpression foundX)
{
System.Reflection.PropertyInfo childIDPropertyInfo = typeof(Child).GetProperty(nameof(Child.SomeID));
ParameterExpression foundY = Expression.Parameter(typeof(Child), "y");
Expression childIDLeft = Expression.Property(foundY, childIDPropertyInfo);
Expression conditionExpression = Expression.Constant(false, typeof(bool));
foreach (Guid id in idsToFilter)
conditionExpression = Expression.Or(conditionExpression, Expression.Equal(childIDLeft, Expression.Constant(id)));
Expression<Func<Child, bool>> idLambda = Expression.Lambda<Func<Child, bool>>(conditionExpression, foundY);
var countMethod = typeof(Enumerable).GetMethods()
.First(method => method.Name == "Count" && method.GetParameters().Length == 2)
.MakeGenericMethod(typeof(Child));
System.Reflection.PropertyInfo childrenPropertyInfo = typeof(Parent).GetProperty("Children");
Expression childrenLeft = Expression.Property(foundX, childrenPropertyInfo);
Expression ret = Expression.GreaterThan(Expression.Call(countMethod, childrenLeft, idLambda), Expression.Constant(0));
return ret;
}
}
public class Parent
{
public Child[] Children { get; set; }
}
public class Child
{
public int ID { get; set; }
public Guid SomeID { get; set; }
}
}