How to Invoke IDBSet.FirstOrDefault(predicate)

2019-03-31 00:15发布

问题:

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?

回答1:

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.



回答2:

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!



回答3:

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