I want to create lambda something like this
user => user.Address == address
but is not compiled one, I want to return LambdaExpression
If the lambda take constant like this
user => user.Age == 50
Then I can use this method
public static LambdaExpression PropertyEqual(Type tEntityType, string propertyName, object value)
{
// entity => entity.PropName == const
var itemParameter = Expression.Parameter(tEntityType, "entity");
return Expression.Lambda
(
Expression.Equal
(
Expression.Property
(
itemParameter,
propertyName
),
Expression.Constant(value) // Tried to replace this with Expression.Parameter or Expression.Variable but no luck
),
new[] { itemParameter }
);
}
How to make this method accept variable address come from the scope just outside from the lambda expression?
var addressPropertyName = "Address";
var address = new Address() {...};
var q = Repo.GetQuery().Where(PropertyEqual(typeof(User), addressPropertyName, address))
Edit: clarify my question: How build the right Expression to generate the first lambda?
Update: This is not possible because EF does not support non-scalar variable
I change the lambda to user => user.AddressId == addressId as suggested here. It just the matter how to get AddressId FK PropertyInfo from a known navigation property Address.
You can't dynamically generate a closure on a variable (you can't extend the lifetime of a variable outside its context) because this is a trick of the compiler (that rewrites your code to do it).
If you don't want a closure but you want an additional parameter then you can add an additional parameter to the expression.
You could
Expression<Func<string>> myExpr = () => address;
now you have an expression that closes around your address. Now you only have to combine the two expressions.
You'll have to change the method to:
public static LambdaExpression PropertyEqual<T>(Type tEntityType, string propertyName, Expression<Func<T>> getValue)
{
// entity => entity.PropName == const
var itemParameter = Expression.Parameter(tEntityType, "entity");
return Expression.Lambda
(
Expression.Equal
(
Expression.Property
(
itemParameter,
propertyName
),
Expression.Invoke(getValue) // You could directly use getValue.Body instead of Expression.Invoke(getValue)
),
new[] { itemParameter }
);
}
Related
I'm struggling a bit to understand how to construct an expression that consumes a delegate. I'm new to expressions and embarrassingly wasn't able to create a unit test that replicated the problem I'm seeing, so hopefully the information below will be sufficient to explain the problem.
Consider the following classes:
public class Instance
{
internal Instance(string value)
{
Value = value;
}
public string Value { get; }
public Expression<Func<Container, bool>> IsContainerMatch => c => Selector(c.Item) == Value;
public Func<Item, string> Selector => i => i.Value;
}
public class Container
{
internal Container(Item item)
{
Item = item;
}
public Item Item { get; }
}
public class Item
{
internal Item(string value)
{
Value = value;
}
public string Value { get; }
}
The IsContainerMatch expression is used as an argument on a per-Instance basis for a third party method and is compiled/used at a later time. When the expression is actually called, however, I get an error that states that the variablecis referenced from scope '' but is not defined. If I'm not mistaken, this problem can be solved if I can incorporate the Selector delegate into the expression, so that the two have the same scope. (Please do correct me if I'm wrong!)
I came across this issue, but one fundamental difference I see is that the argument to my delegate is not a constant; it's determined at compile time. I haven't had much luck figuring out how to construct the expression for my scenario. Could someone provide me with a little guidance?
EDIT: This is the simplest test I can construct that fails - sorry it's not readily repeatable. The issue only occurs when I attempt to use the expression in conjunction with NHibernate; calling the func works fine when I use the method from #juharr. When I inject the expression into the .And statement, I get the error message variable c of type Container referenced from scope '', but it is not defined.
[Fact]
public void Test()
{
var instanceList = new Collection<Instance>{new Instance("test")};
var dbConfig = OracleDataClientConfiguration.Oracle10
.ConnectionString(c => c.Is("Data Source=(DESCRIPTION =(ADDRESS = (PROTOCOL = TCP)(HOST = 10.11.12.13)(PORT = 1521))(CONNECT_DATA = (SERVER = DEDICATED)(SERVICE_NAME = orcl.test.com)));Password=test;User ID=test;"))
.Driver<NHibernate.Driver.OracleManagedDataClientDriver>();
FluentConfiguration configuration = Fluently.Configure().Database(dbConfig)
.Mappings(m => m.FluentMappings.AddFromAssembly(Assembly.GetExecutingAssembly()));
var sessionFactory = configuration.BuildSessionFactory();
var session = sessionFactory.OpenSession();
var query = session.QueryOver<Container>();
foreach (var element in instanceList) query.And(c => element.Selector(c.Item) == element.Value);
var query2 = session.QueryOver<Container>();
foreach (var element in instanceList) query.And(element.IsContainerMatch);
}
EDIT #2: Note the query2 above; it is a second use case I have. The first query is the one that throws the exception, but my intent is to reuse the delegate for both the first query as well as in the expression for the second query.
Ok, lets unpack this;
public Expression<Func<Container, bool>> IsContainerMatch => c => Selector(c.Item) == Value;
IsContainerMatch is a property, returning a new instance of an expression on each get. The expression contains constant references to the Instance to access Value and Selector. This is roughly equivalent to;
public Expression<Func<Container, bool>> IsContainerMatch { get {
var inst = Expression.Constant(this);
var container = Expression.Parameter(typeof(Container), "c");
var selector = typeof(Instance).GetProperty("Selector");
var value = typeof(Instance).GetProperty("Value");
var item = typeof(Container).GetProperty("Item");
return Expression.Lambda<Func<Container, bool>>(
Expression.Equal(
Expression.Invoke(
Expression.MakeMemberAccess(inst, selector),
Expression.MakeMemberAccess(container, item)
),
Expression.MakeMemberAccess(inst, value)
),
container
);
} }
This is unlikely to be the true source of your exception. Somewhere else a new LambdaExpression has been constructed, perhaps from pieces of this Expression, with a reference to this ParameterExpression 'C'. But with a different parameter.
For example something like this might cause that Exception;
...
return Expression.Lambda<Func<Container, bool>>(
Expression.Equal(
Expression.Invoke(
Expression.MakeMemberAccess(inst, selector),
Expression.MakeMemberAccess(Expression.Parameter(typeof(Container), "X"), item)
),
Expression.MakeMemberAccess(inst, value)
),
Expression.Parameter(typeof(Container), "Y")
);
Clearly you are attempting to use a type of Expression that your 3rd party library doesn't support.
Now that you've updated the question to include NHibernate, it seems like what you're trying to achieve is;
foreach (var element in instanceList) query.And(c => c.Item.[Dynamic member] == element.Value);
So that the criteria can be evaluated efficiently by NHibernate. But since your Selector is a compiled Func, there's no way for NHibernate to look inside that method and translate this into an efficient query.
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'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.
I'm doing a little bit of search from a database using linq. I have multiple column names like country, name, phone number...
Now I've created a dropdownlist and pass the user selected data as a parameter "searchedField" to my controller method. Now if I take the input of a "country", I expect the code to be
entries = entries.Where(s => s.country.Contains(searchString));
If user selected "name"
entries = entries.Where(s => s.name.Contains(searchString));
Excuse me for this rather unreasonable example, since I can always just copy lines and make cases, but I wonder if there is a way to utilize things like reflection to convert string to "code" to access a field?
String searchedField = "name"
...
entries = entries.Where(s => s.searchedField.Contains(searchString));
This is my first question here, thanks!
You can use Dynamic Linq.
entries = entries
.Where(
string.Format(
"{0} = '{1}'",
searchedField,
searchString
)
);
Note: depending on the type of field you'll need to add quotes, or not.
You can do a reflection lookup (note: I've omitted error checking):
string GetPropertyAsString(object obj, string propertyName)
{
var propertyInfo - obj.GetType().GetProperty(propertyName);
return propertyInfo.GetValue(obj).ToString();
}
and then say
entries = entries.Where(s => GetPropertyAsString(s, searchedField).Contains(searchString));
You can build an expression tree for this (which will work with Linq to entities).
public static class QueryableExtensions {
public static IQueryable<T> Filter<T>(this IQueryable<T> queryable, string propertyName, string searchValue)
{
var type = typeof (T);
//this will be the left part of the lambda
var parameter = Expression.Parameter(type, "s");
//s.country for example
var property = Expression.Property(parameter, propertyName);
//string.Contains method
var containsMethod = typeof (string).GetMethod("Contains", new[] {typeof (string)});
//s.country.Contains(<yoursearchvalue>)
var expression = Expression.Call(property, containsMethod, Expression.Constant(searchValue));
//s => s.country.Contains(<yoursearchvalue>)
var lambda = Expression.Lambda<Func<T, bool>>(expression, parameter);
//filter your queryable with predicate
return queryable.Where(lambda);
}
}
usage
var fieldtosearch = "country";
entries = entries.Filter(fieldtosearch , searchString);
In general, other than using dynamic linq queries, you could also use Expression to build a generic method to construct a property Getter and use it in your linq query, a quick sampel:
public static Func<TObject, TProperty> GetPropGetter<TObject, TProperty>(string propertyName)
{
ParameterExpression paramExpression = Expression.Parameter(typeof(TObject), "value");
Expression propertyGetterExpression = Expression.Property(paramExpression, propertyName);
Func<TObject, TProperty> result =
Expression.Lambda<Func<TObject, TProperty>>(propertyGetterExpression, paramExpression).Compile();
return result;
}
to use it:
var getter = GetPropGetter<YourEntity, string>("Name");
var found = entites.Where(m => getter(m).Contains("someInput"));
Given a primitive value age I know how to create an expression like this:
//assuming: age is an int or some other primitive type
employee => employee.Age == age
By doing this:
var entityType = typeof(Employee);
var propertyName = "Age";
int age = 30;
var parameter = Expression.Parameter(entityType, "entity");
var lambda = Expression.Lambda(
Expression.Equal(
Expression.Property(parameter, propertyName),
Expression.Constant(age)
)
, parameter);
That works fine except in scenarios where the property and constant in question are not primitive types.
How would I construct a similar expression if the comparison is between objects?
With EF I can just write:
Location location = GetCurrentLocation();
employees = DataContext.Employees.Where(e => e.Location == location);
That also works, but if I try to create the same expression:
var entityType = typeof(Employee);
var propertyName = "Location";
var location = GetCurrentLocation();
var parameter = Expression.Parameter(entityType, "entity");
var lambda = Expression.Lambda(
Expression.Equal(
Expression.Property(parameter, propertyName),
Expression.Constant(location)
)
, parameter);
I get an error that says:
Unable to create a constant value of type 'Location'. Only primitive types or enumeration types are supported in this context.
My suspicion is that Expression.Constant() only expects primitive types, so I need to use a different expression factory method. (maype Expression.Object? - I know that doesn't exist)
Is there a way to create an expression that compares objects? Why is that EF is able to interpret it correctly if its a compiled LINQ statement, but not when it is an expression?
In addition to what has been mentioned in previous answers. A more specific solution would go as such:
public static Expression CreateExpression<T>(string propertyName, object valueToCompare)
{
// get the type of entity
var entityType = typeof(T);
// get the type of the value object
var valueType = valueToCompare.GetType();
var entityProperty = entityType.GetProperty(propertyName);
var propertyType = entityProperty.PropertyType;
// Expression: "entity"
var parameter = Expression.Parameter(entityType, "entity");
// check if the property type is a value type
// only value types work
if (propertyType.IsValueType || propertyType.Equals(typeof(string)))
{
// Expression: entity.Property == value
return Expression.Equal(
Expression.Property(parameter, entityProperty),
Expression.Constant(valueToCompare)
);
}
// if not, then use the key
else
{
// get the key property
var keyProperty = propertyType.GetProperties().FirstOrDefault(p => p.GetCustomAttributes(typeof(KeyAttribute), false).Length > 0);
// Expression: entity.Property.Key == value.Key
return Expression.Equal(
Expression.Property(
Expression.Property(parameter, entityProperty),
keyProperty
),
Expression.Constant(
keyProperty.GetValue(valueToCompare),
keyProperty.PropertyType
)
);
}
}
IMPORTANT POINTS :
Make sure to check for nulls
Make sure propertyType and valueType are compatible (either they are the same type or are convertible)
Several assumptions are made here (e.g. that you do assign a KeyAttribute)
This code is not tested, so it is not exactly copy/paste ready.
Hope that helps.
You can't do that because EF doesn't know how to translate equality comparisons on Location into a SQL expression.
However, if you know what properties of Location you want to compare, you can do this with anonymous types:
var location = GetCurrentLocation();
var locationObj = new { location.LocationName, location.LocationDescription };
employees = DataContext.Employees.Where(e => new { e.Location.LocationName, e.Location.Description } == locationObj);
Of course that's equivalent to:
var location = GetCurrentLocation();
employees = DataContext.Employees.Where(e => e.Location.LocationName == location.Name &&
e.Location.Description == location.Description);
Give the code below a run. I wanted to test your assumption that e => e.Location == location is compiling into something that can be constructed with Expression.Equal, Expression.Property, and Expression.Constant.
class Program {
static void Main(string[] args) {
var location = new Location();
Expression<Func<Employee, bool>> expression = e => e.Location == location;
var untypedBody = expression.Body;
//The untyped body is a BinaryExpression
Debug.Assert(
typeof(BinaryExpression).IsAssignableFrom(untypedBody.GetType()),
"Not Expression.Equal");
var body = (BinaryExpression)untypedBody;
var untypedLeft = body.Left;
var untypedRight = body.Right;
//The untyped left expression is a MemberExpression
Debug.Assert(
typeof(MemberExpression).IsAssignableFrom(untypedLeft.GetType()),
"Not Expression.Property");
////The untyped right expression is a ConstantExpression
//Debug.Assert(
// typeof(ConstantExpression).IsAssignableFrom(untypedRight.GetType()),
// "Not Expression.Constant");
//The untyped right expression is a MemberExpression?
Debug.Assert(
typeof(MemberExpression).IsAssignableFrom(untypedRight.GetType())));
}
}
public class Employee
{
public Location Location { get; set; }
}
public class Location { }
It seems like it isn't, and its because the right expression isn't a Constant. To see this, uncomment the commented out code.
What I don't understand is why the right expression is a MemberExpression. Perhaps someone who knows the linq expression compiler can shed more light onto this then I can.
Edit: This may have to do with closure in lambdas - a class is created behind the scenes which contains the closed over variables. The location might then be a member of that class. I'm not sure about this, but it's what I suspect.
This post may shed additional light on the situation.