如何使用反射调用IDBSet<T>.FirstOrDefault(predicate)?

4

如果给定一个包含“Id”属性的Person IDbSet,我该如何通用地执行以下命令:

var p = PersonDbSet.FirstOrDefault(i=>i.Id = 3);

我可以构建谓词,并获得对FirstOrDefault扩展方法的引用,但似乎无法将它们整合起来:

首先是谓词

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

现在是关于扩展方法的参考:
    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 });            

最后尝试执行该方法:

object retVal = genericMethod.Invoke(null, new object[] {dbSet, predicate});

抛出一个带有以下信息的ArgumentException:

"类型为'System.Reflection.RuntimePropertyInfo'的对象无法转换为类型为'System.Linq.IQuerable`1[Person]'的对象。"

有什么想法吗?


1
你可能想要使用 Queryable 类中的 FirstOrDefault,因为使用 Enumeable.FirstOrDefault 会获取所有记录并在内存中进行过滤。 - Lee
谢谢你发现了这个问题。我已经更新了代码示例以反映其当前状态,包括扩展方法类型的更改和从调用返回的错误消息。 - cdarrigo
3个回答

3
你需要制作该方法的通用版本:
MethodInfo genericMethod = method.MakeGenericMethod(entityType);

object retVal = genericMethod.Invoke(dbSet, new object[] {expr});

顺便说一下,你应该尝试从System.Linq.Queryable中获取方法吗?System.Linq.Enumerable完全涉及对象的linq,看起来你正在尝试调用数据库。


谢谢你的提醒!我已经更新了问题,以反映当前的代码状态和异常信息。 - cdarrigo

0
我找到了问题所在... 我在反射数据上下文以获取dbset时,返回的是属性信息而不是属性的值。
现在以上代码运行得很好... 感谢大家的帮助!

0

我遇到了同样的问题。这段代码对我也起作用。希望能对你有所帮助...

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

请添加一个简短的解释,不需要解析相当大的代码片段,来说明您的解决方案。 - Brian

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接