How to convert Lambda Expressions from diferent domains? - c#

I'm trying to convert a Lambda expression from a domain to another.
This is what I receive as parameter:
Expression<Func<Entities.UserRight, bool>> expression
And I should return ane expression of type
Expression<Func<UserRight,bool>>
Both UserRight and Entities.UserRight are same models, but belongs to diferent nugget packages.
I'm trying by:
public Expression<Func<UserRight,bool>> ConvertExpression(Expression<Func<Entities.UserRight, bool>> expression)
{
var resultBody = Expression.Convert(expression.Body, typeof(UserRight));
var result = Expression.Lambda<Func<UserRight, bool>>(resultBody, expression.Parameters);
return result;
}
But I receive an error of InvalidOperationException

Suppose we have two class Address1 and Address2 like bellow:
class Address1
{
public string City { get; set; }
public string Detail { get; set; }
}
class Address2
{
public string City { get; set; }
public string Detail { get; set; }
}
and whe have an expression for Address1 like this:
Expression<Func<Address1, string>> getCityName = ad1 => ad1.City;
We can change the expression to be usable for Address2 by an ExpressionVisitor:
public class ExpressionConvertor: ExpressionVisitor
{
private ParameterExpression ad2Parameter;
public ExpressionConvertor(ParameterExpression ad2Parameter)
{
this.ad2Parameter = ad2Parameter;
}
public override Expression Visit(Expression node)
{
if(node is LambdaExpression node2)
{
var exp = base.Visit(node2.Body);
return Expression.Lambda(exp, ad2Parameter);
}
return base.Visit(node);
}
protected override Expression VisitMember(MemberExpression node)
{
if(node.Expression is ParameterExpression)
return Expression.PropertyOrField(ad2Parameter, node.Member.Name);
return base.VisitMember(node);
}
}
and finally we use this class like this:
Expression<Func<Address1, string>> getCityName = ad1 => ad1.City;
var ad2Parameter = Expression.Parameter(typeof(Address2), "ad2");
var convertor = new ExpressionConvertor(ad2Parameter);
var getCityName2 = (Expression<Func<Address2, string>>)convertor.Visit(getCityName);
var getCityName2Method = getCityName2.Compile();
var addr2 = new Address2()
{
City = "MyCity",
Detail = "My Address Detail"
};
var cityName = getCityName2Method(addr2);
Console.WriteLine(cityName); //MyCity

Related

Linq expression orderBy on child collections

I've got this class with a collection of revisions:
public class Client : BaseEntity
{
public virtual ICollection<Draw> Draws { get; set; }
public virtual ICollection<ClientRevision> ClientRevisions { get; set; }
}
public class ClientRevision : BaseEntity
{
public Guid ClientId { get; set; }
public Client Client { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public double? Increase { get; set; }
public string RevisionCode { get; set; }
public string RevisionNote { get; set; }
}
On controller, I'd like to retrieve the list of clients with the last revision available. I've got a base repository with the following get method:
public async Task<IEnumerable<T>> GetAsync(
Expression<Func<T, bool>> filter = null,
Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null,
IEnumerable<Expression<Func<T, object>>> includeProperties = null,
int? pageIndex = null,
int? itemsPerPage = null,
bool ignoreQueryFilter = true,
bool ignoreTracking = true)
{
var query = DataContext.GetData<T>(ignoreTracking);
if (filter != null)
{
query = query.Where(filter);
}
if (orderBy != null)
{
query = orderBy(query);
}
if (ignoreQueryFilter)
{
query = query.IgnoreQueryFilters();
}
query = ApplyIncludePropertiesIfNeeded(includeProperties, query);
if (pageIndex.HasValue && itemsPerPage.HasValue)
{
query = query
Skip(pageIndex.Value * itemsPerPage.Value)
Take(itemsPerPage.Value + 1);
}
return await query.ToListAsync();
}
I'd like to order by the list by the Name of the client; the name is in the Client Revision. I try to implement the following filter and orderby, but I cannot create a correct linq expression for retrive the Name of the Client.
public async Task<ActionResult<ClientResponse>> GetClientListAsync()
{
var result = new List<ClientResponse>();
List<Expression<Func<Client, object>>> includes = new() { i => i.Draws };
var sortOn = "ClientRevisions.Name";
var param = Expression.Parameter(typeof(Client), "client");
var parts = sortOn.Split('.');
Expression parent = param;
foreach (var part in parts)
{
parent = Expression.Property(parent, part);
}
var sortExpression = Expression.Lambda<Func<Client, object>>(parent, param);
Func<IQueryable<Client>, IOrderedQueryable<Client>> order = o => o.OrderBy(sortExpression);
}
The error is the following:
Instance property 'Name' is not defined for type 'System.Collections.Generic.ICollection`1[ClientRevision]' (Parameter 'propertyName')
Because is a list. How to retrieve this data from a list? Thanks

How to create an expression getting a property dynamically using LinqExpression in c#?

I am trying to create a linq expression getting dynamically a property from a Student object based on a SearchFilter object parameter.
The problem is that Expression.Property(expr, string) asks for a string for the property name. And I cannot retrieve the string from the ParameterExpression. I tried this but I am stuck... The cast as a (string) off course does not work. Any clue on how to proceed instead ?
Check the code below:
using System;
using System.Linq.Expressions;
namespace ConsoleApp2
{
class Program
{
static void Main(string[] args)
{
var student = new Student
{
FirstName = "John",
Address = new Address
{
City = "New York",
ZipCode = 10005
}
};
var filter = new SearchFilter
{
Rule = "ZipCode"
};
var studentParamExpr = Expression.Parameter(typeof(Student), "s");
var filterParamExpr = Expression.Parameter(typeof(SearchFilter), "filter");
var lambdaExpr = Expression.Lambda(
CreateStudentAddressRuleAccessor(studentParamExpr, filterParamExpr),
new ParameterExpression[] { studentParamExpr, filterParamExpr });
var expression = lambdaExpr.Compile().DynamicInvoke(student, filter);
}
public static Expression CreateStudentAddressRuleAccessor(ParameterExpression student, ParameterExpression filter)
{
var ruleExpr = Expression.Property(filter, "Rule");
string rule = (string)ruleExpr;
var resultExpr = Expression.Property(Expression.Property(student, "Address"), rule);
return resultExpr;
}
}
public class Student
{
public string FirstName { get; set; }
public Address Address { get; set; }
}
public class Address
{
public string City { get; set; }
public int ZipCode { get; set; }
}
public class SearchFilter
{
public string Rule { get; set; }
}
}
Lambda body does not require all of their arguments to be expressions.
Remove the filter from the list of lambda params and turn the type of the filter arg to SearchFilter like this:
static void Main(string[] args)
{
...
var studentParamExpr = Expression.Parameter(typeof(Student), "s");
var lambdaExpr = Expression.Lambda(
CreateStudentAddressRuleAccessor(studentParamExpr, filter),
new ParameterExpression[] { studentParamExpr });
var expression = lambdaExpr.Compile().DynamicInvoke(student);
Console.WriteLine($"Lambda expression = {lambdaExpr}");
Console.WriteLine($"Expression = {expression}");
}
public static Expression CreateStudentAddressRuleAccessor(ParameterExpression student, SearchFilter filter)
{
var resultExpr = Expression.Property(Expression.Property(student, "Address"), filter.Rule);
return resultExpr;
}

dynamically build select clause linq

class someClass
{
public int Id { get; set; }
public string Name{ get; set; }
...
public string someProperty { get; set; }
}
Expression<Func<someClass, object>> selector = null;
selector = k => new { k.Id ,k.Name };
var serult = myData.Select(selector);
// .Select(p=> new {p.Name , p.Id}) etc.
This sample code is working
But;
Expression<Func<someClass, ???>> createSelector(string[] fields)
{
...
....
return ...
}
Expression<Func<someClass, ???>> selector = createSelector({"Name","Id"});
Is this possible?
This method when running create dynamic selector
This can be used to create the expression you need.
public static Expression<Func<T, TReturn>> CreateSelector<T, TReturn>(string fieldName)
where T : class
where TReturn : class
{
ParameterExpression p = Expression.Parameter(typeof(T), "t");
Expression body = Expression.Property(p, fieldName);
Expression conversion = Expression.Convert(body, typeof(object));
return Expression.Lambda<Func<T, TReturn>>(conversion, new ParameterExpression[] { p });
}

Linq IQueryable Generic Filter

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)

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