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;
}