Is there any way to dynamically select which column this runs on? I don't need the comparison, just the column dynamically selected.
where SqlMethods.Like(s.GradeLevel, grade)
You can build up the query dynamically using the Expression. Here's an example I wrote to be able to dymically choose colums to filter.
private static Expression GetStringCompareExpression<TEntity>(Expression parameters, string method, FilterContainer filter, Type type)
{
Expression expression;
MemberExpression field = Expression.PropertyOrField(parameters, filter.field);
Expression constant = Expression.Constant(filter.value.ToLower().Trim(), type);
Expression lowercase = Expression.Call(field, "ToLower", null, null);
Expression trim = Expression.Call(lowercase, "Trim", null, null);
expression = AddNotNullExpression(field, Expression.Call(trim, method, null, constant));
return expression;
}
This one does the following:
Creates a field expression
Creates constant expression (the value to compare in this case)
Creates a call expression to call the ToLower() on the value in the expression
Creates a call expression to call the Trim() on the value in the expression
The 'AddNotNullExpression' add a test for 'not null' for the value
It returns the expression
Here's the 'AddNotNullExpression' method:
private static Expression AddNotNullExpression(MemberExpression field, Expression expression)
{
if(field.Type.IsNullable())
{
// String mag niet null zijn
Expression nullConstant = Expression.Constant(null, field.Type);
Expression notNull = Expression.NotEqual(field, nullConstant);
expression = Expression.AndAlso(notNull, expression);
}
return expression;
}
Call it like so:
Expression expression = GetStringCompareExpression<TEntity>(parameters, "Contains", filter, propertyInfo.PropertyType);
And finally return the IQueryable<> like so:
public static IQueryable<TEntity> Where<TEntity>(this IQueryable<TEntity> query, FilterContainer filters)
{
Expression expression = GetSingleExpression<TEntity>(filters, parameters);
var lambda = Expression.Lambda<Func<TEntity, bool>>(expression, parameters);
return query.Where(lambda);
}
It is not completely what you ask because this one was written to translate a filter expression coming from a grid in the view (which is in the FilterContainer) but it gives an idea on how you can write your 'Like' yourself
If your "dynamically" depends on a certain condition, they yes. Build your linq query without this condition first. Then add if statements and add appropriate condition.
var query = ....;
if (value1 = condition1) {
query = from q in query
where SqlMethods.Like(s.GradeLevel, grade);
}
if (value2 == condition2) {
query = from q in query
where SqlMethods.Like(s.Name, name);
}
The query will be build on the very last possible moment, not in advance, so you can safely use this approach.
Related
I have an Expression which could be any value. For example it could be () => 5 + 5. How would I use that Expression to search a List<int> to return true if the value is found in the List<int>? What I'm trying to do is I'm given an Expression and I'm checking a list to see if the value of the Expression is in the list
private int FindRecord(System.Linq.Expressions.Expression expression)
{
// expression when stepping through in debug is {() => 5 + 5}
List<int> list = new List<int>();
list.Add(5);
list.Add(10);
return list.Any(expression); // syntax issue - cannot convert from Expression to Func<int,bool>
}
EDIT : I misunderstood the question so here is my new answer:
private bool FindRecord(System.Linq.Expressions.Expression<Func<int>> expression)
{
int valueToSearch = expression.Compile()();
List<int> list = new List<int>();
list.Add(5);
list.Add(10);
return list.Any(i => i == valueToSearch); // syntax issue - cannot convert from Expression to Func<int,bool>
}
The return type of you function should be a "bool" (Any return a bool, not an int).
Parameter type "Expression" is not accurate enough, you should say it's a expression of a function return an integer.
You need to compile this expression to be able to "run" it and to get the actual value.
Well, you can do it as follows without Compile() on the Expression parameter, but it's really not a good idea. You generally don't want to be passing Expression around in your code as it will fail at runtime instead of compile time. You should change your method to accept an Expression of the expected type instead, or make it generic.
private static bool FindRecord(Expression expression, IEnumerable<int> list)
{
ParameterExpression parameter = Expression.Parameter(typeof(int), "x");
Expression body = Expression.Equal(parameter, Expression.Invoke(expression));
Expression<Func<int, bool>> lambda = (Expression<Func<int, bool>>)Expression.Lambda(body, parameter);
Console.WriteLine(lambda);
return list.Any(lambda.Compile());
}
With the recent release of Entity Framework Core 3.0, LINQ queries are no longer evaluated on the client by default. I'm a big fan of this change, as it revealed some potentially dangerous client-side evaluation in my project that I thought was translated to SQL; however, it also made some of the helper methods that I was using to avoid crazy chains of ternaries unusable.
Has anyone manged to nest LINQ expressions for use with Entity Framework Core 3.0? Here's an example of what I'm hoping to achieve:
[Fact]
public async Task Can_use_custom_expression()
{
var dbContext = new ApplicationDbContext(new DbContextOptionsBuilder<ApplicationDbContext>().UseInMemoryDatabase("Test").Options);
dbContext.Users.Add(new ApplicationUser { FirstName = "Foo", LastName = "Bar" });
dbContext.SaveChanges();
string query = "Foo";
Expression<Func<string, string, bool>> valueCheck = (value, expected) => !string.IsNullOrEmpty(value) && value.Contains(expected);
var valueCheckFunc = valueCheck.Compile();
Expression<Func<ApplicationUser, bool>> whereExpression = (u) => valueCheckFunc(u.FirstName, query);
var user = await dbContext.Users
.Where(whereExpression)
.FirstOrDefaultAsync();
Assert.NotNull(user);
}
When I run this example, I get the following exception:
Message:
System.InvalidOperationException : The LINQ expression 'Where<ApplicationUser>(
source: DbSet<ApplicationUser>,
predicate: (a) => Invoke(__valueCheckFunc_0, a.FirstName, __query_1)
)' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync(). See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
Like I said, I do not want to evaluate this expression client-side, but I would like to avoid having to chain a dozen or so !string.IsNullOrEmpty(x) && x.Contains(y)'s in a single expression. I'd love some tips on how to achieve this.
If you want your expressions to be translatable to Sql by EF you need to avoid calling delegates or methods (with some exceptions, of course). But what you want to achieve is doable by replacing the delegate invocation with its defining expression. For that you need a specialized ExpressionVisitor.
The following visitor will traverse expressions replacing delegate references within its wrapping invocations by a lambda expression body:
public class DelegateByLambda: ExpressionVisitor
{
LambdaExpression delegateReferenceExpression;
LambdaExpression lambdaExpression;
Stack<InvocationExpression> invocations;
public DelegateByLambda(LambdaExpression delegateReferenceExpression, LambdaExpression lambdaExpression)
{
this.delegateReferenceExpression = delegateReferenceExpression;
this.lambdaExpression = lambdaExpression;
this.invocations = new Stack<InvocationExpression>();
}
protected override Expression VisitParameter(ParameterExpression node)
{
var paramIndex = lambdaExpression.Parameters.IndexOf(node);
if (paramIndex >= 0)
{
InvocationExpression call = invocations.Peek();
return base.Visit(call.Arguments[paramIndex]);
}
return base.VisitParameter(node);
}
protected override Expression VisitInvocation(InvocationExpression node)
{
if (node.Expression.ToString() == delegateReferenceExpression.Body.ToString())
{
invocations.Push(node);
var result = base.Visit(lambdaExpression.Body);
invocations.Pop();
return result;
}
return base.VisitInvocation(node);
}
}
This class has no protection against attempting to replace delegate invocations by lambdas with mismatching arguments (number and types) however, the following extension method will do the trick:
public static class DelegateByLambdaExtensions
{
public static Expression<T> Replace<T, X>(this Expression<T> source, Expression<Func<X>> delegateReference, Expression<X> lambdaReference)
{
return new DelegateByLambda(delegateReference, lambdaReference).Visit(source) as Expression<T>;
}
}
So, all you need to do in your code is to call the replace extension method on the expression you want to translate passing an expression returning the delegate and desired lambda expression for expansion. Your sample should look like this:
Expression<Func<string, string, bool>> valueCheck = (value, expected) => !string.IsNullOrEmpty(value) && value.Contains(expected);
var valueCheckFunc = valueCheck.Compile();
Expression<Func<ApplicationUser, bool>> whereExpression = (u) => valueCheckFunc(u.FirstName, query);
whereExpression = whereExpression.Replace(() => valueCheckFunc, valueCheck);
var user = dbContext.Users
.Where(whereExpression)
.FirstOrDefault();
Console.WriteLine(user != null ? $"Found {user.FirstName} {user.LastName}!" : "User not found!");
A working sample can be found here. https://dotnetfiddle.net/Lun3LA
I am trying to create a generic way of getting an EntityFramework object based on its own id without passing in a lambda expression as parameter to the method GetById(). For the code below the entity T is of type Message, is known to the class where GetById() is implemented and has a property MessageId along with several other properties. The MessageId name has been hard-coded in the example below as this is still experimental - extracting the id property name from T is quite easy to fix later.
I have been struggling to find a way to construct a simple LambdaExpression which has IQueryable<T> as parameter type and hope that someone would have a clue on how this could be done. The reason why I want IQueryable<T> is because my underlying channel factory provider requires this for more complex queries.
The line with var exp = Expression.Lambda<...> in the code below shows the expression function type definition which I want to end up with, but the line gives the exception:
Expression of type System.Boolean cannot be used for return type IQueryable
That's because the body has the Boolean type while my expression parameter queryParamtRet is of type IQueryable<Message>. Further, if I change the body type to be an IQueryable<Message>, I'm not able to find the property MessageId since the type is no longer type T as Message but type IQueryable<T>.
public T GetById(int id)
{
var queryParamLeft = Expression
.Parameter(typeof(System.Data.Entity.DbSet<T>), "o");
var queryParamRet = Expression
.Parameter(typeof(IQueryable<T>), "o");
var entityFrameworkType = Expression
.Parameter(typeof(T), "o");
var queryProperty = Expression
.PropertyOrField(entityFrameworkType, "MessageId");
var body = Expression
.Equal(queryProperty, Expression.Constant(id));
var exp = Expression
.Lambda<Func<System.Data.Entity.DbSet<T>, IQueryable<T>>>(
body,
queryParamRet);
var returnXml = DoWithChannel(channel
=> channel.Load(serializer.Serialize(exp)));
}
TLDR: Write out code that you want to create an expression for, then deliberately create the expression, starting with any inner expressions before combining them into the outer expression.
If you write your intended code as a function, it would look something like this
public static IQueryable<T> FilterADbSet(DbSet<T> dbSet)
{
return Queryable.Where<T>(dbSet, o => o.MessageId == 34);
}
It has one input parameter of type DbSet<T>, an output of type IQueryable<T> and it calls Queryable.Where<T> with parameters of the dbSet variable and an expression.
Working from the outside in, you first need to build the expression to pass to the where clause. You have already done that in your code.
Next you need to create a lambda expression for the where clause.
var whereClause = Expression.Equal(queryProperty, Expression.Constant(id));
var whereClauseLambda = Expression.Lambda<Func<T, bool>>(whereClause, entityFrameworkType);
Next, as the comments indicate, you need to use Expression.Call to create a body.
My end result with making your code work is below.
static Expression<Func<IQueryable<T>, IQueryable<T>>> WhereMethodExpression = v => v.Where(z => true);
static MethodInfo WhereMethod = ((MethodCallExpression)WhereMethodExpression.Body).Method;
public T GetById(int id)
{
var queryParamLeft = Expression
.Parameter(typeof(System.Data.Entity.DbSet<T>), "dbSet");
var entityFrameworkType = Expression
.Parameter(typeof(T), "entity");
var queryProperty = Expression
.PropertyOrField(entityFrameworkType, "MessageId");
var whereClause = Expression
.Equal(queryProperty, Expression.Constant(id));
var whereClauseLambda = Expression.Lambda<Func<T, bool>>(whereClause, entityFrameworkType);
var body = Expression.Call(
WhereMethod,
queryParamLeft,
whereClauseLambda
);
var exp = Expression
.Lambda<Func<System.Data.Entity.DbSet<T>, IQueryable<T>>>(
body,
queryParamLeft);
var returnXml = DoWithChannel(channel
=> channel.Load(serializer.Serialize(exp)));
}
I used an expression to fetch the MethodInfo object of Queryable.Where<T>
Your body expression needed queryParamLeft passed in. queryParamRet is not needed
I realize there have been a ton of posts related to this and I've researched extensively and can't seem to figure this out. It should be super simple. I simply need to generate a column domain with a dynamic column name. Something like
public IEnumerable<ColumnEntity> GetColumnDomain(string column)
{ List<ColumnEntity> columnEntities = new List<ColumnEntity>();
var query = db.CITATIONs.Select(m => m."column").Distinct();
....
}
Where "column" is the dynamic parameter value. I started building and expression tree to dynamically generate the query expression
ParameterExpression pe = Expression.Parameter(typeof(CITATION), "c");
Expression theColumn = Expression.Property(pe, typeof(string).GetProperty(column));
But that is about it. Thanks in advance
Use the Expression.PropertyOrField() method to generate the member access. You'll also need to know the type of the column as well or it just won't work.
This could all be generalized into this generic method:
public static Expression<Func<TSource, TResult>>
GenerateSelector<TSource, TResult>(string propertyOrFieldName)
{
var parameter = Expression.Parameter(typeof(TSource));
var body = Expression.Convert(
// generate the appropriate member access
Expression.PropertyOrField(parameter, propertyOrFieldName),
typeof(TResult)
);
var expr = Expression.Lambda<Func<TSource, TResult>>(body, parameter);
return expr;
}
Then you could just do:
public IEnumerable<ColumnEntity> GetColumnDomain<TColumn>(string column)
{
var query = db.CITATIONs
.Select(GenerateSelector<CITATION, TColumn>(column))
.Distinct();
// ...
}
So not entirely sure what you're after. But simply put:
public PropertyInfo GetPropertyInfo<T>(Expression<Func<T, Object>> expression)
{
MemberExpression memberData = (MemberExpression)expression.Body;
return (PropertyInfo)memberData.Member;
}
this much will give you property info about the member, so if you have:
PropertyInfo info = GetPropertyInfo<FileInfo>(file => file.FullName);
Console.WriteLine(info.Name);
It will show the 'FullName' corresponding to the property you put in the expression.
I have some code that dynamically builds up some search criteria based on user input, resulting in an Expression<Func<T, bool>> that is passed to the LINQ .Where() method. It works fine when input is present, but when input is not present, I want to create a simple 'return false;' statement so that no results are returned.
Below is my current attempt, but when this is passed to the .Where() method it throws a NotSupportedException "Unknown LINQ expression of type 'Block'."
var parameter = Expression.Parameter(typeof(T), "x");
var falseValue = Expression.Constant(false);
var returnTarget = Expression.Label(typeof (bool));
var returnFalseExpression = Expression.Block(Expression.Return(returnTarget, falseValue), Expression.Label(returnTarget, falseValue));
var lambdaExpression = Expression.Lambda<Func<T, bool>>(returnFalseExpression, parameter);
How can I build a 'return false' expression that can be interpreted by LINQ?
Expression<Func<T, bool>> falsePredicate = x => false;
Can you wrap the entire thing in an if-else expression?
Meaning:
if input
return <your normal code>
else
return false
The return is implicit in expressions; the return value of the expression will simply be the last value. So you could try:
Expression.Condition
(
Expression.NotEqual(input, Expression.Constant("")),
normalSearchExpression,
Expression.Constant(false)
)
That's assuming normalSearchExpression also returns a bool.