Building a parameterized EntityFramework Core Expression - c#

Hi, I'm trying to build an Expression to get a generic entity by its primary key and getting a parameterized sql query.
Currently I can get the correct WHERE query, but it isn't parameterized.
public async Task<TDbo> Get(TKey key, Expression<Func<TEntity, TKey>> keySelector)
{
var propertyRef = keySelector.Body;
var parameter = keySelector.Parameters[0];
var constantRef = Expression.Constant(key);
var equals = Expression.Equal(propertyRef, constantRef);
var comparer = Expression.Lambda<Func<TEntity, bool>>(equals, parameter);
return await _context.Set<TDbo>().SingleOrDefaultAsync(comparer);
}
This results in the following query:
SELECT e.\"Id\", e.\"Name\" \r\n FROM \"People\" AS e\r\nWHERE e.\"Id\" = 1\r\nLIMIT 2,
instead of the wanted:
SELECT e.\"Id\", e.\"Name\" \r\n FROM \"People\" AS e\r\nWHERE e.\"Id\" = #__s_0\r\nLIMIT 2

It's because of Expression.Constant(key). Value constant expressions are not parameterized by the query translator. What you need is an expression referring to a property or field of another expression (which could be constant). That's basically what C# compiler emits for closures.
One way is to actually use the C# compiler to create lambda expression with closure and take the body:
Expression<Func<TKey>> keyValue = () => key;
var variableRef = key.Body;
(the variableRef is a replacement of yours constantRef)
Another way is to use anonymous, tuple or specific class type to create explicit closure instance and bind the corresponding property or field. For instance, with anonymous type:
var variableRef = Expression.Property(Expression.Constant(new { key }), "key");
or with System.Tuple:
var variableRef = Expression.Property(Expression.Constant(Tuple.Create(key)), "Item1");
The actual method doesn't really matter (I personally prefer the first variant with lambda) - all they will cause creating parameter by EF Core query translator.

Related

How to use the where clause with IQueryable<dynamic>

I am using Entity Framework Core with reflection to generate some forms dynamically. Everything is working except the WHERE Clause. I get the following error:
An expression tree may not contain a dynamic operation
I am able to fix this by converting my IQueryable to a List, but that introduces different problems that i would like to avoid.
Here is my code:
public async void ViewCollection(PropertyInfo propertyInfo)
{
Type propertyType = propertyInfo.PropertyType;
InversePropertyAttribute inversePropertyAttribute = (InversePropertyAttribute)ReflectionHelpers.GetAttribute(propertyInfo, typeof(InversePropertyAttribute));
//GET THE TYPE OF THE COLLECTION
Type collectionType = propertyInfo.PropertyType.GenericTypeArguments[0];
//GET THE INVERSE PROPERTY INFO
PropertyInfo inverseProperty = collectionType.GetProperty(inversePropertyAttribute.Property);
//GET THE FOREIGN KEY ATTRIBUTE FROM THE INVERSE PROPERTY
ForeignKeyAttribute foreignKeyAttribute = (ForeignKeyAttribute)ReflectionHelpers.GetAttribute(inverseProperty, typeof(ForeignKeyAttribute));
//GET THE FOREIGN KEY PROPERTY FROM THE FOREIGN KEY ATTRIBUTE
PropertyInfo foreignKeyProperty = collectionType.GetProperty(foreignKeyAttribute.Name);
//GET INCLUDED TYPE NAMES BY FOREIGN KEY
IEnumerable<string> includedTypes = collectionType.GetProperties().Where(p => p.PropertyType.IsClass).Where(p => ReflectionHelpers.HasAttribute(p, typeof(ForeignKeyAttribute))).Select(r => r.Name);
//GET THE DATA SET
IQueryable<dynamic> items = ReflectionHelpers.GetDbCollectionByType(Db, collectionType);
//INCLUDE THE INCLUDED TYPES BY NAME
foreach (string includedType in includedTypes) items = items.Include(includedType);
//THIS IS WHERE THE ERROR IS
items = items.Where(i => foreignKeyProperty.GetValue(i, null) == PrimaryKeyProperty.GetValue(Item, null));
await ShowCollection(collectionType, items, propertyInfo.Name);
}
How can i solve this with out changing my type to a list?
You can't. IQueryable is part of language integrated query (LINQ) - it's statically typed, so you cannot use value types.
IQueryable behind the scenes is an expression tree that represents a query that has not yet been executed. The expression tree part represents the query. The static types represent the data that the query operates over.
Your best alternative is to build the expression tree by hand using the expression tree API.
You can't build queries over dynamic because it's not supported natively in expression trees. Instead you should base your work on either non generic IQueryable or generic IQueryable<object>. e.g. if the ReflectionHelpers.GetDbCollectionByType is calling DbContext.Set<T> dynamically (similar to here Dynamically access table in EF Core 2.0), you should be able to cast it to IQueryable<object>:
var items = (IQueryable<object>)ReflectionHelpers.GetDbCollectionByType(Db, collectionType);
To add Where clause, you shouldn't use reflection calls inside the predicate expression, but build it dynamically using the Expression class methods (from System.Linq.Expressions namespace). Something like this:
// (object i) => (({collectionType})i).{ForeignKey} == Item.{PrimaryKey}
var parameter = Expression.Parameter(typeof(object), "i");
var body = Expression.Equal(
Expression.Property(Expression.Convert(parameter, collectionType), foreignKeyProperty),
Expression.Property(Expression.Constant(Item), PrimaryKeyProperty));
var predicate = Expression.Lambda<Func<object, bool>>(body, parameter);
and then
items = items.Where(predicate); // still IQueryable<object>

Creating Linq Expression to convert int to enum name in AutoMapper projection

I'd like to streamline my current view model mapping. I currently use Automapper ProjectTo to create my view model, then call a post mapping method to populate a StatusDescription string property from a Status enum whose value is in another int property in the view model class, StatusId.
I found this other answer, but I am not able to get it to work, and am having a hard time understanding what exactly it is doing due to my general lack of experience with expressions.
For reference, here is the expression method (note I had to comment out the defaultValue parameter as I was getting an error An expression tree may not contain a call or invocation that uses optional arguments:
public static Expression<Func<TSource, String>> CreateEnumToStringExpression<TSource, TMember>(
Expression<Func<TSource, TMember>> memberAccess)//,string defaultValue = ""
{
string defaultValue = "";
var type = typeof(TMember);
if (!type.IsEnum)
{
throw new InvalidOperationException("TMember must be an Enum type");
}
var enumNames = Enum.GetNames(type);
var enumValues = (TMember[])Enum.GetValues(type);
var inner = (Expression)Expression.Constant(defaultValue);
var parameter = memberAccess.Parameters[0];
for (int i = 0; i < enumValues.Length; i++)
{
inner = Expression.Condition(
Expression.Equal(memberAccess.Body, Expression.Constant(enumValues[i])),
Expression.Constant(enumNames[i]),
inner);
}
var expression = Expression.Lambda<Func<TSource, string>>(inner, parameter);
return expression;
}
I am calling it like this:
.ForMember(dest => dest.Status, option =>
option.MapFrom(src =>
EnumHelper.ExpressionHelper
.CreateEnumToStringExpression((ShippingContainerHeader s) => s.StatusId)
))
The error I am getting when I run this is:
LINQ to Entities does not recognize the method 'System.String ToString()' method, and this method cannot be translated into a store expression..
A.) I think I am calling this wrong maybe? Shouldn't I be using the reference to src in the MapFrom method call in order to pass the actual value of StatusId to the expression method?
B.) Seems like there is an issue with the expression generation method as it is producing the ToString() error. There is not an explicit .ToString() call in that method, but it must be implied somehow.
C.) Understanding the code: I can follow all of the code within the CreateEnumToStringExpression method except what the parameter variable is and the final expression variable expression statement. Also the generic types I'm confused on...there are a lot of generic types and the call to the method from the linked example doesn't explicitly define any of them.
In Expression<Func<TSource, String>> I'm pretty sure TSource is the input, which is an int. String is the output, which would be the enum name.
In CreateEnumToStringExpression<TSource, TMember> and Expression<Func<TSource, TMember>> memberAccess: TSource again is int, TMember is the enum class type.
But do you not have to explicitly call these out when calling the method? Something like: CreateEnumToStringExpression<int, StatusesEnum>(new Expression<Func<int, ShippingContainerHeader>> { s => s.StatusId })

How to add extra parameter to Expression<Func<T, bool>> predicate in Generic Repository

I'm using generic repository and I need to add extra parameter in one of the repository methods. For instance I have isDeleted column on database, I want to add this column as false to predicate.
How can I add extra parameter to predicate from repository method? This extra parameter is fixed for all tables. (isDeleted = false)
This is my original method which is getting single record from db.
public T GetSingle(Expression<Func<T, bool>> expression)
{
return _dbSet.Where(expression).SingleOrDefault();
}
This is updated version to add extra parameter I came so far.
public T GetSingle(Expression<Func<T, bool>> expression)
{
Expression<Func<T, bool>> extra = d => d.GetType().GetProperty("isDeleted").Equals(false);
var exp = Expression.And(expression.Body, extra.Body);
var body = Expression.And(expression.Body, extra.Body);
var lambda = Expression.Lambda<Func<T, bool>>(body, extra.Parameters);
return _dbSet.Where(lambda).SingleOrDefault();
}
But this updated version gives lambda body like this and ofcouse it's not working.
((d.ID == value(ProjectName.Namespace.Controllers.ControllerName).User.CompanyId) And d.GetType().GetProperty("isDeleted").Equals(Convert(False)))
public T GetSingle<T>(Expression<Func<T, bool>> expression)
{
var #params = expression.Parameters;
var checkNotDeleted = Expression.Equal(Expression.PropertyOrField(#params[0], "isDeleted"), Expression.Constant(false));
var originalBody = expression.Body;
var fullExpr = Expression.And(originalBody, checkNotDeleted);
var lambda = Expression.Lambda<Func<T, bool>>(fullExpr, #params);
return _dbSet.Where(lambda).SingleOrDefault();
}
Once you start using Expression's methods, you need to always use them (except for Expression.Constant, of course). All logic and code you write must be represented with Expression nodes.
There's a method PropertyOrField which reads the value of a property from a particular expression. In this case, we're reading it from the parameter (that is, d => d.isDeleted - we're writing the d.isDeleted part). Then we need to compare the value to false.
Finally, we simply And the original expression to ours, and create a lambda with the original parameters.
Take a look to PredicateBuilder, in particular the "Generic Predicates" section that explains how to create generic constraints to filter your items
You can have a BaseEntity which contains IsDeleted property.
All the other entities should be child of this BaseEntity.
Now in the Repository,
public class Repository<T> where T : BaseEntity
{
....
}
And in the query, you can add expression for IsDeleted also.
Please have a look here: Generic Repository Pattern - Entity Framework, ASP.NET MVC and Unit Testing Triangle
You can change method to:
public T GetSingle(param Expression<Func<T, bool>>[] expression)
Use array, and key word param.
Than you can send two, three, four or what ever number of parameters you want.

Combine property selector expression tree and value to create a predicate for EF filtering - create filter from lambda selector and value

Given a simple class with arbitrary properties (for discussion lets say Id, Name, and Description)
and given an instance of that class, I want to find matching entries in the database by specifying the property to match
I'm trying to do something in this respect similar to the AddOrUpdate method of EF, but I need the entity returned to me for further processing.
var match = new SomeClass{Name="Whatever"};
var found = Context.SomeClass.Find(x=>x.Name, match);
public static T Find<T>(this DbSet<T> set, Expression<Func<T, object>> matchOn, T matchAgainst) where T : class {
var func = matchOn.Compile();
var valueToFind = func(matchAgainst);
var combinedExpression = //matchon + "=" + valueToFind;
var found = set.FirstOrDefault(combinedExpression);
return found;
}
That gives me the value of the property in the passed in object, but I need to now combine that value with the passed in expression and pass it to the db set.
IE, the code I'm effectively trying to run is set.FirstOrDefault(x=>x.Name==valueToFind) How do I take the matchon expression (which contains x=>x.Name) and combine that with the ==valueToFind to get the x=>x.Name==valueToFind from them?
How do I build the combined expression? (I realize the "string" code above is completely wrong, but I was trying to get across what I need the function to do, but I have no idea what that syntax would look like.)
For manually coded examples, it would be easy enough just to pass in a hardcoded lambda with the value set, but my use case involves running through a collection of objects and finding the match for each one, so the value will not be known until runtime, and the method must work against arbitrary types and various properties, so I can't hardcode the property name either.
If you have a property selector, and a value to compare to, you can get an expression tree like this:
public static Func<TEntity, bool> GetComparer<TEntity,TProperty>(
Expression<Func<TEntity,TProperty>> selector, TProperty value)
{
var propertyRef = selector.Body;
var parameter = selector.Parameters[0];
var constantRef = Expression.Constant(value);
var comparer
= Expression.Lambda<Func<TEntity, bool>>
(Expression.Equal(propertyRef, constantRef), parameter)
.Compile();
return comparer;
}
Sample usage:
var comparer = GetComparer<Person, string>(p => p.Name, "John");
var persons = Person.GetPersons();
var john = persons.FirstOrDefault(comparer);

Creating an IQueryable to Equal int32?

I am trying to create an query extension which would compare a nullable int sql column value with a value. But i am struggling already over 8 hours to find any working solution.
I have already found a lot of help on this side. But all the remarks did not helped me.
I have altered the code so many times, but nothing seems to work. I want to create something similar as WHERE ManagerID IN (10,20,30)
The main code
IQueryable<Users> query = _context.CreateObjectSet<Users>();
query = query.IsMember(a => a.ManagerID, new Int32?[] { 10,20,30 });
return query.ToList();
Currently while executing the query.ToList(); it returns me a
Unable to create a constant value of type 'System.Object'. Only primitive types or enumeration types are supported in this context.
public static IQueryable<T> IsMember<T>(this IQueryable<T> source, Expression<Func<T, Int32?>> stringProperty, params Int32?[] searchTerms)
{
if (searchTerms == null || !searchTerms.Any())
{
return source;
}
Expression orExpression = null;
foreach (var searchTerm in searchTerms)
{
var searchTermExpression = Expression.Constant(searchTerm, typeof(object)); // <<--- This cast would make it no longer a primitive type
var containsExpression = Expression.Call(stringProperty.Body, typeof(Int32?).GetMethod("Equals"), searchTermExpression);
orExpression = BuildOrExpression(orExpression, containsExpression);
}
var completeExpression = Expression.Lambda<Func<T, bool>>(orExpression, stringProperty.Parameters);
return source.Where(completeExpression);
}
private static Expression BuildOrExpression(Expression existingExpression, Expression expressionToAdd)
{
return existingExpression == null ? expressionToAdd : Expression.OrElse(existingExpression, expressionToAdd);
}
The line marked to give the constant another datatype, is indeed causing the issue, but if I dont make it of type object, the Expression will not work, as it could not match the Int32? datatype.
Can anybody help me?
thanks
=============================================
Additional info
It is indeed to had a a larger picture.
I just want to create something more dynamic which could be used on other projects also.
I would like to use some functions which will look appealing than all those multilines
query = query.Like(a => a.UserName, filter.UserName, true);
query = query.Equals(a => a.UserTown, filter.UserTown, true);
query = query.IsMember(a => a.Division, filter.Division); // is an array of possible divisions
it worked fine for Like and Equals which are string based. But want to have a similar product for (nullable) integers
I was inspired by the following post. Which created a search function (which i renamed for my project to Like)
link
I wanted to create others similar. the last boolean is to verify if nullable in the column is allowed or not.
The reason also to use an extension, is that i also have alot of filters in my filter page.
With this extension, i would easily check in the beginning of my Like and Equal function if a filter was given without checking if my filter has a value 20x.
public static IQueryable<T> Like<T>(this IQueryable<T> source, Expression<Func<T, string>> stringProperty, string searchTerm, bool isnullValueAllowed)
if (String.IsNullOrEmpty(searchTerm))
{
return query;
}
It's not clear why you want to create this extension as you could simply write something like:
query.Where(user => (new[]{10,20,30}).Contains(user.ManagerId)).ToList();
However, assuming the real use case is somewhat more complicated than the example you've given, could you not construct your expression as either a comparison against a constant Int32 or a comparison against null, depending on whether searchTerm.HasValue() was true?
You need to use the equality operator == rather than the Equals method here. It actually makes your expression code a tad simpler:
foreach (var searchTerm in searchTerms)
{
var comparison = Expression.Equals(stringProperty.Body,
Expression.Constant(searchTerm));
orExpression = BuildOrExpression(orExpression, comparison);
}
Of course, as is mentioned by others, you don't need to build up an expression representing an OR of each of these comparisons, you can simply use Conatains on the set in a lambda and the query provider will do all of this for you.

Categories