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;
}
Related
I have a task when the same table is used to work with different data which similar content. I use dynamic linq expressions to get data from sql.
The most complicated task for me was to choose distinct rows based on one field. The one of the tables is Parts, and I will show it as the sample.
I need to select the first part from db with each Description.
Sql request is:
SELECT * FROM [Parts] WHERE [Id] IN (SELECT MIN([Id]) FROM[Parts] GROUP BY [Description])
LINQ request is:
queryable = queryable.GroupBy(r => r.Dewcription).Select(g => g.FirstOrDefault());
I should rewrite LINQ query to expressions. I have successed with GROUP BY:
var grouped = queryable.GroupBy(SelectedField, type); // using
public static IQueryable GroupBy([NotNull] this IQueryable source, string Property, [NotNull] Type type)
{
PropertyInfo property = type.GetProperty(Property);
ParameterExpression parameter = Expression.Parameter(type, "r");
MemberExpression member = Expression.MakeMemberAccess(parameter, property);
LambdaExpression lambda = Expression.Lambda(member, parameter);
MethodCallExpression resultExpression = GroupByCall(source, lambda, property.PropertyType, type);
return source.Provider.CreateQuery(resultExpression);
}
// to apply Group by method
public static MethodCallExpression GroupByCall([NotNull] IQueryable queryable, LambdaExpression predicate, Type typeKey, Type typeSource)
{
MethodInfo MethodGroupBy = typeof(Queryable).GetMethods(BindingFlags.Static | BindingFlags.Public)
.First(m => m.Name.Equals("GroupBy", StringComparison.OrdinalIgnoreCase) && m.GetParameters().Length == 2).MakeGenericMethod(typeSource, typeKey);
Type QueryableType = typeSource.QueryableType();
return Expression.Call(null, MethodGroupBy, Expression.Convert(queryable.Expression, QueryableType), Expression.Quote(predicate));
}
// to get type of Queryable to convert from Queryable
public static Type QueryableType(this Type type)
{
var list = Activator.CreateInstance(typeof(List<>).MakeGenericType(type));
MethodInfo Method = typeof(Queryable).GetMethod(nameof(Queryable.AsQueryable), new[] { typeof(IEnumerable) });
var listAsQueryable = Method.Invoke(null, new[] { list });
return listAsQueryable.GetType();
}
The next step is to make expression for .Select(g => g.FirstOrDefault()). It is not possible to apply it directly, because the type of grouped entity is not known, and the result of grouping is IGrouping<string, object>, where object in fact is the type which we know on execution.
I need something like it:
var grouped = queryable.GroupBy(SelectedField, type).SelectGroupFirst(type);
public static IQueryable SelectGroupFirst([NotNull] this IQueryable source, [NotNull] Type type)
{
ParameterExpression parameter = Expression.Parameter(type, "r");
MemberExpression member = Expression.MakeMemberAccess(parameter, ...get enumerable...);
...apply First() or FirstOrDefault()...
LambdaExpression lambda = Expression.Lambda(member, parameter);
MethodCallExpression resultExpression = SelectCall(source, lambda, ...IGrouping<string, type>..., type);
return source.Provider.CreateQuery(resultExpression);
}
public static MethodCallExpression SelectCall(IQueryable queryable, LambdaExpression predicate, Type typeKey, Type typeSource)
{
if (queryable == null) return null;
var methods = typeof(Queryable).GetMethods(BindingFlags.Static | BindingFlags.Public)
.Where(m => m.Name.Equals("Select", StringComparison.OrdinalIgnoreCase));
MethodInfo MethodSelect = typeof(Queryable).GetMethods(BindingFlags.Static | BindingFlags.Public)
.First(m => m.Name.Equals("Select", StringComparison.OrdinalIgnoreCase) && m.GetParameters().Length == 2).MakeGenericMethod(typeSource, typeKey);
return Expression.Call(null, MethodSelect, queryable.Expression, Expression.Quote(predicate));
}
I have not found the way to make type definition of IGrouping<string, type> dynamically, to define the group enumeration property and to get access to it.
Alternative could be the way to mix usage of SQL and LINQ. Is there the way to use the above mentioned sql request for first selection of rows, and to modify it with LINQ for paging and filtering? I have not found any.
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();
I have a generic crud controller and I try to implement a generic search method.
The code works well with non-nullable fields, but the problem is when I search nullable fields. I get
System.NullReferenceException
protected IQueryable<TEntity> Filter(IQueryable<TEntity> filterable, string ParameterValue)
{
ConstantExpression constant = Expression.Constant(ParameterValue);
ParameterExpression parameter = Expression.Parameter(typeof(TEntity), "e");
MemberExpression[] members = new MemberExpression[filterProps.Count()];
MethodInfo method = typeof(string).GetMethod("StartsWith", BindingFlags.Public | BindingFlags.Instance, null, new Type[] { typeof(string) }, null);
for (int i = 0; i < filterProps.Count(); i++)
{
members[i] = Expression.Property(parameter, filterProps[i]);
}
Expression predicate = null;
foreach (var item in members)
{
MethodCallExpression callExpression = Expression.Call(item, method, constant);
predicate = predicate == null ? (Expression)callExpression : Expression.OrElse(predicate, callExpression);
}
var Lambda = Expression.Lambda<Func<TEntity, bool>>(predicate, parameter);
return filterable.Where(Lambda);
}
when I query a non-null column as "username" the code works fine, but I get System.NullReferenceException when I try to query a nullable column as "name"
A null check on the member will need to be included to make sure the member call can be invoked without exception.
//...omitted for brevity
Expression predicate = null;
foreach (var member in members) {
//e => e.Member != null
BinaryExpression nullExpression = Expression.NotEqual(member, Expression.Constant(null));
//e => e.Member.StartsWith(value)
MethodCallExpression callExpression = Expression.Call(member, method, constant);
//e => e.Member != null && e.Member.StartsWith(value)
BinaryExpression filterExpression = Expression.AndAlso(nullExpression, callExpression);
predicate = predicate == null ? (Expression)filterExpression : Expression.OrElse(predicate, filterExpression);
}
//...omitted for brevity
Comments added so you see the expression as it is being built.
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().
I have the following method to compare DTOs.
bool Equals<T1, T2>(T1 t1, T2 t2, params Expression<Func<T1, object>>[] accessors)
{
return !(
from accessor in accessors
select ((MemberExpression) accessor.Body).Member.Name into propertyName
let p1 = typeof (T1).GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)
let p2 = typeof (T2).GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)
let p1val = p1.GetValue(t1, null)
let p2val = p2.GetValue(t2, null)
where !Equals(p1val, p2val)
select p1val
).Any();
}
I can call this using (a and b are instances of objects that by convention share the same properties, but that aren't the same objects):
Equals(a, b, x => x.PropertyOne, x => x.PropertyTwo );
Which compares the the objects property by property, which is fine for most cases.
However, I found a case where I needed to compare objects that had properties of complex types and where I wanted to compare properties on the complex types instead of the objects. Something like this:
Equals(a, b, x => x.ComplexTypeProperty.ChildProp );
I have realised that I need to leave the comfy reflection comparison and enter the Expression land, but the main task here is to be able to express both a property accessor and a property accessor via a complex type property and that's where I'm lost.
Any pointers would be nice, thanks!
The task is not so complicated:
Determine property path or expressions that are given by expressions. For instance this extension method will give you this:
public static IEnumerable<string> GetPropertiesNames<T, G>(this Expression<Func<T, G>> pathExpression)
{
List<string> _propertyNames = new List<string>();
Expression expression = pathExpression.Body;
if (expression.NodeType == ExpressionType.Convert)
{
var convert = (UnaryExpression)pathExpression.Body;
expression = convert.Operand;
}
while (expression.NodeType == ExpressionType.MemberAccess)
{
MemberExpression memberExpression = (MemberExpression)expression;
if(!(memberExpression.Member is PropertyInfo))
throw new InvalidOperationException();
_propertyNames.Add(memberExpression.Member.Name);
expression = memberExpression.Expression;
}
if (expression.NodeType != ExpressionType.Parameter)
throw new InvalidOperationException();
return _propertyNames;
}
Aggregate expression for second type to create function that will return value:
var parameter = Expression.Parameter(typeof(T2));
var expressionToConvert = accessors[0]; //for future loop
var propertyChainDescriptor = expressionToConvert.GetPropertiesNames()
.Aggregate(new { Expression = (Expression)parameterCasted, Type = typeof(T2)},
(current, propertyName) =>
{
var property = current.Type.GetProperty(propertyName);
var expression = Expression.Property(current.Expression, property);
return new { Expression = (Expression)expression, Type = property.PropertyType };
});
var body = propertyChainDescriptor.Expression;
if (propertyChainDescriptor.Type.IsValueType)
{
body = Expression.Convert(body, typeof(object));
}
var t2PropertyRetriver = Expression.Lambda<Func<T2, object>>(body, parameter).Compile();
Now execute method that retrieve values and compare:
var t1PropertyRetriver = accessor[0].Compile();
var t1Value = t1PropertyRetriver(t1);
var t2Value = t2PropertyRetriver(t2);
var areEqual = object.Equals(t1Value,t2Value);
The good idea would be to add some caching of generated methods because compilation process is expensive.