Dynamic Select EntityFramework and System.Linq.Dynamic - c#

Im using the DynamicQueryable.Select() // dynamic select library
The only docu I have found so far is System.Linq.Dynamic docu
I have this query with anonymous return type which works.
var result = Context.Set<TPocoText>().Where((Expression<Func<TPocoText, bool>>) whereLambda)
.OrderByDescending(t => t.RowVersion).Skip(skip).Take(take)
.Select("new (m1,m2,Nav1) ");
and this works like select(t=> new {t.m1,t.m2,t.Nav1}) as expected
my Question
How can it do the equivalent of select(t=> new {t,t.Nav1})
i tried .Select("new (it,Nav1) ")
and .Select("new (this,Nav1) ")
the result was a parse error member not found.
Anybody know this dynamic string parsing API?
OR the equivalent Expression building syntax is also an option.
NOTE:The Nav Property ForSourceRecord is only known at runtime otherwise i would just use the normal lambda expression.

This cannot be done using anon types. The compiler just doesn't have the information needed to work, especially with the free text/string approach.
Try this...
var param = System.Linq.Expressions.Expression.Parameter(typeof(TPocoText));
var init = System.Linq.Expressions.Expression.MemberInit(
System.Linq.Expressions.Expression.New(typeof(Foo)),
new []{
System.Linq.Expressions.Expression.Bind(GetMemberInfo((Foo f) => f.Nav), System.Linq.Expressions.Expression.PropertyOrField(param, "NameOfPropertyToBindToNav")),
System.Linq.Expressions.Expression.Bind(GetMemberInfo((Foo f) => f.M1), System.Linq.Expressions.Expression.PropertyOrField(param, "M1")),
}
);
var result = Context.Set<TPocoText>().Where((Expression<Func<TPocoText, bool>>) whereLambda)
.OrderByDescending(t => t.RowVersion).Skip(skip).Take(take)
.Select(System.Linq.Expressions.Expression.Lambda<Func<TPocoText, Foo>>(init, param));
public class Foo
{
public string Nav {get;set;}
public string M1 {get;set;}
}
public static MemberInfo GetMemberInfo<T, U>(Expression<Func<T, U>> expression)
{
var member = expression.Body as MemberExpression;
if (member != null)
return member.Member;
throw new ArgumentException("Expression is not a member access", "expression");
}

Related

Dynamically generating nested queries

The ultimate objective is to have a piece of code that you can feed definitions of simple queries to and it generate data.
so given i can create this query
var query =
from cha in ds.Channels
select new object[]
{
cha.ChanLanguagestreamlocking,
cha.ChanMinimumtrailerduration,
from link in cha.Channelaudiolanguagelinks
select new object[]
{
link.ObjDatecreated
}
};
var data = query.ToArray();
how would i create it dynamically?
well i can get this far using a class stolen from another stack overflow question
public class SelectList<TSource>
{
private List<LambdaExpression> members = new List<LambdaExpression>();
public SelectList<TSource> Add<TValue>(Expression<Func<TSource, TValue>> selector)
{
members.Add(selector);
return this;
}
public Expression<Func<TSource, object[]>> ToDynamicColumns()
{
var parameter = Expression.Parameter(typeof(TSource), "e");
return Expression.Lambda<Func<TSource, object[]>>(
Expression.NewArrayInit(
typeof(object),
members.Select(m =>
Expression.Convert(Expression.Invoke(m, parameter), typeof(object))
)
),
parameter);
}
}
Imagine a case where the definition of which 'columns' to return is passed as a tree at runtime, this can then be mapped to predefined typessafe lambdas that define which columns to return and then the expression is created at runtime. So a hard coded version of this technique is this:
var channelColumns = new SelectList<Channel>();
channelColumns.Add(c => c.ChanLanguagestreamlocking);
channelColumns.Add(c => c.ChanMinimumtrailerduration);
var channelQuery =
ds.Channels.Select(channelColumns.ToDynamicColumns());
var bar = query.ToArray();
i.e. i can generate dynamic queries from the 'root' concept, but how do i generate the nested data.
If i do the obvious i.e. this
var audioColumns = new SelectList<Channelaudiolanguagelink>();
audioColumns.Add(a => a.ObjDatecreated);
var channelColumns = new SelectList<Channel>();
channelColumns.Add(c => c.ChanLanguagestreamlocking);
channelColumns.Add(c => c.ChanMinimumtrailerduration);
// next line causes an error
// Error CS1929 'ICollection<Channelaudiolanguagelink>' does not contain a definition for
// 'Select' and the best extension method overload
// 'Queryable.Select<Channel, object[]>(IQueryable<Channel>, Expression<Func<Channel, object[]>>)' requires a receiver of type 'IQueryable<Channel>' CSharpDb2Raw C:\Users\mark.nicholls\source\repos\scaffold2\CSharpDb2Raw\Program.cs 57 Active
channelColumns.Add(c => c.Channelaudiolanguagelinks.Select(channelColumns.ToDynamicColumns()));
var channelQuery =
ds.Channels.Select(channelColumns.ToDynamicColumns());
var bar = query.ToArray();
the error makes perfect sense.
c.Channelaudiolanguagelinks is an ICollection and so the select is looking for a Func<T,U> and I've given it an Expression<Func<T,U>>
(I don't really understand Expressions!)
Problem that you have defined method for IQueryable version of Select, so basic solution is simple - transform IEnumerable to IQueryable.
channelColumns.Add(c =>
c.Channelaudiolanguagelinks.AsQueryable().Select(audioColumns.ToDynamicColumns())
);

Dynamically generate lambda expression with constants from variables

I've been troubleshooting an unusual issue with some EF 6 code where queries running with the Oracle.ManagedDataAccess.Client driver are sometimes taking several minutes to return results even when the underlying query executes within 2ms. An example query would be as follows:
var result = users.Where(u => u.username == varUserName).FirstOrDefault();
This query might take several minutes to return, however if I replace the query with the same thing with a constant in the lambda function, it runs instantaneously:
var result = users.Where(u => u.username == "testUsername").FirstOrDefault();
To work around this issue, I can either write parameterised SQL queries, or I can manually generate an appropriate lambda expression tree:
var userParam = Expression.Parameter(typeof(Entity.User), "user");
var userNameField = Expression.Property(userParam, "username");
var userNameConstant = Expression.Constant(varUserName, typeof(string));
var equalUserName = Expression.Equal(userNameField, userNameConstant);
var lambda = Expression.Lambda<Func<Entity.User, bool>>(equalUserName, new ParameterExpression[] { userParam });
var result = users.Where(lambda).FirstOrDefault();
Because this works, it begs the question: is there a way to easily generate lambda expression trees which result in variables being directly included as constants, instead of references to variables?
For example, something like this would be ideal:
var lambdaExpression = (u => u.username == varUserName).ReplaceVariablesWithConstants();
It can be done relatively easy with ExpressionVisitor which evaluates the ConstantExpression members like this:
public static class ExpressionUtils
{
public static Expression<TDelegate> ReplaceVariablesWithConstants<TDelegate>(this Expression<TDelegate> source)
{
return source.Update(
new ReplaceVariablesWithConstantsVisitor().Visit(source.Body),
source.Parameters);
}
class ReplaceVariablesWithConstantsVisitor : ExpressionVisitor
{
protected override Expression VisitMember(MemberExpression node)
{
var expression = Visit(node.Expression);
if (expression is ConstantExpression)
{
var variable = ((ConstantExpression)expression).Value;
var value = node.Member is FieldInfo ?
((FieldInfo)node.Member).GetValue(variable) :
((PropertyInfo)node.Member).GetValue(variable);
return Expression.Constant(value, node.Type);
}
return node.Update(expression);
}
}
}
It's kinda hard. You need to modify the ExpressionsTree with a ExpressionsVisitor. It would probably become something like:
var lambdaExpression = ReplaceVariablesWithConstants(u => u.username == varUserName);

Execution-Deferred IQueryable<T> from Dynamic Linq?

I am using Dynamic Linq to perform some queries (sorry but it's my only option). As a result, I am getting an IQueryable instead of an IQueryable<T>. In my case, I want an IQueryable<Thing> where Thing is a concrete type.
My query is as such:
public IQueryable<Thing> Foo(MyContext db)
{
var rootQuery = db.People.Where(x => x.City != null && x.State != null);
var groupedQuery = rootQuery.GroupBy("new ( it.City, it.State )", "it", new []{"City", "State"});
var finalLogicalQuery = groupedQuery.Select("new ( Count() as TotalNumber, Key.City as City, Key.State as State )");
var executionDeferredResults = finalLogicalQuery.Take(10); // IQueryable
IQueryable<Thing> executionDeferredTypedThings = ??; // <--- Help here!!!!
return executionDeferredTypedThings;
}
My Thing.cs:
public class Thing
{
public int TotalNumber { get; set; }
public string City { get; set; }
public string State { get; set; }
}
Yes, I know the exact above thing can be done without Dynamic Linq but I have some variableness going on that I've simplified out of here. I can get it to work with my variableness if my return type is simply IQueryable but I can't figure out how to convert to IQueryable<Thing> while keeping it execution-deferred and while also keeping Entity Framework happy. I do have the dynamic Select always returning something (with the correct data) that looks like a Thing. But I simply can't figure how to return the IQueryable<Thing> and could use some help there. Thanks!!
Failed Attempt 1
Based on Rex M's suggestion, I am now trying to use AutoMapper to solve this problem (although I am not committed to this approach and am willing to try other approaches). For the AutoMapper approach, I am doing it as such:
IQueryable<Thing> executionDeferredTypedThings = executionDeferredResults.ProjectTo<Thing>(); // <--- Help here!!!!
But this results in an InvalidOperationException:
Missing map from DynamicClass2 to Thing. Create using Mapper.CreateMap.
The thing is, while I have defined Thing, I have not defined DynamicClass2 and as such, I cannot map it.
Failed Attempt 2
IQueryable<Thing> executionDeferredTypedThings = db.People.Provider.CreateQuery<Thing>(executionDeferredResults.Expression);
This gives an InvalidCastException and seems to be the same underlying problem that the above AutoMapper fail hits:
Unable to cast object of type 'System.Data.Entity.Infrastructure.DbQuery'1[DynamicClass2]' to type 'System.Linq.IQueryable'1[MyDtos.Thing]'.
You can use AutoMapper's Queryable Extensions to produce an IQueryable which wraps the underlying IQueryable, thus preserving the original IQueryable's IQueryProvider and the deferred execution, but adds in a mapping/translating component to the pipeline to convert from one type to another.
There's also AutoMapper's UseAsDataSource which makes some common query extension scenarios easier.
If I understand correctly, the following extension method should do the job for you
public static class DynamicQueryableEx
{
public static IQueryable<TResult> Select<TResult>(this IQueryable source, string selector, params object[] values)
{
if (source == null) throw new ArgumentNullException("source");
if (selector == null) throw new ArgumentNullException("selector");
var dynamicLambda = System.Linq.Dynamic.DynamicExpression.ParseLambda(source.ElementType, null, selector, values);
var memberInit = dynamicLambda.Body as MemberInitExpression;
if (memberInit == null) throw new NotSupportedException();
var resultType = typeof(TResult);
var bindings = memberInit.Bindings.Cast<MemberAssignment>()
.Select(mb => Expression.Bind(
(MemberInfo)resultType.GetProperty(mb.Member.Name) ?? resultType.GetField(mb.Member.Name),
mb.Expression));
var body = Expression.MemberInit(Expression.New(resultType), bindings);
var lambda = Expression.Lambda(body, dynamicLambda.Parameters);
return source.Provider.CreateQuery<TResult>(
Expression.Call(
typeof(Queryable), "Select",
new Type[] { source.ElementType, lambda.Body.Type },
source.Expression, Expression.Quote(lambda)));
}
}
(Side note: Frankly I have no idea what values argument is for, but added it to match the corresponding DynamicQueryable.Select method signature.)
So your example will become something like this
public IQueryable<Thing> Foo(MyContext db)
{
var rootQuery = db.People.Where(x => x.City != null && x.State != null);
var groupedQuery = rootQuery.GroupBy("new ( it.City, it.State )", "it", new []{"City", "State"});
var finalLogicalQuery = groupedQuery.Select<Thing>("new ( Count() as TotalNumber, Key.City as City, Key.State as State )"); // IQueryable<Thing>
var executionDeferredTypedThings = finalLogicalQuery.Take(10);
return executionDeferredTypedThings;
}
How it works
The idea is quite simple.
The Select method implementation inside the DynamicQueryable looks something like this
public static IQueryable Select(this IQueryable source, string selector, params object[] values)
{
if (source == null) throw new ArgumentNullException("source");
if (selector == null) throw new ArgumentNullException("selector");
LambdaExpression lambda = DynamicExpression.ParseLambda(source.ElementType, null, selector, values);
return source.Provider.CreateQuery(
Expression.Call(
typeof(Queryable), "Select",
new Type[] { source.ElementType, lambda.Body.Type },
source.Expression, Expression.Quote(lambda)));
}
What it does is to dynamically create a selector expression and bind it to the source Select method. We take exactly the same approach, but after modifying the selector expression created by the DynamicExpression.ParseLambda call.
The only requirement is that the projection is using "new (...)" syntax and the names and types of the projected properties match, which I think fits in your use case.
The returned expression is something like this
(source) => new TargetClass
{
TargetProperty1 = Expression1(source),
TargetProperty2 = Expression2(source),
...
}
where TargetClass is a dynamically generated class.
All we want is to keep the source part and just replace that target class/properties with the desired class/properties.
As for the implementation, first the property assignments are converted with
var bindings = memberInit.Bindings.Cast<MemberAssignment>()
.Select(mb => Expression.Bind(
(MemberInfo)resultType.GetProperty(mb.Member.Name) ?? resultType.GetField(mb.Member.Name),
mb.Expression));
and then the new DynamicClassXXX { ... } is replaced with with
var body = Expression.MemberInit(Expression.New(resultType), bindings);
Would something like this be of benefit to you?
public static IQueryable<TEntity> GetQuery<TEntity>(this DbContext db, bool includeReferences = false) where TEntity : class
{
try
{
if (db == null)
{
return null;
}
var key = typeof(TEntity).Name;
var metaWorkspace = db.ToObjectContext().MetadataWorkspace;
var workspaceItems = metaWorkspace.GetItems<EntityType>(DataSpace.OSpace);
var workspaceItem = workspaceItems.First(f => f.FullName.Contains(key));
var navProperties = workspaceItem.NavigationProperties;
return !includeReferences
? db.Set<TEntity>()
: navProperties.Aggregate((IQueryable<TEntity>)db.Set<TEntity>(), (current, navProperty) => current.Include(navProperty.Name));
}
catch (Exception ex)
{
throw new ArgumentException("Invalid Entity Type supplied for Lookup", ex);
}
}
You may want to take a look into the Generic Search project on Github located here:
https://github.com/danielpalme/GenericSearch
There is no need for Dynamic Linq on this one.
var groupedQuery = from p in db.People
where p.City != null && p.State != null
group p by new {p.City, p.State}
into gp
select new Thing {
TotalNumber = gp.Count(),
City = gp.Key.City,
State = gp.Key.State
};
IQueryable<Thing> retQuery = groupedQuery.AsQueryable();
retQuery= retQuery.Take(10);
return retQuery;

How to Create an Expression tree for .Where(x => x.<deep property>.Select(y => y.id).Intersect(List<int>).Any())

I'm creating a method that receives a Queryable<T> source, a string with a property name/path (could be a deep property for example "TrParent.DataTypes" to achieve this x => x.TrParent.DataTypes) and Enumerable<int> which holds the values I need to intersect.
Basically I come from the need to create the following query dynamically (I mean <DT_Det_Tr> and TrParent.DataTypes being know only at runtime, in the example DT_Det_Tr is not a type it is a class):
var _vals = new List<int>();
var res = dbContext.Set<DT_Det_Tr>()
.Where
(x => x.TrParent.DataTypes
.Select(t => t.Id)
.Intersect(_vals)
.Any()
);
Please keep in mind that the preceding query is just an example of what I need to achieve dynamically, what I really need is an expression tree that creates a predicate like the one shown above but using a dynamic type and with the deep navigation property specified within a string.
So, I'm using this function to create the expression for the deep property:
private static LambdaExpression CreateDelegateExpression<T>(out Type resultingtype, string property, string parameterName = "x")
{
var type = typeof(T);
ParameterExpression param = Expression.Parameter(type, parameterName);
Expression expr = param;
foreach (string prop in property.Split('.'))
{
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, param);
resultingtype = type;
return lambda;
}
And here is what I have so far for my function:
public static IQueryable<T> Intersect<T>(this IQueryable<T> source, string property, IEnumerable<int> value)
{
//List of ids
var _value = Expression.Constant(value);
//Get delegate expression to the deep property and it's inner type
Type type = null;
var lambda = CreateDelegateExpression<T>(out type, property, "x");
var enumtype = type.GetGenericArguments()[0];
ParameterExpression tpe = Expression.Parameter(enumtype, "y");
Expression propExp = Expression.Property(tpe, enumtype.GetProperty("Id"));
MethodInfo innermethod = typeof(Queryable).GetMethods().Where(x => x.Name == "Select").First();
//Error on next line...
var selectCall = Expression.Call(typeof(Queryable),
"Select",
new Type[] { enumtype, typeof(long) },
lambda,
propExp);
//TODO: Add rest of logic and actually filter the source
return source;
}
In the var selectCall = line I'm getting error:
No generic method 'Select' on type 'System.Linq.Queryable' is compatible with the supplied type arguments and arguments. No type arguments should be provided if the method is non-generic.
I've read a lot here on SO and other sites but I can't get past this part, I feel I'm going to bump into more trouble when I get to the .Intersect(List<int>).Any() part so any help on that also would be grand, thanks.
After a lot of thought, investigation and attempts I came up with a solution.
First, I made a simpler version of my goal query (the static example I used in my question), so instead of:
var res = dbContext.Set<DT_Det_Tr>()
.Where
(x => x.TrParent.DataTypes
.Select(t => t.Id)
.Intersect(_vals)
.Any()
);
I made this:
var res = dbContext.Set<DT_Det_Tr>()
.Where
(x => x.TrParent.DataTypes
.Any(y => _vals.Contains(y.Id))
);
Which is a lot easier to translate to expressions (or at least it was for me) because it omits the Select call.
I got rid of the method I was using to create the deep navigation property expression and streamlined it in my Intersect function, this was because it was doing some work I don't really need here plus I needed access to some of the variables I use inside it, then I made this:
public static IQueryable<T> Intersect<T>(this IQueryable<T> source, string property, IEnumerable<int> value)
{
var type = typeof(T);
var _value = Expression.Constant(value); //List of ids
//Declare parameter for outer lambda
ParameterExpression param = Expression.Parameter(type, "x");
//Outer Lambda
Expression expr = param;
foreach (string prop in property.Split('.')) //Dig for deep property
{
PropertyInfo pi = type.GetProperty(prop);
expr = Expression.Property(expr, pi);
type = pi.PropertyType;
}
//Get deep property's type
var enumtype = type.GetGenericArguments()[0];
//Declare parameter for inner lambda
ParameterExpression tpe = Expression.Parameter(enumtype, "y");
//Inner Collection lambda logic
//Property for inner lambda
Expression propExp = Expression.Property(tpe, enumtype.GetProperty("Id"));
//Contains method call .Contains(y.Id)
var containsMethodExp = Expression.Call(typeof(Enumerable), "Contains", new[] { propExp.Type }, _value, propExp);
//Create Expression<Func<enumtype, bool>>
var innerDelegateType = typeof(Func<,>).MakeGenericType(enumtype, typeof(bool));
//Create Inner lambda y => _vals.Contains(y.Id)
var innerFunction = Expression.Lambda(innerDelegateType, containsMethodExp, tpe);
//Get Any method info
var anyMethod = typeof(Enumerable).GetMethods().Where(m => m.Name == "Any" && m.GetParameters().Length == 2).Single().MakeGenericMethod(enumtype);
//Call Any with inner function .Any(y => _vals.Contains(y.Id))
var outerFunction = Expression.Call(anyMethod, expr, innerFunction);
//Call Where
MethodCallExpression whereCallExpression = Expression.Call
(
typeof(Queryable),
"Where",
new Type[] { source.ElementType },
source.Expression,
Expression.Lambda<Func<T, bool>>(outerFunction, new ParameterExpression[] { param })
);
//Create and return query
return source.Provider.CreateQuery<T>(whereCallExpression);
}
I hope this helps anyone trying to develop a similar solution.
Working with expression trees can be very hard and frustrating at first, but it's a really powerful tool once you get the hold of it.
If you have access to the dynamic keyword from c# 4.0, you might be able to work around the problem like this:
var _vals = new List<int>();
var res = dbContext.Set<DT_Det_Tr>()
.Where(obj => { dynamic x = obj;
return x.TrParent.DataTypes
.Select(t => t.Id)
.Intersect(_vals)
.Any();
}
);
But I don't know enough about the details of the problem you want to solve to say for sure.

Dynamically constructing an Expression<Func<T,bool>> doesn't work for GreaterThen

Hi I have a simple query in Entity Framework:
using (var db = new BookstoreContext())
{
return db.Book
.Where(w => w.PublishedYear > 2005)
.ToList();
}
Now I want to change this query to something more dynamic. But I want to change, not only the constant value (2005) but also my column field (PublishedYear).
I am looking for several days how to dynamically build an Expression<Func<,>>. Now i found this page and I'm trying to do so. So far I came to this:
public IEnumerable<Book> GetBooksGreaterThan(string columnName, object value)
{
using (var db = new BookstoreContext())
{
return db.Book
.Where(GreaterThan(columnName, value))
.ToList();
}
}
public Expression<Func<Book, bool>> GreaterThan(string columnName, object value)
{
var param = Expression.Parameter(typeof(Book), "w");
var property = Expression.PropertyOrField(param, columnName);
var body = Expression.GreaterThan(property, Expression.Constant(value));
return Expression.Lambda<Func<Book, bool>>(body, param);
}
But in line 15 (var body = Expression.Greater...) a exception is thrown:
The binary operator GreaterThan is not defined for the types 'System.Nullable`1[System.Int32]' and 'System.Int32'.
PS:
My column PublishedYear of the table Book is INT NULL or int? in entity framework class.
The expression w => w.PublishedYear > 2005 so why it keep saying that don't exists this operation? How can I fix it?
The problem is the nullability. You could probably get away with just adding a conversion expression from the value to the type of the property:
public Expression<Func<Book, bool>> GreaterThan(string columnName, object value)
{
var param = Expression.Parameter(typeof(Book), "w");
var property = Expression.PropertyOrField(param, columnName);
var propertyType = typeof(Book).GetProperty(columnName).PropertyType;
var body = Expression.GreaterThan(property,
Expression.Convert(Expression.Constant(value), propertyType));
return Expression.Lambda<Func<Book, bool>>(body, param);
}
This is now effectively doing:
w => w.PublishedYear > (int?) 2005
... which is what the C# code is doing implicitly.
If the value NULL for PublishedYear is permitted but does not actually occur, perhaps you can change the type of argument value in both functions to int? and use value.Value for the comparison in Expression.GreaterThan.

Categories