Linq Expression tree Any() issue - c#

Hello I'm having trouble with an expression tree using the .Any() extension method.
Here's my code:
IQueryable<Book> querableBooks = Books.Values.AsQueryable<Book>();
ParameterExpression pe = Expression.Parameter(typeof(Book), "book");
MemberExpression props = Expression.Property(pe, "properties");
ParameterExpression propList = Expression.Parameter(typeof(List<BookProperty>), "properties");
var _test = Expression.Lambda<Func<List<BookProperty>, bool>>(operation, new ParameterExpression[] { propList });
var _Any = Expression.Call(typeof(Enumerable), "any", new Type[] { typeof(BookProperty) }, new Expression[] { propList });
Expression Lamba = Expression.Lambda(_Any, _props);
_test returns {properties => ((bookProperty.type.key == "lingerie") And (bookProperty.value == "1"))}
_Any returns {properties.Any()}
Lambda returns {book.properties => properties.Any()}
The Book class is like this:
public class Book : IBook
{
public int id { get; set; }
//Removed for clarity
public List<BookProperty> properties { get; set; }
}
An the BookProperty class:
public class BookProperty
{
public BookProperty()
{
value = "0";
}
public int id { get; set; }
public int bookId { get; set; }
public Book book { get; set; }
public int typeId { get; set; }
public BookPropertyType type { get; set; }
public string value { get; set; }
}
And BookPropertyType class:
public class BookPropertyType
{
public int id { get; set; }
public string groupe { get; set; }
public string key { get; set; }
public string label { get; set; }
public int order { get; set; }
public string editorType { get; set; }
public string unite { get; set; }
}
So I'm close to it, but I don't how to merge all this correctly to have a query like this
{book.propertie.Any(bookProperty => bookProperty.type.key == "lingerie") And (bookProperty.value == "1")}
Thanks for your help.

If I understand correctly, you are looking for expression like this:
Expression<Func<Book, bool>> bookPredicate = book => book.properties.Any(
bookProperty => bookProperty.type.key == "lingerie" && bookProperty.value == "1");
You can build it dynamically like this:
var book = Expression.Parameter(typeof(Book), "book");
var properties = Expression.PropertyOrField(book, "properties");
var bookProperty = Expression.Parameter(typeof(BookProperty), "bookProperty");
// bookProperty.type.key == "lingerie"
var conditionA = Expression.Equal(
Expression.PropertyOrField(Expression.PropertyOrField(bookProperty, "type"), "key"),
Expression.Constant("lingerie")
);
// bookProperty.value == "1"
var conditionB = Expression.Equal(
Expression.PropertyOrField(bookProperty, "value"),
Expression.Constant("1")
);
// bookProperty.type.key == "lingerie" && bookProperty.value == "1"
var condition = Expression.AndAlso(conditionA, conditionB);
// bookProperty => bookProperty.type.key == "lingerie" && bookProperty.value == "1"
var predicate = Expression.Lambda<Func<BookProperty, bool>>(condition, bookProperty);
// book.properties.Any(bookProperty => bookProperty.type.key == "lingerie" && bookProperty.value == "1")
var anyCall = Expression.Call(
typeof(Enumerable), "Any", new[] { typeof(BookProperty) },
properties, predicate
);
// book => book.properties.Any(...)
var bookPredicate = Expression.Lambda<Func<Book, bool>>(anyCall, book);

Related

Creating dynamic Expression Tree for selected search criteria

I have an Expression Tree to create a dynamic where clause based on the criteria a user selects on a checkbox.
Eg: - User wants to search for: "test"
User selects
1. Prop1
2. Prop2
for an Object
MyDBObject
The search query will look like
dbRecords.Where(r=> r.Prop1.Contains("test") || r.Prop2.Contains("test"))
The reason to use an Expression Tree is so that it can be used for any unknown number of properties of an unknown object.
I almost have it working, but I get Argument Expression is not valid
Also how does one initialize an empty boolean expression other than using
"something that evaluates to -- true/false" ?
I've only read about them for a few hours by now so maybe there's something I didn't see yet.
public static Expression<Func<T, bool>> CreatePredicateFromCrtieriaAndSearchTerm<T>(List<string> checkedCriteria, string searchTerm)
{
// sample checked records
checkedCriteria = new[]
{
new { Name = "Prop1", DisplayValue = "Checkbox value 1" },
new { Name = "Prop2", DisplayValue = "Checkbox value 2" }
}
.Select(x => x.Name).ToList();
var param = Expression.Parameter(typeof(T), "record");
Expression oneEqualsOne = Expression.Equal(Expression.Constant(1), Expression.Constant(1));
// Creates (record => (1=1) AND ...)
Expression<Func<T, bool>> finalExpression = Expression.Lambda<Func<T, bool>>(oneEqualsOne, param);
Console.WriteLine(finalExpression);
try
{
// Iterate through properties, find selected props and create
// (record.SelectedProp1.Contains("searchTerm") || record.SelectedProp2.Contains("searchTerm") ... )
PropertyDescriptorCollection props = TypeDescriptor.GetProperties(typeof(T));
List<Expression> matchExpressions = new List<Expression>();
for (int i = 0; i < props.Count; i++)
{
PropertyDescriptor prop = props[i];
for (int j = 0; j < checkedCriteria.Count; j++)
{
if (prop.Name == checkedCriteria[j])
{
// add to where expression
Expression left = Expression.Property(param, prop.Name);
MethodInfo contains = typeof(string).GetMethod("Contains", new[] { typeof(string) });
Expression right = Expression.Constant(searchTerm, searchTerm.GetType());
Expression matchExpression = Expression.Call(left, contains, right);
matchExpressions.Add(matchExpression);
}
}
}
// Creates (1=0 OR ... OR ...)
Expression currentPredicateBody = Expression.Equal(Expression.Constant(1), Expression.Constant(0));
foreach (var matchExpression in matchExpressions)
{
currentPredicateBody = Expression.MakeBinary(ExpressionType.OrElse, matchExpression, currentPredicateBody);
Console.WriteLine(currentPredicateBody);
}
// ( (1=0) || record.SelectedProp1.Contains("searchTerm") || record.SelectedProp2.Contains("searchTerm") )
if (matchExpressions.Count > 0)
{
oneEqualsOne = Expression.AndAlso(oneEqualsOne, currentPredicateBody);
Console.WriteLine(oneEqualsOne);
}
// Full expression:
// ( record => (1=1) AND ( (1=0) || record.SelectedProp1.Contains("searchTerm") || record.SelectedProp2.Contains("searchTerm") ))
finalExpression = Expression.Lambda<Func<T, bool>>(oneEqualsOne, new ParameterExpression[] { param });
Console.WriteLine(finalExpression);
}
catch (Exception ex)
{
throw new Exception(string.Format(#"Error occurred creating where predicate from checked criteria: {0}", ex.Message));
}
return finalExpression;
}
internal class MyDBObject
{
public int Id { get; set; }
public string Prop1 { get; set; }
public string Prop2 { get; set; }
public string Prop3 { get; set; }
public string Prop4 { get; set; }
public string Prop5 { get; set; }
public string Prop11 { get; set; }
public string Prop12 { get; set; }
public string Prop13 { get; set; }
public string Prop14 { get; set; }
public string Prop15 { get; set; }
public string Prop21 { get; set; }
public string Prop22 { get; set; }
public string Prop23 { get; set; }
public string Prop24 { get; set; }
public string Prop25 { get; set; }
}
public static void Main(string[] args)
{
List<MyDBObject> dbRecords = new List<MyDBObject>
{
new MyDBObject { Id = 1, Prop2 = "O1_P2", Prop3 = "O1_P3", Prop12 = "O1_P12", Prop15 = "O1_P15", Prop24 = "O1_P24", Prop25 = "O1_P25" },
new MyDBObject { Id = 2, Prop15 = "O2_P15", Prop21 = "test", Prop22 = "O2_P22", Prop23 = "O2_P23", Prop24 = "O2_P24", Prop25 = "O2_P25" },
new MyDBObject { Id = 3, Prop21 = "O3_P21", Prop22 = "O3_P22", Prop23 = "O3_P23", Prop24 = "test", Prop25 = "O3_P25" }
};
try
{
var predicate = CreatePredicateFromCrtieriaAndSearchTerm<MyDBObject>(null, "test");
var query = dbRecords.AsQueryable().Provider.CreateQuery<MyObject>(predicate);
List<MyObject> results = query.ToList();
foreach (var rs in results)
{
Console.WriteLine("Id: " + rs.Id);
}
}
catch (Exception ex)
{
Console.WriteLine("Error->> " + ex.Message);
}
}
Try this code:
public static Expression<Func<T, bool>> CreatePredicate<T>(List<string> propsToSearch,
string valueToSearch)
{
var parameter = Expression.Parameter(typeof(T), "record");
// filtering is not required
if (!propsToSearch.Any() || string.IsNullOrEmpty(valueToSearch))
return Expression.Lambda<Func<T, bool>>(Expression.Constant(true), parameter);
var props = typeof(T).GetProperties()
.Select(p => p.Name)
.Intersect(propsToSearch.Distinct());
var containsMethod = typeof(string).GetMethod("Contains");
var body = props
.Select(p => Expression.PropertyOrField(parameter, p))
.Aggregate((Expression) Expression.Constant(false),
(c, n) => Expression.OrElse(c,
Expression.Call(n, containsMethod, Expression.Constant(valueToSearch)))
);
var lambda = Expression.Lambda<Func<T, bool>>(body, parameter);
return lambda;
}
It return record => true if there is no properties to search or search patern is empty. QueryProvider can be smart enough to not generate sql where in this case.
Update: I created a demo (it's not working because of security restriction of dotNetFiddle, but localy works fine)

Create Default Value For Expression<Func<TDto, object>[] In Runtime By Knowing the Type Only

i want to call ProjectTo dynamically
for example on this overload:
public static IQueryable ProjectTo(this IQueryable source, params Expression<Func<TDestination, object>>[] membersToExpand);
like this
Expression.Call(typeof(AutoMapper.QueryableExtensions.Extensions), "ProjectTo", new[] { DtoType },'What goes here as arguments???')
This is the overload that i want to call
public static MethodCallExpression Call(Type type, string methodName, Type[] typeArguments, params Expression[] arguments);
Based on this link:
https://github.com/AutoMapper/AutoMapper/issues/2234#event-1175181678
Update
When i have a parameter like this
IEnumerable<Expression> membersToExpand = new Expression<Func<TDto, object>>[] { };
and pass it to the function like this it works :
Expression.Call(typeof(Extensions), "ProjectTo", new[] { typeof(TDto) }, body, Expression.Constant(membersToExpand));
But when the membersToExpand parameter is null it throw the exception
No generic method 'ProjectTo' on type 'AutoMapper.QueryableExtensions.Extensions' is compatible with the supplied type arguments and arguments. No type arguments should be provided if the method is non-generic
So let me clear The Question:
The 'membersToExpand' have to be not null (OK i know that), when it's not null the 'Expression.Call' can find the suitable overload and it will success But the problem is when the 'membersToExpand' is null so i have to create the default 'membersToExpand' and i have to create it dynamically, i can't create it like this
Expression.Constant(null, typeof(Expression<Func<TDto, object>>[]))
How to create this dynamicaly:
typeof(Expression<Func<TDto, object>>[]
when i just know the 'typeof(TDto)' ??
To reproduce the problem Just copy and Paste and then Run:
class Program
{
static void Main(string[] args)
{
Mapper.Initialize(cfg =>
{
cfg.CreateMap<UserGroupDto, UserGroup>();
cfg.CreateMap<UserGroup, UserGroupDto>()
.ForMember(dest => dest.UserGroupDetails, conf =>
{
conf.MapFrom(ent => ent.UserGroupDetails);
conf.ExplicitExpansion();
});
cfg.CreateMap<UserGroupDetailDto, UserGroupDetail>();
cfg.CreateMap<UserGroupDetail, UserGroupDetailDto>()
.ForMember(dto => dto.UserGroupName, conf => conf.MapFrom(ent => ent.UserGroup.Title));
});
// Someone Wants To Call WITH Members To Expand
Start(new Expression<Func<UserGroupDto, object>>[] { e => e.UserGroupDetails });
Console.WriteLine("===============================================");
// Someone Wants To Call WITHOUT Members To Expand (It's a pain to pass an empty 'new Expression<Func<UserGroupDto, object>>[] { }' so it's better to create the default, dynamically when it's null )
Start(new Expression<Func<UserGroupDto, object>>[] { });
// Someone Wants To Call WITHOUT Members To Expand and pass it null and it will THROW Exception
// Start(null);
Console.ReadLine();
}
static void Start(IEnumerable<Expression> membersToExpand)
{
using (var db = new MyDbContext())
{
IEnumerable projected = CallProjectToDynamically(db.UserGroups.AsQueryable(), typeof(UserGroupDto), membersToExpand);
foreach (var item in projected.OfType<UserGroupDto>())
{
Console.WriteLine(" UserGroupID => " + item.ID);
Console.WriteLine(" UserGroupTitle => " + item.Title);
if (item.UserGroupDetails != null)
item.UserGroupDetails.ToList().ForEach(d =>
{
Console.WriteLine(" UserGroupDetailID => " + d.ID);
Console.WriteLine(" UserGroupDetailTitle => " + d.Title);
Console.WriteLine(" UserGroupDetailUserGroupName => " + d.UserGroupName);
});
}
}
}
static IQueryable CallProjectToDynamically<T>(IQueryable<T> source, Type dtoType, IEnumerable<Expression> membersToExpand)
{
// What i want is this:
// if(membersToExpand == null)
// Create The default dynamically For 'params Expression<Func<TDestination, object>>[] membersToExpand'
var param = Expression.Parameter(typeof(IQueryable<T>), "data");
var body = Expression.Call(typeof(Extensions), "ProjectTo", new[] { dtoType }, param, Expression.Constant(membersToExpand));
return (Expression.Lambda<Func<IQueryable<T>, IQueryable>>(body, param).Compile())(source);
}
}
public partial class MyDbContext : DbContext
{
public MyDbContext()
: base("name=MyDbContext")
{
Database.SetInitializer(new MyDbContextDBInitializer());
}
public virtual DbSet<UserGroup> UserGroups { get; set; }
public virtual DbSet<UserGroupDetail> UserGroupMembers { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<UserGroup>()
.HasMany(e => e.UserGroupDetails)
.WithRequired(e => e.UserGroup)
.HasForeignKey(e => e.UserGroupID);
}
}
public class MyDbContextDBInitializer : DropCreateDatabaseAlways<MyDbContext>
{
protected override void Seed(MyDbContext context)
{
IList<UserGroup> defaultStandards = new List<UserGroup>
{
new UserGroup() { Title = "First UserGroup", UserGroupDetails = new [] { new UserGroupDetail { Title = "UGD for First UserGroup" } } },
new UserGroup() { Title = "Second UserGroup", UserGroupDetails = new [] { new UserGroupDetail { Title = "UGD for Second UserGroup" } } },
new UserGroup() { Title = "Third UserGroup", UserGroupDetails = new [] { new UserGroupDetail { Title = "UGD for Third UserGroup" } } }
};
foreach (UserGroup std in defaultStandards)
context.UserGroups.Add(std);
base.Seed(context);
}
}
public partial class UserGroup
{
public UserGroup()
{
UserGroupDetails = new HashSet<UserGroupDetail>();
}
public long ID { get; set; }
public string Title { get; set; }
public virtual ICollection<UserGroupDetail> UserGroupDetails { get; set; }
}
public partial class UserGroupDetail
{
public long ID { get; set; }
public long UserGroupID { get; set; }
public string Title { get; set; }
public virtual UserGroup UserGroup { get; set; }
}
public partial class UserGroupDto
{
public long ID { get; set; }
public string Title { get; set; }
public IEnumerable<UserGroupDetailDto> UserGroupDetails { get; set; }
}
public partial class UserGroupDetailDto
{
public long ID { get; set; }
public string Title { get; set; }
public long UserGroupID { get; set; }
public string UserGroupName { get; set; }
}
Sincerely.
var call = Expression.Call(typeof(AutoMapper.QueryableExtensions.Extensions), "ProjectTo", new[] { typeof(Bar) },
Expression.Constant(source), Expression.Constant(null, typeof(Expression<Func<Bar, object>>[])));
But I think you're wasting your time. In the latest version, you're not even allowed to pass null.
I solve this problem by wrapping code into generic metod. When I use ProjectTo<T> I don't need to pass second argument. Here is my helper, it takes the type and list of fields and returns DataTable. DB2 is DbContext.
public static class DTODataHelper
{
public static DataTable GetDataOfType(DB2 db, string[] fields, Type type)
{
var method = typeof(DTODataHelper).GetMethod("GetData").MakeGenericMethod(type);
var result = method.Invoke(null, new object[] { db, fields }) as DataTable;
return result;
}
public static DataTable GetData<T>(DB2 db, string[] fields)
{
var dtoType = typeof(T);
var source = Mapper.Configuration.GetAllTypeMaps().Where(i => i.DestinationType == dtoType).Select(i => i.SourceType).FirstOrDefault();
if (source == null)
throw new HMException("Не найден источник данных");
var dbSet = db.Set(source);
var querable = dbSet.AsQueryable();
var list = dbSet.ProjectTo<T>().ToList();
return GetDataTable(list, fields);
}
public static DataTable GetDataTable<T>(IEnumerable<T> varlist, string[] fields)
{
DataTable dtReturn = new DataTable();
PropertyInfo[] oProps = null;
var hasColumnFilter = fields != null && fields.Length > 0;
if (varlist == null) return dtReturn;
foreach (T rec in varlist)
{
if (oProps == null)
{
oProps = rec.GetType().GetProperties();
var excludedProperties = new List<PropertyInfo>();
foreach (PropertyInfo pi in oProps)
{
if (hasColumnFilter && !fields.Contains(pi.Name))
{
excludedProperties.Add(pi);
continue;
}
Type colType = pi.PropertyType;
if (colType.IsGenericType && colType.GetGenericTypeDefinition() == typeof(Nullable<>))
{
colType = colType.GetGenericArguments()[0];
}
var piName = pi.GetCustomAttributes().OfType<DisplayNameAttribute>().FirstOrDefault()?.DisplayName ?? pi.Name;
dtReturn.Columns.Add(new DataColumn(piName, colType));
}
if (excludedProperties.Count > 0)
{
oProps = oProps.Except(excludedProperties).ToArray();
}
}
DataRow dr = dtReturn.NewRow();
foreach (PropertyInfo pi in oProps)
{
var piName = pi.GetCustomAttributes().OfType<DisplayNameAttribute>().FirstOrDefault()?.DisplayName ?? pi.Name;
dr[piName] = pi.GetValue(rec, null) == null ? DBNull.Value : pi.GetValue(rec, null);
}
dtReturn.Rows.Add(dr);
}
return dtReturn;
}
}

C# Lambda Expressions Using Complex Objects

How can I use a Lambda Expression on a List of Payment objects that have a List PaymentFields object. Here's what I currently have.
var list = paymentList.Where(payment => payment.PaymentFields.Any(field => field.FieldName == "ItemA" && field.FieldValue == "50");
That gives me payments that have ItemA as the Field Name and 50 as the Field Value. However, I want to COMPARE the two PaymentFields like so...
Where FieldName == "ItemA" && FieldValue = "50" && FieldName ItemA < FieldName ItemB
How would I do this?
I have two Objects:
public class Payment
{
public int Id { set; get; }
public string Name { set; get; }
public List<PaymentFields> PaymentFields { set; get; }
}
public class PaymentFields
{
public string FieldName { set; get; }
public string FieldValue { set; get; }
}
Here is an example Object:
var payment = new Payment()
{
Id = 1,
Name = "Test",
PaymentFields = new List<PaymentFields>()
{
new PaymentFields()
{
FieldName = "ItemA",
FieldValue = "20"
},
new PaymentFields()
{
FieldName = "ItemB",
FieldValue = "50"
}
}
};
Thanks for your help!
If you are absolutely sure there will be an "ItemA" and "ItemB", then this will work.
If either "ItemA" or "ItemB" is missing, it will throw an exception.
var list = paymentList
.Where(payment => payment.PaymentFields.Any(field => field.FieldName == "ItemA" && field.FieldValue == "50")
.Where(payment => payment.PaymentFields.First(field => field.FieldName == "ItemA").FieldValue
<
payment.PaymentFields.First(field => field.FieldName == "ItemB").FieldValue)
);

How to deep copy members of a strongly typed collection

I have two classes XmlPerson and Person, each class has public properties, no methods nor any fields.
How would I deep copy all the properties from Person to XmlPerson? I dont want to use a third-party library like MiscUtil.PropertyCopy or Automapper. I have managed to copy the "first-level" properties that are primitive types and strongly typed objects but when it comes the List I have no idea.
The structure of the Person class is below:
public class Person
{
public string FirstName { get; set; }
public string Surname { get; set; }
public decimal? Salary { get; set; }
public List<AddressDetails> AddressDetails { get; set; }
public NextOfKin NextOfKin { get; set; }
}
public class NextOfKin
{
public string FirstName { get; set; }
public string Surname { get; set; }
public string ContactNumber { get; set; }
public List<AddressDetails> AddressDetails { get; set; }
}
public class AddressDetails
{
public int HouseNumber { get; set; }
public string StreetName { get; set; }
public string City { get; set; }
}
thank u for the help.
charles
Here is the what I have so far:
public class XmlTestCaseToClassMapper
{
internal TTarget MapXmlClassTotargetClass(TSource xmlPerson)
{
var targetObject = Activator.CreateInstance();
var sourceObject = Activator.CreateInstance();
//var xmlClassProperties = xmlPerson.GetType().GetProperties().ToList().OrderBy(x => x.Name);
var xmlClassProperties = GetProperties(xmlPerson.GetType());
//var targetClassProperties = targetObject.GetType().GetProperties().ToList().OrderBy(x => x.Name);
var targetClassProperties = GetProperties(targetObject.GetType());
PropertyInfo targetClassProperty = null;
foreach (var xmlProperty in xmlClassProperties)
{
if (!xmlProperty.PropertyType.IsClass || xmlProperty.PropertyType.UnderlyingSystemType == typeof(string)
|| xmlProperty.PropertyType.IsPrimitive)
{
targetClassProperty = targetClassProperties.ToList().FirstOrDefault(x => x.Name == xmlProperty.Name);
var propertyValue = xmlProperty.GetValue(xmlPerson, null);
targetClassProperty.SetValue(targetObject, propertyValue, null);
}
else if (xmlProperty.PropertyType.UnderlyingSystemType == typeof(NextOfKin)) //Check subType of the property
{
var subPropertyInstance = Activator.CreateInstance(xmlProperty.GetType());
var subProperties = GetProperties(xmlProperty.GetType());
subProperties.ForEach(subProperty =>
{
targetClassProperty = targetClassProperties.ToList().FirstOrDefault(x => x.Name == subProperty.Name && x.GetType().IsClass);
targetClassProperty.SetValue(subPropertyInstance, xmlProperty.GetValue(this, null), null);
});
}
//else if (xmlProperty.PropertyType.IsGenericType)
//{
// var xmlGenericType = xmlProperty.PropertyType.GetGenericArguments().First();
// var xmlGenericTypeProperties = GetProperties(xmlGenericType);
// targetClassProperty = targetClassProperties.ToList().FirstOrDefault(x => x.Name == xmlProperty.Name);
// var targetGenericType = targetClassProperty.PropertyType.GetGenericArguments().First();
// var targetGenericProperties = GetProperties(targetGenericType);
// Type targetGenericList = typeof(List<>).MakeGenericType(new Type[] { targetGenericType });
// object listInstance = Activator.CreateInstance(targetGenericList);
// //foreach (var xmlGenericProperty in xmlGenericTypeProperties)
// //{
// // var targetGenericProperty = targetGenericProperties.FirstOrDefault(x => x.Name == xmlGenericProperty.Name);
// // targetGenericProperty.SetValue(targetGenericProperty, xmlGenericProperty.GetValue(xmlGenericType, null), null);
// //}
// xmlGenericTypeProperties.ForEach(x =>
// {
// foreach (var targetGenericProperty in targetGenericProperties)
// {
// targetGenericProperty.SetValue(targetGenericProperty, targetGenericProperty.GetValue(x, null), null);
// }
// });
//}
//}
}
return targetObject;
}
private List<PropertyInfo> GetProperties(Type targetType)
{
var properties = new List<PropertyInfo>();
targetType.GetProperties(BindingFlags.Instance | BindingFlags.Public).ToList().ForEach(property =>
{
properties.Add(property);
});
return properties.OrderBy(x => x.Name).ToList();
}
}
Here is a possible solution. It probably requires some tweaks based on your actual classes.
public T DeepCopy<S, T>(S source) where T : new()
{
var sourceProperties = typeof(S).GetProperties(BindingFlags.Instance | BindingFlags.Public);
T target = new T();
foreach (var sourceProperty in sourceProperties)
{
var property = typeof(T).GetProperty(sourceProperty.Name);
if (property.PropertyType.IsPrimitive ||
property.PropertyType == typeof(string) ||
(property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>)))
{
object value = sourceProperty.GetValue(source);
property.SetValue(target, value);
}
else if (property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(List<>))
{
var sourceList = (IEnumerable)sourceProperty.GetValue(source);
if (sourceList != null)
{
var deepCopy = this.GetType().GetMethod("DeepCopy").MakeGenericMethod(sourceProperty.PropertyType.GenericTypeArguments[0], property.PropertyType.GenericTypeArguments[0]);
var ctor = property.PropertyType.GetConstructor(Type.EmptyTypes);
IList targetList = (IList) ctor.Invoke(null);
foreach (var element in sourceList)
{
targetList.Add(deepCopy.Invoke(this, new object[] { element } ));
}
property.SetValue(target, targetList);
}
}
else
{
var value = sourceProperty.GetValue(source);
if (value != null)
{
var deepCopy = this.GetType().GetMethod("DeepCopy").MakeGenericMethod(sourceProperty.PropertyType, property.PropertyType);
property.SetValue(target, deepCopy.Invoke(this, new object[] { value }));
}
}
}
return target;
}

Create predicate with nested classes with Expression

I have this :
public class Company
{
public int Id { get; set; }
public string Name { get; set; }
}
public class City
{
public int Id { get; set; }
public string Name { get; set; }
public int ZipCode { get; set; }
}
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int? Age { get; set; }
public City City { get; set; }
public Company Company { get; set; }
}
I'd like a some case generate the predicate like this :
var result = listPerson.Where(x => x.Age == 10).ToList<>();
Or this :
var result = listPerson.Where( x => x.Company.Name == 1234).ToList();
Or this :
var result = listPerson.Where( x => x.City.ZipCode == "MyZipCode").ToList();
Or this :
var result = listPerson.Where( x => x.Company.Name == "MyCompanyName").ToList();
Then I created a "PredicateBuilder", that's work (I get the type, if nullable or not and I build the predicate) when I do this :
BuildPredicate<Person>("Age", 10); I get this : x => x.Age == 10
But I don't how manage when there is an nested property like this :
BuildPredicate<Person>("City.ZipCode", "MyZipCode");
I'd like get this : x => x.City.ZipCode == "MyZipCode"
Or this :
BuildPredicate<Person>("City.Name", "MyName");
I'd like get this : x => x.City.Name == "MyName"
Or this :
BuildPredicate<Person>("Company.Name", "MyCompanyName");
I'd like get this : x => x.Company.Name == "MyCompanyName"
(not intending to duplicate Jon - OP contacted me to provide an answer)
The following seems to work fine:
static Expression<Func<T,bool>> BuildPredicate<T>(string member, object value) {
var p = Expression.Parameter(typeof(T));
Expression body = p;
foreach (var subMember in member.Split('.')) {
body = Expression.PropertyOrField(body, subMember);
}
return Expression.Lambda<Func<T, bool>>(Expression.Equal(
body, Expression.Constant(value, body.Type)), p);
}
The only functional difference between that and Jon's answer is that it handles null slightly better, by telling Expression.Constant what the expected type is. As a demonstration of usage:
static void Main() {
var pred = BuildPredicate<Person>("City.Name", "MyCity");
var people = new[] {
new Person { City = new City { Name = "Somewhere Else"} },
new Person { City = new City { Name = "MyCity"} },
};
var person = people.AsQueryable().Single(pred);
}
You just need to split your expression by dots, and then iterate over it, using Expression.Property multiple times. Something like this:
string[] properties = path.Split('.');
var parameter = Expression.Parameter(typeof(T), "x");
var lhs = parameter;
foreach (var property in properties)
{
lhs = Expression.Property(lhs, property);
}
// I've assumed that the target is a string, given the question. If that's
// not the case, look at Marc's answer.
var rhs = Expression.Constant(targetValue, typeof(string));
var predicate = Expression.Equals(lhs, rhs);
var lambda = Expression.Lambda<Func<T, bool>>(predicate, parameter);

Categories