I have a class
public class Person
{
public int PersonId { get; set; }
public string Name { get; set; }
}
And I have a lambda expression of the Person type
Expression<Func<TModel, TProperty>> expression
Who contains this value
{model => model.Name}
How can I evaluate that lambda expression against an instance of Person, to extract the Name value attribute?
You can compile the expression into a delegate and pass in a Person object:
Func<Person, string> getName = expression.Compile();
string name = getName(person);
Using Expression trees:
http://msdn.microsoft.com/en-us/library/bb397951.aspx
http://blogs.msdn.com/b/charlie/archive/2008/01/31/expression-tree-basics.aspx
Related
I have an entity called User in my EF Model:
public class User
{
public int UserId { get; set; }
public Branch HomeLocation{ get; set; }
public string CellPhone { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
public string Email { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string UserCode { get; set; }
}
Branch is another entity in the model:
public class Branch
{
public int BranchId { get; set; }
public string BranchName{ get; set; }
public string Address { get; set; }
}
My requirement is to get a users list and display it on a grid, and then sort the list by some of the columns (one at a time). Say, for example, sort by username, firstname, lastname and HomeLocation. When sorting by homelocation, it should be sorted by the branch name.
I have many grids like this displaying other data as well. So I want to develop a generic sort mechanism and I have achieved it using some of the examples found in Google, for example this one:
public class GenericSorter<T>
{
public IEnumerable<T> Sort(IEnumerable<T> source, string sortBy, string sortDirection)
{
var param = Expression.Parameter(typeof(T), "item");
var sortExpression = Expression.Lambda<Func<T, object>>
(Expression.Convert(Expression.Property(param, sortBy), typeof(object)), param);
switch (sortDirection.ToLower())
{
case "asc":
return source.AsQueryable<T>().OrderBy<T, object>(sortExpression);
default:
return source.AsQueryable<T>().OrderByDescending<T, object>(sortExpression);
}
}
}
However, sorting by home location fails because it needs to be sorted by an inner property of the user entity. I've tried using Dynamic LINQ library too, but there's no luck.
Update: Note that I have to sort a List, not IQueryable, because my list contains fields encrypted with AE, which don't support DB-level sorting.
Can someone point out to me how to achieve the dynamic sorting from an inner property?
Update2: I followed the example and implemented the sort using the extension methods and this is how it's applied on my list:
var users = (from u in context.Users.Include("Branch")
where (u.FkBranchId == branchId || branchId == -1) && u.IsActive
&& (searchTerm == string.Empty || (u.FirstName.Contains(searchTerm) || u.LastName.Equals(searchTerm)
|| u.UserName.Contains(searchTerm) || u.UserCode.Contains(searchTerm)))
select u).ToList();
var rowCount = users.Count;
var orderedList = users.OrderBy(sortInfo.SortColumn).Skip(pageInfo.Skip).Take(pageInfo.PageSize).ToList();
But I get the following error:
Object of type 'System.Linq.Expressions.Expression1[System.Func2[ClientData.User,System.String]]' cannot be converted to type 'System.Func`2[ClientData.User,System.String]'.
Error is thrown from the following:
object result = typeof(Enumerable).GetMethods().Single(
method => method.Name == methodName
&& method.IsGenericMethodDefinition
&& method.GetGenericArguments().Length == 2
&& method.GetParameters().Length == 2)
.MakeGenericMethod(typeof(T), type)
.Invoke(null, new object[] { source, lambda });
After this, I'm receiving the following error, in some occasions as explained in the comment:
Adapt the code from #MarcGravell found here.
public static class EnumerableExtensions
{
public static IOrderedEnumerable<T> OrderBy<T>(
this IEnumerable<T> source,
string property)
{
return ApplyOrder<T>(source, property, "OrderBy");
}
public static IOrderedEnumerable<T> OrderByDescending<T>(
this IEnumerable<T> source,
string property)
{
return ApplyOrder<T>(source, property, "OrderByDescending");
}
public static IOrderedEnumerable<T> ThenBy<T>(
this IOrderedEnumerable<T> source,
string property)
{
return ApplyOrder<T>(source, property, "ThenBy");
}
public static IOrderedEnumerable<T> ThenByDescending<T>(
this IOrderedEnumerable<T> source,
string property)
{
return ApplyOrder<T>(source, property, "ThenByDescending");
}
static IOrderedEnumerable<T> ApplyOrder<T>(
IEnumerable<T> source,
string property,
string methodName)
{
string[] props = property.Split('.');
Type type = typeof(T);
ParameterExpression arg = Expression.Parameter(type, "x");
Expression expr = arg;
foreach (string prop in props)
{
// use reflection (not ComponentModel) to mirror LINQ
PropertyInfo pi = type.GetProperty(prop);
expr = Expression.Property(expr, pi);
type = pi.PropertyType;
}
Type delegateType = typeof(Func<,>).MakeGenericType(typeof(T), type);
LambdaExpression lambda = Expression.Lambda(delegateType, expr, arg);
object result = typeof(Enumerable).GetMethods().Single(
method => method.Name == methodName
&& method.IsGenericMethodDefinition
&& method.GetGenericArguments().Length == 2
&& method.GetParameters().Length == 2)
.MakeGenericMethod(typeof(T), type)
.Invoke(null, new object[] { source, lambda.Compile() });
return (IOrderedEnumerable<T>)result;
}
}
UPDATED
Use it from a List<> :
var list = new List<MyModel>();
list = list.OrderBy("MyProperty");
I need to implement filter function with expression parameter.
So i can't apply filtered query to entity.
Entity :
[XmlRoot(ElementName = "Zip")]
public class Zip
{
[XmlAttribute(AttributeName = "code")]
public string Code { get; set; }
}
[XmlRoot(ElementName = "District")]
public class District
{
[XmlElement(ElementName = "Zip")]
public List<Zip> Zip { get; set; }
[XmlAttribute(AttributeName = "name")]
public string Name { get; set; }
}
[XmlRoot(ElementName = "City")]
public class City
{
[XmlElement(ElementName = "District")]
public List<District> District { get; set; }
[XmlAttribute(AttributeName = "name")]
public string Name { get; set; }
[XmlAttribute(AttributeName = "code")]
public string Code { get; set; }
}
[XmlRoot(ElementName = "AddressInfo")]
public class AddressInfo
{
[XmlElement(ElementName = "City")]
public List<City> City { get; set; }
}
Test case filtered by City name "Berlin". How can apply predicate with function.
public IConverter<T> Filter(Expression<Func<T, bool>> predicate)
{
// ???
return this;
}
I presume you need to filter a collection with a given predicate.
You can define a Filter extension method that takes a predicate as an argument (or simply rely on the already existing collection.Where extension method)
public static class Extensions
{
public static IEnumerable<T> Filter<T>(this IEnumerable<T> collection, Func<T, bool> predicate)
{
return collection.Where(predicate);
}
}
define predicates based on your needs
// Filter by city Berlin
Func<City, bool> berlin = city => city.Name == "Berlin";
// Filter by district Spandau
Func<City, bool> spandau = city => city.Districts.Any(d => d.Name == "Spandau");
// Filter by zip 10115
Func<City, bool> zipcode = city =>
{
var predicate = from district in city.Districts
from zip in district.Zips
where zip.Code == "10115"
select zip;
return predicate.Any();
};
filter the data based on given predicate
var query = from address in addresses
from city in address.Cities.Filter(berlin)
select city;
I am using Range Validator to validate the date. My requirement is validate the input date should be between current date and current date -60 years.
This is what I tried so far,
[Range(typeof(DateTime),DateTime.UtcNow.ToString(),DateTime.UtcNow.AddYears(-60).Date.ToString())]
public DateTime? DOJ { get; set; }
But this throws error : An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type.
So I modified my code:
#region variables
public const string MaxDate = DateTime.UtcNow.ToString();
public const string MinDate = DateTime.UtcNow.AddYears(-60).Date.ToString();
#endregion
And Set property :
[Range(typeof(DateTime),maximum:MaxDate,minimum:MinDate)]
public DateTime? DOJ { get; set; }
Now the error is :The expression being assigned to 'MaxDate' must be constant.
Same for MinDate.
What's the solution?
You can't use variables in Attributes. All items in attributes must be constant. If you want to filter value based on dynamic values, then you can make new ValidationAttribute like this:
public class ValidateYearsAttribute : ValidationAttribute
{
private readonly DateTime _minValue = DateTime.UtcNow.AddYears(-60);
private readonly DateTime _maxValue = DateTime.UtcNow;
public override bool IsValid(object value)
{
DateTime val = (DateTime)value;
return val >= _minValue && val <= _maxValue;
}
public override string FormatErrorMessage(string name)
{
return string.Format(ErrorMessage, _minValue, _maxValue);
}
}
And then you just need to place it on your property:
[ValidateYears]
public DateTime? DOJ { get; set; }
You can update FormatErrorMessage based on what you need.
I have a List array called taskItems
public class TaskItem
{
public int Intnum { get; set; }
public int ID { get; set; }
public int TaskID { get; set; }
public string Name { get; set; }
public string Value { get; set; }
}
taskItems.Find(x => (x.Name == "function")).Value
I am trying to use the Assign component in windows workflow to assign the above lambda expression to a string variable. This string variable is in FlowChart. It won't take it. The same lamdba expression works if I use it in code.
You're using C# syntax. Workflow expressions are VB only. The equivalent syntax in VB should be:
taskItems.Find(Function(t As TaskItem) t.Name = "function").Value
Awesome! that worked great. I had to make a little change though.
taskItems.Find(Function(t As TaskItem) t.Name = "function").Value
I didn't know that it's vb only. Thanks for that too.
I am using DataAnnotations for validation (including client side)
I have a form with multiple fields. Basic validation for individual fields work fine. Now there are a couple of fields of which atleast one needs to have a value (if there are 3 fields then either 1st or 2nd or 3rd field should have a value).
I have read quite a few posts on this site and couple of blog entries. But I couldn't find a solution that works in the above mentioned scenario. I might have missed something or doing it incorrectly.
Can you help with this please?
try this
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
public sealed class EitherOr : ValidationAttribute
{
private const string _defaultErrorMessage = "'{0}' OR '{1}' OR '{2}' must have a value";
private readonly object _typeId = new object();
public EitherOr(string prop1, string prop2, string prop3)
: base(_defaultErrorMessage)
{
Prop1 = prop1;
Prop2 = prop2;
Prop3 = prop3;
}
public string Prop1 { get; private set; }
public string Prop2 { get; private set; }
public string Prop3 { get; private set; }
public override object TypeId
{
get
{
return _typeId;
}
}
public override string FormatErrorMessage(string name)
{
return String.Format(CultureInfo.CurrentUICulture, ErrorMessageString, Prop1, Prop2,Prop3);
}
public override bool IsValid(object value)
{
if(string.IsNullOrEmpty(Prop1)&&string.IsNullOrEmpty(Prop2) && string.IsNullOrEmpty(Prop3))
{
return false;
}
return true;
}
then mark your class with the EitherOr attribute:
[EitherOr("Bar","Stool","Hood", ErrorMessage = "please supply one of the properties")]
public class Foo
{
public string Bar{ get; set;}
public string Stool{ get; set;}
public string Hood{ get; set;}
}
Please note that i made use of string properties, if your property is of other type, makle sure to change the IsValid(object value) validation