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?

Answers


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

Need Your Help

How can I overlay one audio file over another one and save it?

android arrays audio merge overlay

What I am trying to accomplish is overlaying a vocal track over a music track to form a new song track.

JQGrid Form Edit/View Customizing the Template form data

jqgrid jqgrid-asp.net mvcjqgrid

I am using jqGrid form Edit with templating from the link in