I am using Linq-to-Entities and one query requires a property to be loaded dynamically. The query looks like this:
var found = Context.IntegrateViews.GroupBy(x => x.TypeOfBed)
.Select(type => new TypeCounts
{ Name = type.Key, Count = type.Count() }).ToList();
The property on which Group By clause is being run is TypeOfBed. Now I want to run this query on different properties e.g., next time I want to run it on, let's say x.TypeOfChair and so on. For this, I need to have this property in there dynamically.
I am trying to write a Expression builder which is like this.
public Expression<Func<LookupFilterItem>> PropertyLambda(string propertyName)
{
var param = Expression.Parameter(typeof(LookupFilterItem), "lookupItem");
//type of x in above example is LookupFilterItem
var propertyExpression = Expression.Property(param, propertyName);
var returnExpr = Expression.Lambda<Func<LookupFilterItem>>(propertyExpression);
return returnExpr;
//If I Pass string "TypeOfBed" to this method, I am expecting it to return something like
// lookupItem => lookupItem.TypeOfBed
}
And I intend to use it like this:
var found = Context.IntegrateViews.GroupBy(PropertyLambda("TypeOfBed"))
.Select(type => new TypeCounts
{ Name = type.Key, Count = type.Count() }).ToList();
But this is not working, I am getting a compile time error which says:
error CS0411: The type arguments for method
'System.Linq.Queryable.GroupBy(System.Linq.IQueryable,
System.Linq.Expressions.Expression>)' cannot
be inferred from the usage. Try specifying the type arguments
explicitly.
What am I doing wrong here?
This seems to be a syntax issue. Try this:
public Expression<Func<LookupFilterItem, string>> PropertyLambda(string propertyName)
{
var param = Expression.Parameter(typeof(LookupFilterItem), "lookupItem");
var propertyExpression = Expression.Property(param, propertyName);
var returnExpr = Expression.Lambda<Func<LookupFilterItem, string>>(propertyExpression, param);
return returnExpr;
}
And the usage:
var found = Context.IntegrateViews.GroupBy(PropertyLambda("TypeOfBed").Compile())
.Select(type => new TypeCounts
{ Name = type.Key, Count = type.Count() }).ToList();
Another option is to use LINQ Dynamic Query Library :
using System.Linq.Dynamic;
var found = Context.IntegrateViews.AsQueryable().GroupBy("new(TypeOfBed)", "it")
.Select(new (Key.TypeOfBed as Name, Count() as Count)).ToList();
Related
I need to dynamically execute the Include and Where method in it to build a query in EF. From the input parameters there is only the name of the field for Include and the condition in the form of a string.
What I want without dynamics looks like:
using System.Linq.Dynamic.Core;
contextEF.Table1.Include(x => x.Field1.Where("c => c.Id == 500")).ToList();
Here is my extreme attempt at getting this code to work dynamically:
var nameField = "Field1";
var queryWhere = "c => c.Id == 500";
var parameter = Expression.Parameter(typeof(T), "x");
var property = Expression.Property(parameter, typeof(T).GetProperty(nameField));
var typeItem = typeof(T).GetProperty(nameField).PropertyType.GetGenericArguments().First();
var lambda = DynamicExpressionParser.ParseLambda(ParsingConfig.Default, typeItem, typeof(bool), queryWhere);
var expressionWhere = Expression.Call(typeof(Queryable), "Where", new Type[] { typeItem }, lambda, property);
var expressionInclude = Expression.Lambda<Func<T, object>>(expressionWhere, parameter);
return queryable.Include(expressionInclude);
At the moment, I get errors with a call to the Where method and I can't move further:
System.InvalidOperationException: No generic method 'Where' 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.
It is clear from the error that it cannot find the Where method, where the parameter is string. But I don't understand how the program can prove that there is such a method =)
I managed to solve the problem, it all boiled down to the fact that I did not correctly specify the types for the .Include method, because there I have .Where, then the return type will be IEnumerable, and not ICollection as I expected.
var nameField = "Field1";
var queryWhere = "c => c.Id == 500";
var queryLambda = $"x => x.{nameField}.Where({queryWhere}).Take(20)";
Type typeEnumerable = typeof(IEnumerable<>).MakeGenericType(TProperty);
LambdaExpression lambdaInclude = DynamicExpressionParser.ParseLambda(typeof(T), typeEnumerable, queryLambda);
var includeQ =
Expression.Call(
type: typeof(Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions),
methodName: "Include",
typeArguments: new Type[] { typeof(T), typeEnumerable },
arguments: new[] {
queryable.Expression,
Expression.Quote(lambdaInclude)
}
);
queryable = queryable.Provider.CreateQuery<T>(includeQ);
I have an IQueryable<T> from my DbSet in Entity Framework. I am provided a "Fuzzy Search String", named searchText, like so:
public List<T> Search<T>(string searchText)
{
using (var context = ...)
{
var baseQuery = context.Set<T>().AsQueryable();
baseQuery = baseQuery.Where(x =>
DbFunctions.Like(x.PropertyName, searchText)
|| DbFunctions.Like(x.PropertyTwo, searchText)
|| DbFunctions.Like(x.PropertyThree, searchText)
|| DbFunctio..... etc
);
return baseQuery.ToList();
}
}
But given the generic nature, I don't know what properties there are on the type. I can provide an abstract method to somebody implementing this which allows them to give me a List of Properties (or even PropertyInfo or whatever else, I can figure that out). But I don't know how to dynamically create the expression. This is what I have so far:
var baseQuery = context.Set<T>().AsQueryable();
var expression = baseQuery.Expression;
var colName = "colName"; // Or names, I can iterate.
var parameter = Expression.Parameter(typeof(T), "x");
var selector = Expression.PropertyOrField(parameter, colName);
expression = Expression.Call(typeof(DbFunctions), nameof(DbFunctions.Like),
new Type[] { baseQuery.ElementType, selector.Type },
expression, Expression.Quote(Expression.Lambda(selector, parameter)));
The problem here is... well, it doesn't work to begin with. But mainly that I'm not using the searchText anywhere in it, and don't know how to plug it in. I THINK I'm close... but have spent an inordinate amount of time on it.
Hopefully I'm getting your query logic right: if you want to build a set of LIKE conditions based on known type and list of column names, you could try something like this:
static private MethodInfo dbLikeMethod = typeof(DbFunctions).GetMethod(nameof(DbFunctions.Like), BindingFlags.Public | BindingFlags.Static, null, new Type[] {typeof(string), typeof(string)}, null); // I am targeting DbFunctions.Like(string, string). You might want another overload (or even mix them up depending on your inputs)
public List<T> Search<T>(string searchText) where T: class
{
using (var context = new ...)
{
var baseQuery = context.Set<T>().AsQueryable().Where(CreateExpression<T>(searchText));// you could probably find a more elegant way of plugging it into your query
return baseQuery.ToList();
}
}
Expression<Func<T, bool>> CreateExpression<T>(string searchText) where T : class
{
var cols = new List<string> {
"PropertyName",
"PropertyTwo" // i understand you've got a way to figure out which strings you need here
};
var parameter = Expression.Parameter(typeof(T), "x");
var dbLikeCalls = cols.Select(colName => Expression.Call(dbLikeMethod, Expression.PropertyOrField(parameter, colName), Expression.Constant(searchText))); // for convenience, generate list of DbFunctions.Like(x.<Property>, searchText) expressions here
var aggregatedCalls = dbLikeCalls.Skip(1).Aggregate((Expression)dbLikeCalls.First(), (accumulate, call) => Expression.OrElse(accumulate, call)); // aggregate the list using || operators: use first item as a seed and keep adding onto it
return Expression.Lambda<Func<T, bool>>(aggregatedCalls, parameter);
}
I'm trying to dynamically build some sql-queries depending on a given config to only query data needed:
When writing plain linq it would look like this:
var data = dbContext
.TableOne
.Select(t1 => new TableOneSelect
{
TableOneId = t1.TableOneId,
TableOneTableTwoReference = new[] { TableOne.FirstTableTwoReference.Invoke(t1) }
.Select(t2 => new TableTwoSelect
{
TableTowId = (Guid?)t2.TableTwoId,
// ... some more properties of t2
}).FirstOrDefault(),
// ... some more properties of t1
});
whereas TableOne.FirstTableTwoReference.Invoke(t1) is defined
public static Expression<Func<TableOne, TableTwo>> FirstTableTwoReference => (t1) => t1.TableTwoReferences.FirstOrDefault();
Currently I have the following for building the TableOne-part dynamically:
public Expression<Func<TableOne, TableOneSelect>> Init(TableOneConfig cfg)
{
var memberBindings = new List<MemberBinding>();
var selectType = typeof(TableOneSelect);
var newExpression = Expression.New(selectType);
var theEntity = Expression.Parameter(typeof(TableOne), "t1");
// decide if the property is needed and add to the object-initializer
if (cfg.Select("TableOneId"))
memberBindings.Add(Expression.Bind(selectType.GetProperty("TableOneId"), Expression.Property(theEntity, nameof("TableOneId"))));
// ... check other properties of TableOneSelect depending on given config
var memberInit = Expression.MemberInit(newExpression, memberBindings);
return Expression.Lambda<Func<tblTournament, EventResourceSelect>>(memberInit, theEntity);
}
same for TableTwo (different properties and different db-table).
This I can dynamically invoke like this
dbContext.TableOne.Select(t => TableOneHelper.Init(cfg).Invoke(t1));
whereas Invoke is the one from LinqKit.
But I get stuck with the inner part for the TableOneTableTwoReference where I need to make an enumeration to call the Init of TableTwoHelper but I don't get the point how this can be achieved.
I guess Expression.NewArrayInit(typeof(TableTwo), ...) would be step one. But I still get stuck in how to pass t1.TableTwoReferences.FirstOrDefault() to this array calling the Select on.
I guess Expression.NewArrayInit(typeof(TableTwo), ...) would be step one. But I still get stuck in how to pass t1.TableTwoReferences.FirstOrDefault() to this array calling the Select on.
As I understand, the question is what is the expression equivalent of
new[] { TableOne.FirstTableTwoReference.Invoke(t1) }
It's really simple. As you correctly stated, you'll need Expression.NewArrayInit expression. However, since it expects params Expression[] initializers, instead of LINQKit Invoke extension method you should use Expression.Invoke method to emit call to TableOne.FirstTableTwoReference lambda expression with the outer theEntity ("t1") parameter:
var t2Array = Expression.NewArrayInit(
typeof(TableTwo),
Expression.Invoke(TableOne.FirstTableTwoReference, theEntity));
The same way you can emit the Select expression:
var t2Selector = TableTwoHelper.Init(cfg2);
// t2Selector is Expression<Func<TableTwo, TableTwoSelect>>
var t2Select = Expression.Call(
typeof(Enumerable), "Select", new[] { t2Selector.Parameters[0].Type, t2Selector.Body.Type },
t2Array, t2Selector);
then FirstOrDefault call:
var t2FirstOrDefault = Expression.Call(
typeof(Enumerable), "FirstOrDefault", new[] { t2Selector.Body.Type },
t2Select);
and finally the outer member binding:
memberBindings.Add(Expression.Bind(
selectType.GetProperty("TableOneTableTwoReference"),
t2FirstOrDefault));
This will produce the equivalent of your "plain linq" approach.
Add the member binding...
memberBindings.Add(Expression.Bind(selectType.GetProperty("TableOneTableTwoReference"), BuildTableTwoExpression(theEntity)));
...and then build TableTwo's expression
private Expression BuildTableTwoExpression(ParameterExpression t1)
{
var arrayEx = Expression.NewArrayInit(typeof(TableTwo), Expression.Invoke(TableOne.FirstTableTwoReference, t1));
Expression<Func<TableTwo, TableTwoSelect>> selector = (t2 => new TableTwoSelect
{
TableTowId = (Guid?)t2.TableTwoId,
// ... some more properties of t2
});
Expression<Func<IEnumerable<TableTwo>, TableTwoSelect>> selectEx =
((t1s) => Enumerable.Select(t1s, selector.Compile()).FirstOrDefault());
return Expression.Invoke(selectEx, arrayEx);
}
I have expression:
var newValues = MetarDecoder.Decode(telegram)
.OfType<MeteoParameter<decimal>>()
.Select(parameter => MeteoParameterFactory
.Create(parameter.ParameterId, parameter.DateTime.ToLocalTime(), parameter.Status, parameter.Value))
.ToList();
MeteoParameterFactory cannot be changed for some reasons, just take it as it is.
MeteoParameter also have string Info property.
I need to copy Info from old parameter to MeteoParameterFactory.Create() result.
Without LINQ it looks like:
var val = MetarDecoder.Decode(telegram).OfType<MeteoParameter<decimal>>().ToList();
foreach (var param in val)
{
var parameter = MeteoParameterFactory.Create(param.ParameterId, param.DateTime.ToLocalTime(), param.Status, param.Value);
parameter.Info = param.Info;
newValues.Add(parameter);
}
So, is there any way to add this part in LINQ expression shown below?
In Select you can create an anonymous function that returns the parameter created inside of it.
var newValues = MetarDecoder.Decode(telegram)
.OfType<MeteoParameter<decimal>>()
.Select(param => {
var parameter = MeteoParameterFactory.Create(param.ParameterId, param.DateTime.ToLocalTime(), param.Status, param.Value);
parameter.Info = param.Info;
return parameter;
}).ToList();
var val = MetarDecoder
.Decode(telegram)
.OfType<MeteoParameter<decimal>>()
.ToList()
.ForEach(param =>
{
var parameter = MeteoParameterFactory.Create(param.ParameterId, param.DateTime.ToLocalTime(), param.Status, param.Value);
parameter.Info = param.Info;
newValues.Add(parameter);
});
Let's say I have a class like:
public class Foo
{
public string Title {get;set;}
}
Now, let's assume I have a public List<Foo> myList which I want to filter by Linq as so:
var x = myList.Where(f => f.Title == myValue);
Everything is nice and clear until now.
But how can access the property by variable? Something like:
string myProperty = "Title";
var x = myList.Where(f => f.myProperty == myValue);
You can write an extension method
public static class MyExtensions
{
public static object GetProperty<T>(this T obj, string name) where T : class
{
Type t = typeof(T);
return t.GetProperty(name).GetValue(obj, null);
}
}
and use it like this
var x = myList.Where(f => f.GetProperty("Title") == myValue);
This is not the type of situation that LINQ is used for. LINQ is a fluent interface for manipulating collections. Accessing members via a textual representation is done with reflection.
object GetProperty(Foo f, string propertyName) {
var type = typeof(Foo);
var propInfo = type.GetProperty(propertyName);
return propInfo.GetValue(f, null);
}
If you need to compose your queries dynamically on the fly, you can use the LINQ Dynamic Query library, a sample from Microsoft:
This sample shows a technique for composing LINQ statements on the
fly, dynamically, at run time.
Reference the library in your code:
using System.Linq.Dynamic;
Your query would look like this:
// You can use a string as the argument for the Where method
// meaning you can compose this string dynamically
string myProperty = "Title";
var x = myList.Where(myProperty + " = " + myValue);
It's also possible to use placeholder in the query string, which improves readability (somewhat):
var x = myList.Where("#0 = #1", myProperty, myValue);
See also this post from Scott Guthrie: Dynamic LINQ Part 1: Using the LINQ Dynamic Query Library (I don't think there ever was a part 2...)
Note: you have to compile the sample code from Microsoft and reference the built assembly, or you could include the code in your own project.
I know this is an old thread but here is another way to do it. This has the advantage of being significantly faster if you need to do it in a loop. I have converted the result out of "func" to be object to make it a bit more general purpose.
var p = Expression.Parameter(typeof(string));
var prop = Expression.Property(p, "Length");
var con = Expression.Convert(prop, typeof(object));
var exp = Expression.Lambda(con, p);
var func = (Func<string, object>)exp.Compile();
var obj = "ABC";
int len = (int)func(obj);
In the original question the code was being used inside linq so speed could be good. It would be possible to use "func" direct in the where clause also if it was built correctly, eg
class ABC
{
public string Name { get; set; }
}
var p = Expression.Parameter(typeof(ABC));
var prop = Expression.Property(p, "Name");
var body = Expression.Equal(prop, Expression.Constant("Bob"));
var exp = Expression.Lambda(body, p);
var func = (Func<ABC, bool>)exp.Compile();
ABC[] items = "Fred,Bob,Mary,Jane,Bob".Split(',').Select(s => new ABC() { Name = s }).ToArray();
ABC[] bobs = items.Where(func).ToArray();
you cant use linq dynamic query from microsoft
here is sample code
var query = db.Customers.Where("City == #0 and Orders.Count >= #1", "London", 10).
OrderBy("CompanyName").
Select("New(CompanyName as Name, Phone)");