I'm driving myself crazy trying to understand Expressions in LINQ. Any help is much appreciated (even telling me that I'm totally off base here).
Let's say I have three classes
public class Person
{
public string Name { get; set;}
public IEnumerable<PersonLocation> Locations { get; set;}
public IEnumerable<PersonEducation> Educations { get; set:}
}
public class PersonLocation
{
public string Name { get; set;}
public string Floor { get; set;}
public string Extension { get; set;}
}
public class PersonEducation
{
public string SchoolName { get; set;}
public string GraduationYear { get; set;}
}
I'm trying to create a method that takes in a string, such as Locations.Name or Locations.Floor, or Educations.SchoolName which will then create a dynamic linq query
IEnumerable<Person> people = GetAllPeople();
GetFilteredResults(people, "Location.Name", "San Francisco");
GetFilteredResults(people, "Location.Floor", "17");
GetFilteredResults(people, "Educations.SchoolName", "Northwestern");
This GetFilteredResults(IEnumerable<Person> people, string ModelProperty, string Value) method should create an expression that is roughly equivalent to people.Where(p => p.Locations.Any(pl => pl.Name == Value);
I have this working if ModelProperty is a string, i.e. people.Where(p => p.Name == Value) looks like this:
string[] modelPropertyParts = ModelProperty.Split('.');
var prop = typeof(Person).GetProperty(modelPropertyParts[0]);
var sourceParam = Expression.Parameter(typeof(Person), "person");
var expression = Expression.Equal(Expression.PropertyOrField(sourceParam, modelPropertyParts[0]), Expression.Constant(option.Name));
var whereSelector = Expression.Lambda<Func<Person, bool>>(orExp, sourceParam);
return people.Where(whereSelector.Compile());
Here's what I have been playing around with for an IEnumerable type, but I just can't get the inner Any, which seems correct, hooked into the outer Where:
/*i.e. modelPropertyParts[0] = Locations & modelPropertyParts[1] = Name */
string[] modelPropertyParts = ModelProperty.Split('.');
var interiorProperty = prop.PropertyType.GetGenericArguments()[0];
var interiorParameter = Expression.Parameter(interiorProperty, "personlocation");
var interiorField = Expression.PropertyOrField(interiorParameter, modelPropertyParts[1]);
var interiorExpression = Expression.Equal(interiorField, Expression.Constant(Value));
var innerLambda = Expression.Lambda<Func<PersonLocation, bool>>(interiorExpression, interiorParameter);
var outerParameter = Expression.Parameter(typeof(Person), "person");
var outerField = Expression.PropertyOrField(outerParameter, modelPropertyParts[0]);
var outerExpression = ??
var outerLambda == ??
return people.Where(outerLambda.Compile());
The problem is that System.Linq.Enumerable.Any is a static extension method.
Your outerExpression must reference System.Linq.Enumerable.Any(IEnumerable<T>, Func<T, bool>):
var outerExpression = Expression.Call(
typeof(System.Linq.Enumerable),
"Any",
new Type[] { outerField.Type, innerLambda.Type },
outerField, innerLambda);
Take a look at these links for more information:
MSDN Expression.Call(Type, String, Type[], params Expression[])
Some helpful, similar examples.
Related
With the following data structure.
public class Contact
{
public string Firstname { get; set; }
public string Lastname { get; set; }
public List<Phone> Phones { get; set; }
}
public class Phone
{
public string AreaCode { get; set; }
public string PhoneNumber { get; set; }
public bool IsMobile { get; set; }
}
I've created a simple utility function to build a custom Term query. I'm trying to define the fieldExpression using a string. I would like to subtitude "p.Phone.First().PhoneNumber" with a stringExpression parameter instead. Is that possible?
private TermQuery BuildTermQuery(string stringExpression, string value)
{
// Expression<Func<Contact, string>> fieldExpression = p => p.Phone.First().PhoneNumber;
Expression<Func<Contact, string>> fieldExpression = p => $"{stringExpression}";
var query = new TermQuery
{
Field = new Field(fieldExpression),
Value = value
};
return query;
}
If there is other way to accomplish this please let me know. Thanks for your help in advance🙂
Using an Expression<Func<T, TProp>> lambda expression to access a member of T is intended to help with typed access to properties, so passing an expression string kinda defeats the purpose :)
It's possible to just pass a string to Field constructor, or use the implicit conversion from string to Field
Field f = "my_field";
Field f2 = new Field("my_field_2");
If you know what casing convention you're using, you could use nameof() in conjunction with a property name, then case accordingly. For example, for camel case
Field f3 = nameof(ElasticContact.Property).ToCamelCase();
public class ElasticContact
{
public string Property {get;set;}
}
public static class Extensions
{
public static string ToCamelCase(this string s)
{
if (string.IsNullOrEmpty(s) || char.IsLower(s, 0))
return s;
var array = s.ToCharArray();
array[0] = char.ToLowerInvariant(array[0]);
return new string(array);
}
}
EDIT
For a nested object field, a string can still be passed
Field f = "phone.phoneNumber";
In your example, you could just pass the expression into the method, and avoid strings
private static void Main()
{
var query = BuildTermQuery<Contact, string>(c => c.Phones.First().PhoneNumber, "123456");
}
private static TermQuery BuildTermQuery<T, TProp>(Expression<Func<T, TProp>> fieldExpression, string value)
{
var query = new TermQuery
{
Field = new Field(fieldExpression),
Value = value
};
return query;
}
If you really want to build an member expression from a string expression, you could reference Microsoft.CodeAnalysis.CSharp.Scripting and evaluate the expression string into an expression delegate.
Presentation
I have a ContactProfileModel entity class with some properties :
FirstName
LastName
BirthDate etc..
I have other Entities who have a ContactProfileModel foreignkey. Example : RegistrationModel.Contact.
Needs
I would like to create a method whith the Following signature :
public static Expression<Func<TModel, string>> Contact<TModel>(Expression<Func<TModel, ContactProfileModel>> contact)
And use it this way :
DisplayQuery.Contact<RegistrationModel>(m => m.ContactProfile))
As an equivalent of
m => m.ContactProfile.FirstName + " " + m.ContactProfile.FirstName + " " + m.ContactProfile.BirthDate.ToShortTimeString()
Objective
The objective is to return a linq query where result is a string and contains different informations of the contact. Example : "John Doe (10/10/90)"
Note
I have discussed with some people who told me to use Expression.Call and Expression.Property but unfortunatly I do not have enough knowledge to use it properly.
Here I expose my problem without extra details, but I have my reasons to create my method only this way.
Thanks in advance.
Here's a full working implementation: the code runs and outputs what you'd expect.
I'm slightly short on time, so I'm going to leave it as this. If you want clarification, ask in the comments and I'll do my best to answer.
public class Program
{
private static readonly MethodInfo stringConcatMethod = typeof(string).GetMethod("Concat", new[] { typeof(string[]) });
private static readonly MethodInfo toShortTimeStringMethod = typeof(DateTime).GetMethod("ToShortTimeString");
private static readonly PropertyInfo firstNameProperty = typeof(ContactProfileModel).GetProperty("FirstName");
private static readonly PropertyInfo lastNameProperty = typeof(ContactProfileModel).GetProperty("LastName");
private static readonly PropertyInfo birthDateProperty = typeof(ContactProfileModel).GetProperty("BirthDate");
public static void Main()
{
var result = Contact<RegistrationModel>(x => x.ContactProfile);
// Test it
var model = new RegistrationModel()
{
ContactProfile = new ContactProfileModel()
{
FirstName = "First",
LastName = "Last",
BirthDate = DateTime.Now,
}
};
var str = result.Compile()(model);
}
public static Expression<Func<TModel, string>> Contact<TModel>(Expression<Func<TModel, ContactProfileModel>> contact)
{
// We've been given a LambdaExpression. It's got a single
// parameter, which is the 'x' above, and its body
// should be a MemberExpression accessing a property on
// 'x' (you might want to check this and throw a suitable
// exception if this isn't the case). We'll grab the
// body of the LambdaExpression, and use that as the
// 'm.ContactProfile' expression in your question.
// At the end, we'll construct a new LambdaExpression
// with our body. We need to use the same ParameterExpression
// given in this LambdaExpression.
var modelParameter = contact.Parameters[0];
var propertyAccess = (MemberExpression)contact.Body;
// <contact>.FirstName
var firstNameAccess = Expression.Property(propertyAccess, firstNameProperty);
// <contact>.LastName
var lastNameAccess = Expression.Property(propertyAccess, lastNameProperty);
// <contact>.BirthDate
var birthDateAccess = Expression.Property(propertyAccess, birthDateProperty);
// <contact>.BirthDate.ToShortTimeString()
var birthDateShortTimeStringCall = Expression.Call(birthDateAccess, toShortTimeStringMethod);
// string.Concat(new string[] { <contact>.FirstName, " ", etc }
var argsArray = Expression.NewArrayInit(typeof(string), new Expression[]
{
firstNameAccess,
Expression.Constant(" "),
lastNameAccess,
Expression.Constant(" "),
birthDateShortTimeStringCall
});
var concatCall = Expression.Call(stringConcatMethod, argsArray);
// Turn it all into a LambdaExpression
var result = Expression.Lambda<Func<TModel, string>>(concatCall, modelParameter);
// Note: if you inspect 'result.DebugView' in a debugger at this
// point, you'll see a representation of the expression we've built
// up above, which is useful for figuring out where things have gone
// wrong.
return result;
}
}
public class ContactProfileModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime BirthDate { get; set; }
}
public class RegistrationModel
{
public ContactProfileModel ContactProfile { get; set; }
}
It might be that EF doesn't like the call to String.Concat - in that case, you might have to use a set of Expression.Add calls there instead.
First answer on StackOverflow, so be kind ;)
I tried to solve the problem, but expressions are not easy to work with. Thanks canton7 for the answer. I edited my answer in order to show the solution if you want to use the .ToString() method in expressions.
public class ContactProfileModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime BirthDate { get; set; }
public override string ToString()
{
return $"{FirstName} {LastName} {BirthDate.ToShortDateString()}";
}
}
public class RegistrationModel
{
public ContactProfileModel ContactProfile { get; set; }
}
public class Program
{
static void Main(string[] args)
{
var registration = new RegistrationModel
{
ContactProfile = new ContactProfileModel
{
FirstName = "John",
LastName = "Doe",
BirthDate = DateTime.Now
}
};
var expression = Contact<RegistrationModel>(m => m.ContactProfile);
Console.WriteLine(expression.Compile()(registration));
Console.ReadKey();
}
public static Expression<Func<TModel, string>> Contact<TModel>(Expression<Func<TModel, ContactProfileModel>> contact)
{
var propertyAccess = (MemberExpression)contact.Body;
var toString = typeof(ContactProfileModel).GetMethod("ToString");
var toStringValue = Expression.Call(propertyAccess, toString);
return Expression.Lambda<Func<TModel, string>>(toStringValue, contact.Parameters[0]);
}
}
I have a linq query where I’d like to dynamically select only the fields requested by my user.
Currently I’m mapping my Jobs to a data transformation object like this:
var jobs = (from p in jobsDB
select new JobReportDTO()
{
JobID = p.JobID,
EventType = p.EventType,
DateApproved = p.ApprovedDate,
DateEntered = p.EnteredDate,
DateClosed = p.ClosedDate,
StartDate = p.StartDate,
FinishDate = p.FinishDate,
InsuredName = p.InsuredName,
StreetAddress = p.StreetAddress,
Suburb = p.Suburb,
State = p.State,
Postcode = p.Postcode,
.... etc
Within this function I have a number of boolean variables that identify whether that field should be sent to the view, i.e.:
public bool ShowInsuredName { get; set; }
public bool ShowSuburb { get; set; }
public bool ShowICLA { get; set; }
public bool ShowClaimNumber { get; set; }
public bool ShowFileMananger { get; set; }
public bool ShowSupervisor { get; set; }
public bool ShowStatus { get; set; }
... etc
How can I modify my linq query to show selected fields only?
I’ve tried
var jobs = (from p in jobsDB
select new JobReportDTO()
{
JobID = p.JobID,
jobReport.ShowEventType == true ? EventType = p.EventType : "",
... etc
But am getting “invalid initialiser member declarator”
If you can afford LINQ method syntax and use strong naming convention for options like public bool Show{DTOPropertyName} { get; set; }, then you can make your life much easier with the help of the System.Linq.Expressions and the following little helper method
public static class MyExtensions
{
public static IQueryable<TResult> Select<TSource, TResult>(this IQueryable<TSource> source, Expression<Func<TSource, TResult>> selector, object options)
{
var memberInit = (MemberInitExpression)selector.Body;
var bindings = new List<MemberBinding>();
foreach (var binding in memberInit.Bindings)
{
var option = options.GetType().GetProperty("Show" + binding.Member.Name);
if (option == null || (bool)option.GetValue(options)) bindings.Add(binding);
}
var newSelector = Expression.Lambda<Func<TSource, TResult>>(
Expression.MemberInit(memberInit.NewExpression, bindings), selector.Parameters);
return source.Select(newSelector);
}
}
What it does is to remove the assignments which has associated ShowProperty with value set to false.
The usage is simple
var jobs = jobsDB.Select(p => new JobReportDTO
{
JobID = p.JobID,
EventType = p.EventType,
DateApproved = p.ApprovedDate,
DateEntered = p.EnteredDate,
DateClosed = p.ClosedDate,
StartDate = p.StartDate,
FinishDate = p.FinishDate,
InsuredName = p.InsuredName,
StreetAddress = p.StreetAddress,
Suburb = p.Suburb,
State = p.State,
Postcode = p.Postcode,
.... etc
}, jobReport);
If you set a breakpoint in the debugger and examine the newSelector variable, you'll see that only properties that do not have ShowProperty (like JobID) or have ShowProperty = true are included.
Try this way:
EventType = jobReport.ShowEventType == true ? p.EventType : string.Empty,
I am looking for a generic Filter for the searchText in the query of any Column/Field mapping
public static IQueryable<T> Filter<T>(this IQueryable<T> source, string searchTerm)
{
var propNames = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public).Where(e=>e.PropertyType == typeof(String)).Select(x => x.Name).ToArray();
//I am getting the property names but How can I create Expression for
source.Where(Expression)
}
Here I am giving you an example scenario
Now From my HTML5 Table in Asp.net MVC4 , I have provided a Search box to filter results of entered text , which can match any of the below columns/ Menu class Property values , and I want to do this search in Server side , how can I implement it.
EF Model Class
public partial class Menu
{
public int Id { get; set; }
public string MenuText { get; set; }
public string ActionName { get; set; }
public string ControllerName { get; set; }
public string Icon { get; set; }
public string ToolTip { get; set; }
public int RoleId { get; set; }
public virtual Role Role { get; set; }
}
You can use Expressions:
private static Expression<Func<T, bool>> GetColumnEquality<T>(string property, string term)
{
var obj = Expression.Parameter(typeof(T), "obj");
var objProperty = Expression.PropertyOrField(obj, property);
var objEquality = Expression.Equal(objProperty, Expression.Constant(term));
var lambda = Expression.Lambda<Func<T, bool>>(objEquality, obj);
return lambda;
}
public static IQueryable<T> Filter<T>(IQueryable<T> source, string searchTerm)
{
var propNames = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Where(e => e.PropertyType == typeof(string))
.Select(x => x.Name).ToList();
var predicate = PredicateBuilder.False<T>();
foreach(var name in propNames)
{
predicate = predicate.Or(GetColumnEquality<T>(name, searchTerm));
}
return source.Where(predicate);
}
Combined with the name PredicateBuilder from C# in a NutShell. Which is also a part of LinqKit.
Example:
public class Foo
{
public string Bar { get; set; }
public string Qux { get; set; }
}
Filter<Foo>(Enumerable.Empty<Foo>().AsQueryable(), "Hello");
// Expression Generated by Predicate Builder
// f => ((False OrElse Invoke(obj => (obj.Bar == "Hello"), f)) OrElse Invoke(obj => (obj.Qux == "Hello"), f))
void Main()
{
// creates a clause like
// select * from Menu where MenuText like '%ASD%' or ActionName like '%ASD%' or....
var items = Menu.Filter("ASD").ToList();
}
// Define other methods and classes here
public static class QueryExtensions
{
public static IQueryable<T> Filter<T>(this IQueryable<T> query, string search)
{
var properties = typeof(T).GetProperties().Where(p =>
/*p.GetCustomAttributes(typeof(System.Data.Objects.DataClasses.EdmScalarPropertyAttribute),true).Any() && */
p.PropertyType == typeof(String));
var predicate = PredicateBuilder.False<T>();
foreach (var property in properties )
{
predicate = predicate.Or(CreateLike<T>(property,search));
}
return query.AsExpandable().Where(predicate);
}
private static Expression<Func<T,bool>> CreateLike<T>( PropertyInfo prop, string value)
{
var parameter = Expression.Parameter(typeof(T), "f");
var propertyAccess = Expression.MakeMemberAccess(parameter, prop);
var like = Expression.Call(propertyAccess, "Contains", null, Expression.Constant(value,typeof(string)));
return Expression.Lambda<Func<T, bool>>(like, parameter);
}
}
You need to add reference to LinqKit to use PredicateBuilder and AsExpandable method
otherwise won't work with EF, only with Linq to SQL
If you want Col1 like '%ASD%' AND Col2 like '%ASD%' et, change PredicateBuilder.False to PredicateBuilder.True and predicate.Or to predicate.And
Also you need to find a way to distinguish mapped properties by your own custom properties (defined in partial classes for example)
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);