is it possible to construct MethodCallExpression (by using Expression.Call), when this call is NESTED? so, in design time, the actual sequnce (IQueryable or IEnumerable) which the call should be run against, is unknown?
Example:
let's say this is the query:
var result = data.GroupBy(x => x.Name).Select(grouping => grouping.OrderByDescending(x => x.Date).Take(1).FirstOrDefault()).ToList();
when data is a list of objects contains:Name,Date properties.
How do i constructs this query block:
grouping => grouping.OrderByDescending(x => x.Date)
using Expression.call?
thanks
My Answer:
I managed to sort this out.
Aducci gave me the direction, thnks!
the key was to use ParameterExpression and not an actual collection instance.
From here, the sulotion is just around the corner: complie the Expression>, and call the compiled result with the actual collection.
this was very helpful: MethodCallExpression -> compiled expression
and also : link
i'm adding my final code:
Expression<Func<Data,DateTime>> lmbd = x => x.Date;
ParameterExpression par = Expression.Parameter(typeof(IQueryable<Data>),"pname");
ParameterExpression[] parameters = new ParameterExpression[] {par};
MethodCallExpression method = Expression.Call(typeof(Queryable),"OrderBy",new Type[]{typeof(Data),typeof(DateTime)},par, lmbd);
var lambaExpression = Expression.Lambda<Func<IQueryable<Data>, IQueryable<Data>>>(method, par);
var compiled = lambaExpression.Compile();
Thank you all!
I asked a similar question which can be found here
ParameterExpression innerParameter = Expression.Parameter(typeof(GROUPING), "x");
Expression innerExpression = Expression.Lambda<Func<DATA, object>>(Expression.Property(innerParameter, "Date"),
innerParameter);
ParameterExpression parameter = Expression.Parameter(typeof(DATA), "grouping");
Expression call = Expression.Call(typeof(Enumerable),
"OrderByDescending",
new type[] { typeof(DATA) },
parameter,
innerExpression);
Related
In the code below I want to replace x.BaseSalary with any other property whose name is stored in feildtoretrive:
var feildtoretrive = "BaseSalary"
var groupcal = db.Employees.GroupBy(x=>x.Region)
.Select(group => new {
Key = group.Key,
Avg = group.Average(x => x.BaseSalary)
})
.ToList();
You need to
create a MemberExpression that accesses that member of an instance
compile a lambda expression that returns that member for a passed instance parameter
(I assume that the elements in Employees are of type Employee)
// the parameter expression for the lambda (your 'x')
var parameter = Expression.Parameter(typeof(Employee));
// the property access expression (your 'x.BaseSalary' or 'x.<feildtoretrive')
var propAccess = Expression.PropertyOrField(parameter, feildtoretrive);
// the build-together and compiled lambda
var expression = (Expression<Func<Employee, int?>>)Expression.Lambda(propAccess, parameter);
You can now use lambda for the x.Average call:
new { Key = group.Key, Avg = group.Average(lambda) }
A caveat: This only works now for members of type int?. I'm lacking a little experience on how to do this more type independent, but what types can you calculate an average over? But if there are int or double members, another cast expression maybe necessary.
EDIT: (changed the return type to int?). According to Ivan Stoev's comment on my follow up question you could try this:
new { Key = group.Key, Avg = group.AsQueryable().Average(expression) }
EF6 should recognize the call to AsQueryable() and then use the correct Average method (note that I use the expression as argument instead of the lambda). EF Core and linq2Sql won't work with that.
I would like to build a method that determines if a given URL is a child of one of a number of URL's in a List. I thought of approaching this using Linq but the syntax seems beyond my understanding. Here is what I have attempted and I would expect isChild == true.
List<Uri> ProductionUriList = new List<Uri>(){
new Uri(#"https://my.contoso.com/sites/Engineering",UriKind.Absolute),
new Uri(#"https://my.contoso.com/sites/APAC",UriKind.Absolute),
new Uri(#"https://my.contoso.com/sites/China",UriKind.Absolute),
new Uri(#"https://my.contoso.com/sites/EMEA",UriKind.Absolute),
new Uri(#"https://my.contoso.com/sites/India",UriKind.Absolute),
new Uri(#"https://my.contoso.com/sites/Mexico",UriKind.Absolute),
new Uri(#"https://my.contoso.com/sites/SamCam",UriKind.Absolute),
new Uri(#"https://my.contoso.com/sites/USA",UriKind.Absolute),
};
var isChild =
ProductionUriList.SelectMany (p => p.IsBaseOf(new Uri("https://my.contoso.com/sites/China/Site1",UriKind.Absolute)));
The runtime error says:
The type arguments for method
'System.Linq.Enumerable.SelectMany(System.Collections.Generic.IEnumerable,
System.Func>)'
cannot be inferred from the usage. Try specifying the type arguments
explicitly.
If you just want to check for a bool condition on a set you can use the any operator:
var isChild = ProductionUriList.Any(p => p.IsBaseOf(new Uri("https://my.contoso.com/sites/China/Site1", UriKind.Absolute)));
Concerning your error: The selectmany operator expects a delegate that returns IEnumerable which you do not supply. You are mixing up select and selectmany. If you choose select as the linq operator you could do a count > 0 on the result which would give the same result as using the any operator:
var isChild = ProductionUriList.Select(p => p.IsBaseOf(new Uri("https://my.contoso.com/sites/China/Site1", UriKind.Absolute)).Count > 0;
To determine if the uri is a child of one or more:
var isChild = ProductionUriList.Any(p => p.IsBaseOf(newUri("https://my.contoso.com/sites/China/Site1",UriKind.Absolute)));
To determine if the uri is a child of exactly one:
var isChild = ProductionUriList.Count(p => p.IsBaseOf(newUri("https://my.contoso.com/sites/China/Site1",UriKind.Absolute))) == 1;
I am trying to programmatically generate an expression tree (that will eventually get used in Linq-to-entity framework).
I can get the query to work just fine with one exception - it does not parametrize the query - which I want for Sql Server query plan reuse.
I read that in order for the generated sql to be parametrized, the expression needs to compare based on a variable. However, I cannot figure out how to assign the value to the variable in the expression tree. If I just use Expression.Constantit works (but is not parametrized).
So basically:
public Expression<Func<T, bool>> FooEquals<T>(
Expression<Func<T, int>> propertyExpression, int value)
{
ParameterExpression param = propertyExpression.Parameters.Single();
int targetValueForEqualityComparison = 9;
//There's some "special sauce" here which is why I don't
//use propertyExpression directly
var body = Expression.Property(param, GetPropertyName(propertyExpression));
//If I just use Expression.Constant, it works, but doesn't parametrize.
//var equalExpression = ParameterExpression.Equal(body,
// Expression.Constant(targetValueForEqualityComparison, typeof(int)));
var variable = Expression
.Variable(typeof(int), "targetValueForEqualityComparison");
var assigned = Expression.Assign(variable,
Expression.Constant(targetValueForEqualityComparison, typeof(int)));
//throws InvalidOperaitonException: "The parameter was not bound in the
//specified Linq to Entities query expression
var equalExpression = ParameterExpression.Equal(body, variable);
//throws NotSupportedException: "Unknown LINQ expression of type 'Assign'.
var equalExpression = ParameterExpression.Equal(body, assigned);
return Expression.Lambda<Func<T, bool>>(equalExpression, param);
}
How do I properly bind a value to the variable expression so that Linq-to-EntityFramework will parametrize the query?
I went ahead and tried it out because I was curious. The following seem to result in identical SQL, at least when used with Linq-to-SQL (which LINQPad does more readily than EF). I'd imagine it should work just the same with EF though.
Seems like a pretty convoluted way to just pass an integer in, but because this is what the compiler generates for a plain lambda function, I guess this is what the SQL generator looks for.
// Given this class, which is equivalent to the compiler-generated class
class Holder {
public int AnInteger;
}
int id = 1;
// You get the same SQL with a plain lambda function
var query = db.Items.Where(i => i.Id == id);
// or with a handwritten expression:
var arg = Expression.Parameter(typeof(Item), "i");
var paramHolder = new Holder { AnInteger = id };
// essentially, (i) => i.Id == paramHolder.AnInteger
var lambda = Expression.Lambda<Func<Item, bool>>(
Expression.Equal(
Expression.Property(arg, "Id"),
Expression.Field(
Expression.Constant(paramHolder), "AnInteger")),
arg);
// the SQL this translates to is equivalent to that of the first query
var query2 = db.Items.Where(lambda);
am a newbie in linq.. am stuck with one scenario. ie,
i have to sort the search results based on user input.
user inputs are Last Name, First Name and Title. for input 3 drop downs are there and i have to sort result based on the values selected.
i tried
order = Request["orders"].Split(',');
var param = order[0];
var p1 = typeof(Test).GetProperty(param);
param = order[1];
var p2 = typeof(Test).GetProperty(param);
param = order[2];
var p3 = typeof(Test).GetProperty(param);
model.Test = (from tests in model.Test
select tests).
OrderBy(x => p1.GetValue(x, null)).
ThenBy(x => p2.GetValue(x, null)).
ThenBy(x => p3.GetValue(x, null));
but it doesn't works.
i want qry like this
from tests in model.Test
select tests).OrderBy(x => x.lastname).
ThenBy(x => x.firstname).ThenBy(x => x.Title);
order[0]== lastname but how can i use it in the place of OrderBy(x => x.order[0])..?
Thanks in advance.
i solved my case as follows
// list of columns to be used for sorting
List<string>order = Request["orders"].Split(',').ToList();
//map the column string to property
var mapp = new Dictionary<string, Func<Test, string>>
{
{"FirstName", x => x.FirstName},
{"LastName", x => x.LastName},
{"SimpleTitle", x => x.SimpleTitle}
};
//user inputted order
var paras = new List<Func<Test, string>>();
foreach (var para in order)
{
if(!string.IsNullOrEmpty(para))
paras.Add(mapp[para]);
}
//sorting
model.Test= model.Test.OrderBy(paras[0]).ThenBy(paras[1]).ThenBy(paras[2]);
Thanks all,
Actually you are looking for dynamic linq query than you can try out Dynamic LINQ (Part 1: Using the LINQ Dynamic Query Library)
which allow to do like this
it means you can dynamically pass string propertyname to short you collection in orderby function
You can also read about : Dynamic query with Linq
You can compose the expression (any Expression) manually from pieces and then append it to the previous part of query. You can find more info, with example in "Sorting in IQueryable using string as column name".
I have the following snippet that I currently use to run a .Contains() with a list of Ids passed as a comma separated list from users. This code works perfectly and the data is filtered exactly as I want it to be:
// Handle id in() statements explicitly, dynamic expression can't parse them
var idIn = new Regex("id in ?(.*)", RegexOptions.IgnoreCase);
if (idIn.IsMatch(predicate))
{
Match list = new Regex(#"in ?\((.*)\)", RegexOptions.IgnoreCase).Match(predicate);
string ins = list.Groups[1].ToString();
// Split ins and store as List<>
List<int> splitValues = ins.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries).Select(i => Convert.ToInt32(i)).ToList();
return source.Where(u => splitValues.Contains(u.Id));
}
I want to be able to use this same idea, except with ANY property of the u object using reflection. I had a version of this working at some point, but cannot for the life of me figure out what has changed or why it stopped working. Here is the version I have that I cannot get working again:
Match splitIn = new Regex(#"([a-zA-Z0-9\.]*) IN ?\((.*)\)", RegexOptions.IgnoreCase).Match(predicate);
string property = splitIn.Groups[1].ToString();
string ins = splitIn.Groups[2].ToString().Trim(new[] {'\'', '"'}); // Trim off separator quotes
List<string> splitValues = ins.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries).ToList();
for (int i = 0; i < splitValues.Count; i++)
{
splitValues[i] = splitValues[i].Trim(new[] {'\'', '"'});
}
Expression<Func<U, bool>> contains = u => ListContainsProperty(u, splitValues, property);
return source.Where(contains);
private static bool ListContainsProperty<U>(U u, ICollection<string> list, string property)
{
string[] split = property.Split(new[] {"."}, StringSplitOptions.RemoveEmptyEntries);
object value = split.Aggregate<string, object>(u, (current, prop) => current.GetType().GetProperty(prop).GetValue(current, null));
return list.Contains(value.ToString());
}
As I said I once had SOME version of this working, but cannot figure out what has changed. Is there something blatantly obvious that I am missing that would help me get this functional again?
Edit: As far as I can tell the ListContainsProperty method is never actually running. Adding a "throw new Exception()" does nothing. I just get the full unfiltered list back.
I think the underlying problem is using "Expression"
you need to compile an Expression.
For example in your code
Expression<Func<U, bool>> contains = u => ListContainsProperty(u, splitValues, property);
is data and not a function. In order to use it you need to compile it.
Func<U, bool> compiled = contains.Compile();
"compiled" variable will call the "ListContainsProperty" method.