How to make LINQ-to-Objects handle projections? - c#

I have implemented a basic (naive?) LINQ provider that works ok for my purposes, but there's a number of quirks I'd like to address, but I'm not sure how. For example:
// performing projection with Linq-to-Objects, since Linq-to-Sage won't handle this:
var vendorCodes = context.Vendors.ToList().Select(e => e.Key);
My IQueryProvider implementation had a CreateQuery<TResult> implementation looking like this:
public IQueryable<TResult> CreateQuery<TResult>(Expression expression)
{
return (IQueryable<TResult>)Activator
.CreateInstance(typeof(ViewSet<>)
.MakeGenericType(elementType), _view, this, expression, _context);
}
Obviously this chokes when the Expression is a MethodCallExpression and TResult is a string, so I figured I'd execute the darn thing:
public IQueryable<TResult> CreateQuery<TResult>(Expression expression)
{
var elementType = TypeSystem.GetElementType(expression.Type);
if (elementType == typeof(EntityBase))
{
Debug.Assert(elementType == typeof(TResult));
return (IQueryable<TResult>)Activator.CreateInstance(typeof(ViewSet<>).MakeGenericType(elementType), _view, this, expression, _context);
}
var methodCallExpression = expression as MethodCallExpression;
if(methodCallExpression != null && methodCallExpression.Method.Name == "Select")
{
return (IQueryable<TResult>)Execute(methodCallExpression);
}
throw new NotSupportedException(string.Format("Expression '{0}' is not supported by this provider.", expression));
}
So when I run var vendorCodes = context.Vendors.Select(e => e.Key); I end up in my private static object Execute<T>(Expression,ViewSet<T>) overload, which switches on the innermost filter expression's method name and makes the actual calls in the underlying API.
Now, in this case I'm passing the Select method call expression, so the filter expression is null and my switch block gets skipped - which is fine - where I'm stuck at is here:
var method = expression as MethodCallExpression;
if (method != null && method.Method.Name == "Select")
{
// handle projections
var returnType = method.Type.GenericTypeArguments[0];
var expType = typeof (Func<,>).MakeGenericType(typeof (T), returnType);
var body = method.Arguments[1] as Expression<Func<T,object>>;
if (body != null)
{
// body is null here because it should be as Expression<Func<T,expType>>
var compiled = body.Compile();
return viewSet.Select(string.Empty).AsEnumerable().Select(compiled);
}
}
What do I need to do to my MethodCallExpression in order to be able to pass it to LINQ-to-Objects' Select method? Am I even approaching this correctly?

(credits to Sergey Litvinov)
Here's the code that worked:
var method = expression as MethodCallExpression;
if (method != null && method.Method.Name == "Select")
{
// handle projections
var lambda = ((UnaryExpression)method.Arguments[1]).Operand as LambdaExpression;
if (lambda != null)
{
var returnType = lambda.ReturnType;
var selectMethod = typeof(Queryable).GetMethods().First(m => m.Name == "Select");
var typedGeneric = selectMethod.MakeGenericMethod(typeof(T), returnType);
var result = typedGeneric.Invoke(null, new object[] { viewSet.ToList().AsQueryable(), lambda }) as IEnumerable;
return result;
}
}
Now this:
var vendorCodes = context.Vendors.ToList().Select(e => e.Key);
Can look like this:
var vendorCodes = context.Vendors.Select(e => e.Key);
And you could even do this:
var vendors = context.Vendors.Select(e => new { e.Key, e.Name });
The key was to fetch the Select method straight from the Queryable type, make it a generic method using the lambda's returnType, and then invoke it off viewSet.ToList().AsQueryable().

Related

How to use a variable when making a lambda expression using Expression.Lambda<Func<>>()

I have a linq query which is as follows:
var query = _appDbContext.Persons
.Where(p => p.Id == 123)
.Select(lambda)
.ToList();
I use lambda because i have a variable in my select statement. The lambda:
var wantedData = "Adress";
var x = Expression.Parameter(typeof(Person), "x");
var body = Expression.PropertyOrField(x, wantedData);
var lambda = Expression.Lambda<Func<Person, Adress>>(body, x);
The query will return the addresses for person with id 123. Let's say instead of the addresses I would like to recieve all subscriptions this person has. Only setting wantedData = "Subscription" will not work, because of the "Adress" in the lambda statement. So I'm looking for a way to use a variable to change "Adress" in the lambda statement.
I tried the following, which obviously does not work.
var lambda = Expression.Lambda<Func<Person, wantedData>>(body, x);
Is there a way to do this?
Ok, I managed to create a generic query method that takes an Expression as parameter.
EDIT: This will receive a string value and will try to match it with an existing property on your Person class.
private IEnumerable<TResult> GetPersonData<TResult>(Expression<Func<Person, TResult>> selectExpression)
{
return _dbContext.Persons
// .Where(filter)
.Select(selectExpression)
.ToList();
}
public IEnumerable<object> GetData(string dataPropertyName)
{
switch(dataPropertyName) {
case nameof(Person.Address): return GetPersonData(p => p.Address);
case nameof(Person.Subscription): return GetPersonData(p => p.Subscription);
// other cases
default: throw new InvalidArgumentException("Invalid property name");
}
}
note that this code is just an example written on the spot and it might not work directly with copy-paste
I've made something similar to an OrderBy and tweak it a little for your select. This only works if you know the return type.
You can create an extension method with the following code:
public static IQueryable<TResult> Select<T,TResult>(this IQueryable<T> source, string propertyName)
{
var prop = typeof(T).GetProperties()
.FirstOrDefault(x => x.Name.ToUpper() == propertyName.ToUpper());
ParameterExpression pe = Expression.Parameter(typeof(T), "x");
MemberExpression body = Expression.Property(pe, prop.Name);
LambdaExpression lambda = Expression.Lambda(body, new ParameterExpression[] { pe });
MethodCallExpression selectCallExpression = Expression.Call(
typeof(Queryable),
"Select",
new Type[] { source.ElementType, prop.PropertyType },
source.Expression,
lambda);
return source.Provider.CreateQuery<TResult>(selectCallExpression);
}
That way, you can use it like:
var query = _appDbContext.Persons
.Where(p => p.Id == 123)
.Select<Person,string>(wantedData)
.ToList();

Expression class, change boolean to string compare

Our database had IsActive booleans everywhere as a soft delete. We have entity framework blocking all requests with that flag deactivated. We recently changed out database to instead have a statusCode string instead of an IsActive flag.
How would I change the below expression to instead check to see if the StatusCode == "ACTIVE"
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
var isActiveProperty = entityType.FindProperty("IsActive");
if (isActiveProperty != null && isActiveProperty.ClrType == typeof(bool))
{
var parameter = Expression.Parameter(entityType.ClrType, "p");
var filter = Expression.Lambda(Expression.Property(parameter, isActiveProperty.PropertyInfo), parameter);
entityType.QueryFilter = filter;
}
}
I tried changing the types to the following, but it is not working. Lambda's written out this way is throwing me off (Lambda/parameter long form). Somewhere I need to compare (isActivyProperty == "ACTIVE")
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
var isActiveProperty = entityType.FindProperty("StatusCode");
if (isActiveProperty != null && isActiveProperty.ClrType == typeof(string))
{
var parameter = Expression.Parameter(entityType.ClrType, "p");
var filter = Expression.Lambda(Expression.Property(parameter, isActiveProperty.PropertyInfo), parameter);
entityType.QueryFilter = filter;//entityType.SetQueryFilter(filter);//Update for ef 3.0
}
}
You were generating code like the following for each type;
.QueryFilter = (p) => p.IsActive;
Now you want
.QueryFilter = (p) => p.StatusCode == "ACTIVE";
I find it useful to write the expression I want in C#, then use the debugger to see how the C# compiler turned that into an expression graph. Or look through the static methods on the Expression class and guess what I might need.
In your case you need your lambda body to include the extra equality test and constant value;
Expression.Equal(Expression.Property(...), Expression.Constant("ACTIVE"))
But there are other ways to use the C# compiler to create the Expression you want. For example;
public interface HasStatus {
StatusCode { get; set; }
}
public static void SetQueryFilter<T>(ModelBuilder builder) where T:HasStatus =>
builder.Entity<T>().HasQueryFilter(p => p.StatusCode == "ACTIVE");
typeof(...).GetMethod("SetQueryFilter")
.MakeGenericMethod(entityType.ClrType)
.Invoke(null, new object[]{ builder });
I think you're after:
Expression.Lambda(Expression.Equal(Expression.Property(parameter, "StatusCode"),
Expression.Constant("ACTIVE", typeof(string))), parameter);
Note: if FindProperty returns the member, you may also be able to use it in Expression.Property to avoid a second reflection lookup:
Expression.Lambda(Expression.Equal(Expression.Property(parameter, isActiveProperty),
Expression.Constant("ACTIVE", typeof(string))), parameter);

How to Invoke IDBSet<T>.FirstOrDefault(predicate) using reflection?

given an IDbSet where Person contains an "Id" property, how can I execute the following command generically:
var p = PersonDbSet.FirstOrDefault(i=>i.Id = 3);
I can build up the predicate, and get a reference to the FirstOrDefault extension method, but I can't seem to put it all together:
First the predicate
ParameterExpression parameter = Expression.Parameter(entityType, "Id");
MemberExpression property = Expression.Property(parameter, 3);
ConstantExpression rightSide = Expression.Constant(refId);
BinaryExpression operation = Expression.Equal(property, rightSide);
Type delegateType = typeof (Func<,>).MakeGenericType(entityType, typeof (bool));
LambdaExpression predicate = Expression.Lambda(delegateType, operation, parameter);
Now a reference to the extension method:
var method = typeof (System.Linq.Queryable).GetMethods(BindingFlags.Static | BindingFlags.Public)
.FirstOrDefault(m => m.Name == "FirstOrDefault" && m.GetParameters().Count() == 2);
MethodInfo genericMethod = method.MakeGenericMethod(new[] { entityType });
Finally try to execute the method:
object retVal = genericMethod.Invoke(null, new object[] {dbSet, predicate});
throws an ArgumentException with this message:
"Object of type 'System.Reflection.RuntimePropertyInfo' cannot be converted to type 'System.Linq.IQuerable`1[Person]'."
Any thoughts?
You have to make generic version of the method:
MethodInfo genericMethod = method.MakeGenericMethod(entityType);
object retVal = genericMethod.Invoke(dbSet, new object[] {expr});
btw. shouldn't you try to get the method from System.Linq.Queryable? System.Linq.Enumerable is all about linq to objects and looks like you're trying to call your DB.
I figured out the issue.. I was reflecting my data context to get the dbset, and returning the property info rather than the property's value.
The above code works great now.. Thanks to all for their help!
I've got same issue. And this code works for me as well. Hope this will help you...
public async Task<Unit> Handle(UpdateCustomCommand<TType> request, CancellationToken cancellationToken)
{
//TODO: set access based on user credentials
var contextCollectionProp = _context.GetType()
.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.FirstOrDefault(e => e.PropertyType == typeof(DbSet<TType>));
if (contextCollectionProp == null)
{
throw new UnSupportedCustomEntityTypeException(typeof(TType), "DB Contexts doesn't contains collection for this type.");
}
var firstOrDefaultMethod = typeof(System.Linq.Queryable).GetMethods(BindingFlags.Static | BindingFlags.Public)
.FirstOrDefault(m => m.Name == "FirstOrDefault" && m.GetParameters().Count() == 2);
if (firstOrDefaultMethod == null)
{
throw new UnSupportedCustomEntityTypeException(typeof(TType), "Cannot find \"Syste.Linq.FirstOrDefault\" method.");
}
Expression<Func<TType, bool>> expr = e => request.Id.Equals(e.ID);
firstOrDefaultMethod = firstOrDefaultMethod.MakeGenericMethod(typeof(TType));
TType item = firstOrDefaultMethod.Invoke(null, new[] { contextCollectionProp.GetValue(_context), expr }) as TType;
if (item == null)
{
throw new NotFoundException(nameof(TType), request.Id);
}
//TODO: use any mapper instead of following code
item.Code = request.Code;
item.Description = request.Description;
await _context.SaveChangesAsync(cancellationToken);
return Unit.Value;
}

Find a method using parameter types where parameters are generic

I am sure this is a duplicate, but I can not find the answer.
System.Linq.Queryable has a method with following signature:
public static int Count<TSource>(
this IQueryable<TSource> source,
Expression<Func<TSource, bool>> predicate);
How do I use System.Type::GetMethod method to get this method?
typeof(System.Linq.Queryable).GetMethod("Count", type[]{ ??? });
You could use
var method = typeof (System.Linq.Queryable).GetMethods().Single(a=>a.Name == "Count" && a.GetParameters().Length == 2);
And after, if you want to invoke it
method.MakeGenericMethod(typeof (YourType));
For additional, it could be filtered (for different selects):
var sel1 = typeof (Queryable).GetMethods().Single(a => a.Name == "Select"
&& a.MakeGenericMethod(typeof(object), typeof(object)).GetParameters()[1].ParameterType == typeof(Expression<Func<object, object>>));
var sel2 = typeof(Queryable).GetMethods().Single(a => a.Name == "Select"
&& a.MakeGenericMethod(typeof(object), typeof(object)).GetParameters()[1].ParameterType == typeof(Expression<Func<object,int, object>>));
The method with the least effort would probably be to capture the method you want with an expression tree, then delve in to find the method reference. You can do this once to get the generic method definition, then cache the result and reuse it as necessary.
Expression<Func<IQueryable<int>, int>> getCount = p => p.Count();
MethodInfo countMethod = ((MethodCallExpression)getCount.Body).Method.GetGenericMethodDefinition();
This has the benefit of being somewhat resilient to API changes (e.g., the addition of more "Count" members); the method returned will be whatever method your expression binds to at compile time.
You could extract this behavior out into utility methods if you like:
public static MethodInfo MethodOf(Expression<Action> accessExpression, bool dropTypeArguments = false)
{
if (accessExpression == null)
throw new ArgumentNullException("accessExpression");
var callExpression = accessExpression.Body as MethodCallExpression;
if (callExpression == null)
throw new ArgumentException("Expression body must be a method call.", "accessExpression");
var method = callExpression.Method;
if (dropTypeArguments && method.IsGenericMethod)
return method.GetGenericMethodDefinition();
return method;
}
public static MethodInfo MethodOf<TInstance>(Expression<Action<TInstance>> call, bool dropTypeArguments = false)
{
if (call == null)
throw new ArgumentNullException("call");
var callExpression = call.Body as MethodCallExpression;
if (callExpression == null)
throw new ArgumentException("Expression body must be a method call.", "call");
var method = callExpression.Method;
if (dropTypeArguments && method.IsGenericMethod)
return method.GetGenericMethodDefinition();
return method;
}
Use the first overload for static-style invocations and the latter for instance-style invocations, e.g.:
var countMethod1 = Extensions.MethodOf(() => Queryable.Count(default(IQueryable<int>)), dropTypeArguments: true);
var countMethod2 = Extensions.MethodOf((IQueryable<int> p) => p.Count(), dropTypeArguments: true);
To preserve the type arguments (e.g., to resolve Count<int>() instead of Count<T>()) just omit the dropTypeArguments: true argument or set it to false.
Note that these aren't terribly comprehensive; they will not, for example, drop generic parameters on the declaring type (only on the method itself). Feel free to use, extend, or throw away :).

linq to entities and store expression

I work on project that use some dynamic linq query to an entities.
i have huge amount of case and to avoid code duplication i refactoring to a method.
But using method which isn't in store expression will result to throw an exception.
One of solutions is to encapsulate method result into an expression which can be interpreted by linq to entitie query.
Consider that code :
parentExpression = x => x.child.Any(y=>IsGoodChild(y,childType, childSize));
private bool IsGoodChild(child c, int childType, int childSize){
return c.type == childType && c.size == childSize;
}
"parentExpression " is predicate of type "Parent" of my EF.
This code throw an exception, "IsGoodChild" method return a boolean and can't be interpreted by linq to Entities.
So, i would like something like this :
parentExpression = x => x.child.AsQueryable().Any(IsGoodChild(childType, childSize));
private System.Linq.Expression.Expression<Func<child, bool>> IsGoodChild(int childType, int childSize){
return ????
}
So how can i do "IsGoodChild(...)" can work even if which not take x.child attribute ?
Thx for advance
Re,
I try something, when i write lambda directly in expression like this :
parentExpression = x => x.child.Any(y=>y.type == childType && y.size == childSize);
i used extract method from resharper and generate it this :
private Expression<Func<child,Boolean>> IsGoodChildFunctional(Int32 childType, Int32 childSize)
{
return c => c.type == childType && c.size == childSize;
}
But i also have .NET Framework Data Provider error 1025' error ...
In this case the compiler is clever, given an anonymous method it will switch between an expression tree or a compiled lambda depending on the declared type. The following should work:
private Expression<Func<child,Boolean>>
IsGoodChildFunctional(Int32 childType, Int32 childSize)
{
return c => c.type == childType && c.size == childSize;
}
which would be used like so:
parentExpression = x => x.child
.AsQueryable()
.Any(IsGoodChildFunctional(childType,childSize));
Create a static generic method which will return an Expression. The Expression is built by using factory methods.
public static Expression<Func<TTargetObject,Boolean>> IsGoodChildFunctional<TTargetObject>(Int32 childType, Int32 childSize)
{
var e = Expression.Parameter(typeof(TTargetObject), "e");
var childTypeMember = Expression.MakeMemberAccess(e, typeof(TTargetObject).GetProperty("childType"));
var childSizeMember = Expression.MakeMemberAccess(e, typeof(TTargetObject).GetProperty("childSize"));
var childTypeConstant = Expression.Constant(childType, childType.GetType());
var childSizeConstant = Expression.Constant(childSize, childSize.GetType());
BinaryExpression b;
BinaryExpression bBis;
Expression<Func<TTargetObject, bool>> returnedExpression;
b = Expression.Equal(childTypeMember , childTypeConstant );
bBis2 = Expression.Equal(childSizeMember, c2);
var resultExpression = Expression.AndAlso(b, bBis);
returnedExpression = Expression.Lambda<Func<TTargetObject, bool>>(resultExpression , e);
return returnedExpression;
}
It is called like this:
var predicat = IsGoodChildFunctional<child>(childType, childSize);
parentExpression = x => x.child.Any(predicat);

Categories

Resources