I have a class that looks like this:
public class MyClass
{
public int Id { get; set; }
public string Name { get; set; }
}
I need to do this:
var memberExpressions = ConvertToMemberExpressions<MyClass>(o => o.Id, o => o.Name);
...
public static List<MemberExpression> ConvertToMemberExpressions<T>(params Expression<Func<T, object>>[] methodExpressions)
{
var results = new List<MemberExpression>();
foreach(var methodExpression in methodExpressions)
{
var memberExpression = methodExpression.Body as MemberExpression;
results.Add(memberExpression);
}
return results;
}
The problem is that because of Func<T, object> (to be able to include both int and string as parameters) my methodExpression looks like this: {o => Convert(o.Id, Object)}, which is not what I need. I need to reach o.Id.
This does not happen with strings: {o => o.Name}, no conversion here.
I use Func<T,object> to be able to take advantage of Intellisense and reach the props of MyClass. I have tried using Func<T, dynamic> instead, but the result is the same.
It could be solved using multiple overloads:
public static ConvertToMemberExpressions<TClass, T1>(Expression<Func<TClass,T1>>[] methodExpression1)
public static ConvertToMemberExpressions<TClass, T1, T2>(Expression<Func<TClass,T1>>[] methodExpression1, Expression<Func<TClass,T2>>[] methodExpression2)
...
...but it is a sacrifice I would like to avoid if possible.
The question:
Is it possible to build o => o.Id from o => Convert(o.Id, Object) ?
Just check whether your methodExpression.Body is a UnaryExpression with a NodeType of Convert:
public static List<MemberExpression> ConvertToMemberExpressions<T>(params Expression<Func<T, object>>[] methodExpressions)
{
var results = new List<MemberExpression>();
foreach (var methodExpression in methodExpressions)
{
var expr = methodExpression.Body;
if (expr is UnaryExpression unaryExpression && unaryExpression.NodeType == ExpressionType.Convert)
{
expr = unaryExpression.Operand;
}
if (expr is MemberExpression memberExpression)
{
results.Add(memberExpression);
}
else
{
throw new ArgumentException($"Unexpected expression type {expr.NodeType}");
}
}
return results;
}
When working with expressions, the debugger is your friend:
Related
I have two DbSets, Foo and Bar. Foo has an identifying string property, FooName, and Bar has an identifying string property, BarName.
I am designing a very simple search feature, where a user's query term can either be equal to, or contained in the identifying name.
So I have two methods (heavily simplified):
public ActionView SearchFoo(string query)
{
var equalsQuery = db.Foo.Where(f => f.FooName.Equals(query));
var containsQuery = db.Foo.Where(f => f.FooName.Contains(query)).Take(10); // Don't want too many or else a search for "a" would yield too many results
var result = equalsQuery.Union(containsQuery).ToList();
... // go on to return a view
}
public ActionView SearchBar(string query)
{
var equalsQuery = db.Bar.Where(f => f.BarName.Equals(query));
var containsQuery = db.Bar.Where(f => f.BarName.Contains(query)).Take(10); // Don't want too many or else a search for "a" would yield too many results
var result = equalsQuery.Union(containsQuery).ToList();
... // go on to return a view
}
Clearly I want some helper method like so:
public IList<T> Search<T>(string query, DbSet<T> set)
{
var equalsQuery = set.Where(f => ???.Equals(query));
var containsQuery = set.Where(f => ???.Contains(query)).Take(10); // Don't want too many or else a search for "a" would yield too many results
var result = equalsQuery.Union(containsQuery).ToList();
... // go on to return a view
}
I originally tried to add a Func<T, string> to the Search parameters, where I could use f => f.FooName and b => b.BarName respectively, but LINQ to Entities doesn't support a lambda expression during the execution of the query.
I've been scratching my head as to how I can extract this duplication.
You can achieve this with Expression<Funt<T,string>>
public IList<T> Search<T>(string query, DbSet<T> set, Expression<Func<T, string>> propExp)
{
MethodInfo method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
ConstantExpression someValue = Expression.Constant(query, typeof(string));
MethodCallExpression containsMethodExp =
Expression.Call(propExp.Body, method, someValue);
var e = (Expression<Func<T, bool>>)
Expression.Lambda(containsMethodExp, propExp.Parameters.ToArray());
var containsQuery = set.Where(e).Take(10);
BinaryExpression equalExpression = Expression.Equal(propExp.Body, someValue);
e = (Expression<Func<T, bool>>)
Expression.Lambda(equalExpression, propExp.Parameters.ToArray());
var equalsQuery = set.Where(e);
var result = equalsQuery.Union(containsQuery).ToList();
}
Then you'll call it:
Search ("myValue", fooSet, foo=>foo.FooName);
if you can have a static method, then you could have it as an extension method:
public static IList<T> Search<T>(this DbSet<T> set,
string query, Expression<Func<T, string>> propExp)
And call it:
FooSet.Search ("myValue", foo=>foo.FooName);
Here's one way to do it.
First you need a helper method to generate the Expression for you:
private Expression<Func<T, bool>> GetExpression<T>(string propertyName, string propertyValue, string operatorName)
{
var parameterExp = Expression.Parameter(typeof(T));
var propertyExp = Expression.Property(parameterExp, propertyName);
MethodInfo method = typeof(string).GetMethod(operatorName, new[] { typeof(string) });
var someValue = Expression.Constant(propertyValue, typeof(string));
var methodExp = Expression.Call(propertyExp, method, someValue);
return Expression.Lambda<Func<T, bool>>(methodExp, parameterExp);
}
This is how you can use this method, propertyName would be FooName and BarName:
public IList<T> Search<T>(string propertyName, string query, DbSet<T> set)
{
var equalsQuery = set.Where(GetExpression<T>(propertyName, query, "Equals"));
var containsQuery = set.Where(GetExpression<T>(propertyName, query, "Contains")).Take(10); // Don't want too many or else a search for "a" would yield too many results
var result = equalsQuery.Union(containsQuery).ToList();
return result;
}
You can create interface:
public interface IName
{
string Name { get; set; }
}
Then implicitly implement IName interface in both entities.
public class Bar : IName { ... }
public class Foo : IName { ... }
And then change your method as:
public IList<T> SearchByName<T>(string query, DbSet<T> set)
where T: class, IName
{
var equalsQuery = set.Where(f => f.Name.Equals(query));
var containsQuery = set.Where(f => f.Name.Contains(query)).Take(10); // Don't want too many or else a search for "a" would yield too many results
var result = equalsQuery.Union(containsQuery).ToList();
... // go on to return a view
}
You could overide your ToString() method and use that in the query
public class foo
{
public string FooName
{
get;
set;
}
public override string ToString()
{
return FooName;
}
}
public class Bar
{
public string BarName
{
get;
set;
}
public override string ToString()
{
return BarName;
}
}
public IList<T> Search<T>(string query, DbSet<T> set)
{
var equalsQuery = set.AsEnumerable().Where(f => f.ToString().Equals(query));
var containsQuery = set.AsEnumerable().Where(f => f.ToString().Contains(query)).Take(10);
var result = equalsQuery.Union(containsQuery).ToList(); . . . // go on to return a view
}
I'm trying to do a LinQ request with a group by where a parameter is parametrizable by an Expression ( (Expression<Func<CompanyModel,TKey>> myGroupingProperty) and the other one is hard coded. But even if my code compile I get an error that linq does not support lambdas. Would you have any idea how to do this request?
Here is the code sample:
public List<timelineResult> getTimelinebyCompany<TKey>(Expression<Func<CompanyModel,TKey>> myGroupingProperty, Filter item)
{
int cntToBeSureThatTheQueryExecuteAtLeastOneTime = 0;
List<timelineResult> toto = new List<timelineResult>();
using (var db = new fintechDbContext())
{
while (cntToBeSureThatTheQueryExecuteAtLeastOneTime == 0)
{
toto = (from p in db.companyDBSET
select p).GroupBy(p=> new {p.Founded_Year, myGroupingProperty})
.Select(o => new timelineResult{ year = o.Key.Founded_Year, cluster = o.myGroupingProperty.ToString(), count = o.Count() })
.OrderBy(o => o.year).ToList();
cntToBeSureThatTheQueryExecuteAtLeastOneTime++;
}
}
return toto;
}
What you are seeking for is doable, but not the way you tried because the passed lambda expression cannot be used directly inside another lambda expression.
You should start first by creating a generic class to hold the grouping key. It's similar to system provided Tuple, but has parameterless constructor and simple property get/setters to conform to the EF projection rules:
public class GroupKey<K1, K2>
{
public K1 Key1 { get; set; }
public K2 Key2 { get; set; }
}
Then you need to build dynamically lambda expression like this
Expression<Func<T, K1, K2>> keySelector = x =>
new GroupKey<K1, K2> { Key1 = x.Prop1, Key2 = x.Prop2 };
In order to do that, you'll need some Expression helpers:
public static class ExpressionUtils
{
public static Expression ReplaceParameter(this Expression expression, ParameterExpression source, Expression target)
{
return new ParameterReplacer { Source = source, Target = target }.Visit(expression);
}
class ParameterReplacer : ExpressionVisitor
{
public ParameterExpression Source;
public Expression Target;
protected override Expression VisitParameter(ParameterExpression node)
{
return node == Source ? Target : base.VisitParameter(node);
}
}
}
and you can encapsulate the grouping part in a custom extension method:
public static class QueryableExtensions
{
public static IQueryable<IGrouping<GroupKey<K1, K2>, T>> GroupByPair<T, K1, K2>(this IQueryable<T> source, Expression<Func<T, K1>> keySelector1, Expression<Func<T, K2>> keySelector2)
{
var parameter = keySelector1.Parameters[0];
var key1 = keySelector1.Body;
var key2 = keySelector2.Body.ReplaceParameter(keySelector2.Parameters[0], parameter);
var keyType = typeof(GroupKey<K1, K2>);
var keySelector = Expression.Lambda<Func<T, GroupKey<K1, K2>>>(
Expression.MemberInit(
Expression.New(keyType),
Expression.Bind(keyType.GetProperty("Key1"), key1),
Expression.Bind(keyType.GetProperty("Key2"), key2)),
parameter);
return source.GroupBy(keySelector);
}
}
Finally, the essential part of your method becomes like this:
toto = db.companyDBSET
.GroupByPair(p => p.Founded_Year, myGroupingProperty)
.Select(g => new timelineResult
{
year = g.Key.Key1,
cluster = g.Key.Key2.ToString(),
count = g.Count()
})
.OrderBy(o => o.year)
.ToList();
I want to sort my columns on the base of column name which is a string but unfortunately I couldn't achieve this because OrderByDescending compares something else, but I am sending a string.
public List<DOlead> sortLead(DOuser user, string Item)
{
List<DOlead> ObjLead = new List<DOlead>();
ObjLead = _Context.leads.Where(x => x.is_converted == false).OrderByDescending(Item).ToList();
return ObjLead;
}
Kindly Help me out?
Use reflection
public List<DOlead> sortLead(DOuser user, string Item)
{
var propertyInfo = typeof(DOlead).GetProperty(Item);
List<DOlead> ObjLead = new List<DOlead>();
ObjLead = _Context.leads.Where(x => x.is_converted == false).OrderByDescending(x => propertyInfo.GetValue(x, null)).ToList();
return ObjLead;
}
Edit
After getting comment by SO, and after some research, I came across this answer and decided to modify it for user.
You have to create lambda expression first then pass it to order by clause.
Creating lambda expression.
public static class QueryableHelper
{
public static IQueryable<TModel> OrderBy<TModel>(this IQueryable<TModel> q, string name)
{
Type entityType = typeof(TModel);
PropertyInfo p = entityType.GetProperty(name);
MethodInfo m = typeof(QueryableHelper).GetMethod("OrderByProperty").MakeGenericMethod(entityType, p.PropertyType);
return(IQueryable<TModel>) m.Invoke(null, new object[] { q, p });
}
public static IQueryable<TModel> OrderByDescending<TModel>(this IQueryable<TModel> q, string name)
{
Type entityType = typeof(TModel);
PropertyInfo p = entityType.GetProperty(name);
MethodInfo m = typeof(QueryableHelper).GetMethod("OrderByPropertyDescending").MakeGenericMethod(entityType, p.PropertyType);
return (IQueryable<TModel>)m.Invoke(null, new object[] { q, p });
}
public static IQueryable<TModel> OrderByPropertyDescending<TModel, TRet>(IQueryable<TModel> q, PropertyInfo p)
{
ParameterExpression pe = Expression.Parameter(typeof(TModel));
Expression se = Expression.Convert(Expression.Property(pe, p), typeof(object));
return q.OrderByDescending(Expression.Lambda<Func<TModel, TRet>>(se, pe));
}
public static IQueryable<TModel> OrderByProperty<TModel, TRet>(IQueryable<TModel> q, PropertyInfo p)
{
ParameterExpression pe = Expression.Parameter(typeof(TModel));
Expression se = Expression.Convert(Expression.Property(pe, p), typeof(object));
return q.OrderBy(Expression.Lambda<Func<TModel, TRet>>(se, pe));
}
}
Your modified method
public List<DOlead> sortLead(DOuser user, string Item)
{
List<DOlead> ObjLead = new List<DOlead>();
ObjLead = _Context.leads.Where(x => x.is_converted == false).OrderByDescending(Item).ToList();
return ObjLead;
}
From Stack , I guess you could use reflection:
from x in db.TableName
where (x.GetType().GetProperty(stringCOLUMN_1_or2).GetValue(x, null)) == " 8"
select x;
Not sure there's any easy Linqish way to do that though...
Assuming dynamic Linq will work it would just be:
from x in objets
.Where(stringCOLUMN_1_or2 + " = ' " + 8 + "'")
select x
There is some more info about dynamic Linq with SQL here: http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx
You should use Expression builder to achieve this example simple with string properties:
public class OrderByData
{
public string PropertyName { get; set; }
}
public static class ExpressionBuilder
{
public static Expression<Func<T, string>> GetExpression<T>(OrderByData filter)
{
ParameterExpression param = Expression.Parameter(typeof(T), "t");
Expression exp = GetExpression<T>(param, filter);
return Expression.Lambda<Func<T, string>>(exp, param);
}
private static Expression GetExpression<T>(ParameterExpression param, OrderByData filter)
{
MemberExpression member = Expression.Property(param, filter.PropertyName);
return member;
}
}
and then call like this:
public List<DOlead> sortLead(DOuser user, string Item)
{
List<DOlead> ObjLead = new List<DOlead>();
new OrderByData { PropertyName = Item };
var deleg = ExpressionBuilder.GetExpression<lead>(filter).Compile();
ObjLead = _Context.leads.Where(x => x.is_converted == false).OrderByDescending(deleg).ToList();
return ObjLead;
}
also you can extend to use also other types not only strings.
You need to pass delegate to OrderByDescending method.
As you just have the property name so you need to access to the property dynamically, you can use dynamic expression to do so:
public List<DOlead> sortLead(DOuser user, string Item)
{
List<DOlead> ObjLead = new List<DOlead>();
ObjLead = _Context.leads.Where(x => x.is_converted == false).OrderByDescending((d) =>
Expression.Lambda(Expression.Property(Expression.Constant(d), Item)).Compile()()).ToList();
return ObjLead;
}
I wonder how I can store orderby expressions in a list. This is what I wanted to write:
List<Expression<Func<Products,Object>>> list = new List<Expression<Func<Products,Object>>>()
{
p => p.Name,
p => p.Id
};
Then:
var expr = list[0];
myProducts.OrderBy( expr );
which works for p.Name, but does not work for p.Id (list[1]) as it drops follwing exception
An unhandled exception of type 'System.NotSupportedException' occurred in EntityFramework.SqlServer.dll
Additional information: Unable to cast the type 'System.Int32' to type 'System.Object'. LINQ to Entities only supports casting EDM primitive or enumeration types.
What type of list do I have to use?
Here's my solution (using Reflection and based on DynamicLinq ideas):
Defining a ConvertableExpression class so we can intercept calls to our custom OrderBy():
public class ConvertableExpression<T>
{
public ConvertableExpression(Expression<Func<T, object>> expr)
{
this.Expression = expr;
}
public Expression<Func<T, object>> Expression { get; private set; }
}
Introducing an Extension-Method for easier casting from normal Expression:
public static class ExpressionExtensions
{
public static ConvertableExpression<T> AsConvertable<T>(this Expression<Func<T, object>> expr)
{
return new ConvertableExpression<T>(expr);
}
}
Extending IQueryable with Reflection-based implementation of OrderBy():
public static class QueryableExtensions
{
public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, ConvertableExpression<T> expr)
{
Expression queryExpr = source.Expression;
var exprBody = SkipConverts(expr.Expression.Body);
var lambda = Expression.Lambda(exprBody, expr.Expression.Parameters);
var quote = Expression.Quote(lambda);
queryExpr = Expression.Call(typeof(Queryable), "OrderBy", new[] { source.ElementType, exprBody.Type }, queryExpr, quote);
return (IOrderedQueryable<T>)source.Provider.CreateQuery(queryExpr);
}
private static Expression SkipConverts(Expression expression)
{
Expression result = expression;
while (result.NodeType == ExpressionType.Convert || result.NodeType == ExpressionType.ConvertChecked)
result = ((UnaryExpression)result).Operand;
return result;
}
}
Usage:
myProducts.OrderBy(expr.AsConvertable());
try this
List<Func<Products, Object>> list = new List<Func<Products, Object>>()
{
new Func<Products,Object>( p => p.Name),
new Func<Products,Object>( p => p.Id),
};
So it looks like the implementation of OrderBy for EF works by checking if < T > is a struct or object, and so you tell it to call OrderBy<..., object >(someStructTypeVariable)
As a workaround I would sugest you to store whole delegates instead of expresions.
Try this:
internal static class MyExtensions
{
public static IOrderedQueryable<TSource> OrderBy<TSource, TField>(this IQueryable<TSource> source, Expression<Func<TSource, TField>> selector, bool descending)
{
return descending
? source.OrderByDescending(selector)
: source.OrderBy(selector);
}
}
var orderers = new List<Func<IQueryable<Products>, IOrderedQueryable<Products>>>()
{
source => source.OrderBy(x => x.Id, true),
source => source.OrderBy(x => x.Id, false),
source => source.OrderBy(x => x.Name, false)
};
// To be replaced with entity source-collection.
IQueryable<Products> dummySource = new EnumerableQuery<MyType>(new List<Products>());
orderers[0](dummySource.Where(x => x.Id != 0));
I would like to use a single, general method to retrieve an ordered list for a given string representing a property inside a lambda expression.
I know people requested this before but it didn't work for me. I tried this and it threw error:
db.Books.OrderByDescending(x => x.GetType().GetProperty("Discount").GetValue(x,null))
.Take(3);
I'm using this at the moment:
public IQueryable<Book> GetCheapestBooks()
{
return db.Books.OrderBy(x => x.Discount)
.Take(3);
}
maybe this is what you are looking for:
Dynamic Linq
With this you can write queries like:
var result = db.Books.OrderBy( "Discount" ).Take( 3 );
Simple console application.
class A
{
public int prop1 { get; set; }
public int prop2 { get; set; }
}
class Program
{
static IEnumerable<T> GenericOrderByDescending<T>(IEnumerable<T> arg, string property, int take)
{
return arg.OrderByDescending(x => x.GetType().GetProperty(property).GetValue(x, null)).Take(take);
}
static void Main(string[] args)
{
IEnumerable<A> arr = new List<A>()
{
new A(){ prop1 = 1, prop2 = 2},
new A(){prop1 = 2,prop2 =2},
new A(){prop1 = 3,prop2 =2},
new A(){prop1 = 441,prop2 =2},
new A(){prop1 = 2,prop2 =2}
};
foreach(var a1 in GenericOrderByDescending<A>(arr, "prop1", 3))
{
Console.WriteLine(a1.prop1);
}
}
}
U can pass your db.Boks.AsEnumerable() as parameter for GenericOrderByDescending<T>() method. Instead of T you should type the type of your db.Boks items. My example sorts an array of instances of class A and I've got no errors, it works fine. Did I understand you correctly?
You can try with this code
public IQueryable<Book> GetCheapestBooks()
{
db.Books.OrderBy(x => x.Discount).Take(3).AsQueryable<Book>();
}
You could create an extension method which creates the property expression:
private static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, string propertyName)
{
PropertyInfo prop = typeof(T).GetProperty(propertyName);
ParameterExpression paramExpr = Expression.Parameter(typeof(T), "obj");
MemberExpression propExpr = Expression.Property(paramExpr, prop);
Type funcType = typeof(Func<,>).MakeGenericType(typeof(T), prop.PropertyType);
Type keySelectorType = typeof(Expression<>).MakeGenericType(funcType);
LambdaExpression keySelector = Expression.Lambda(funcType, propExpr, paramExpr);
MethodInfo orderByMethod = typeof(Queryable).GetMethods().Single(m => m.Name == "OrderBy" && m.GetParameters().Length == 2).MakeGenericMethod(typeof(T), prop.PropertyType);
return (IOrderedQueryable<T>) orderByMethod.Invoke(null, new object[] { source, keySelector });
}