我试图根据值数组中的动态字段名称返回动态类型的Where过滤表的内容。
到目前为止我所拥有的:
public JsonResult GetRelationShips(string linkingTable, string idField, int[] ids)
{
var tableType = typeof(context).GetProperty(linkingTable);
var entityTable = tableType.GetValue(db) as IQueryable;
var method = typeof(List<int>).GetMethod("Contains");
var eParam = Expression.Parameter(tableType.PropertyType.GetGenericArguments()[0]);
var call = Expression.Call(Expression.Constant(ids.ToList()), method, Expression.Property(eParam, idField));
var func = typeof(Func<,>);
var genericFunc = func.MakeGenericType(tableType.PropertyType.GetGenericArguments()[0], typeof(bool));
var lambda = Expression.Lambda(genericFunc, call, eParam);
var results = typeof(System.Linq.Enumerable).GetMethods().Where(x => x.Name == "Where").First().Invoke(db, new object[] { lambda });
return Json(results);
}
最后一行给我一个错误:
无法对ContainsGenericParameters为true的类型或方法执行后期绑定操作。
老实说,我今天下午从互联网上的片段拼凑了这个。我不知道我在这里做什么,这对我来说是新的,我很想学习。只是试图避免SQL注入,项目的其余部分完全是Linq所以我正在努力。我也在学习通用类型,但是我不知道如何在这里使用它们。
答案 0 :(得分:3)
这一行代码中存在许多缺陷:
var results = typeof(System.Linq.Enumerable).GetMethods().Where(x => x.Name == "Where").First().Invoke(db, new object[] { lambda });
尝试拨打Enumerable.Where
而不是Queryable.Where
。这将导致检索整个表数据并在内存而不是数据库端执行过滤。
尝试调用可能错误的方法。 Where
有2个重载,并且未定义哪个将作为反射首先返回。
尝试调用泛型方法定义,导致您获得异常。您必须首先使用MakeGenericMethod
构造一个泛型方法并调用它。
尝试通过反射调用静态泛型扩展方法,就好像它是 instance 方法一样。相反,您应该将null
作为第一个参数传递给Invoke
并传递new object[] { entityTable, lambda }
作为第二个参数。
只需使用C#动态方法调度即可避免所有这些陷阱:
IQueryable results = Queryable.Where((dynamic)entityTable, (dynamic)lambda);
使用以下Expression.Call
重载可以简化整个代码:
public static MethodCallExpression Call(
Type type,
string methodName,
Type[] typeArguments,
params Expression[] arguments);
对于“调用”静态泛型扩展方法非常有用:
var query = (IQueryable)db.GetType().GetProperty(linkingTable).GetValue(db);
// e =>
var entity = Expression.Parameter(query.ElementType, "e");
// ids.Contains(e.idField)
// = Enumerable<int>.Contains(ids, e.idField)
var containsCall = Expression.Call(
typeof(Enumerable),
nameof(Enumerable.Contains),
new Type[] { typeof(int) },
Expression.Constant(ids),
Expression.Property(entity, idField)
);
// e => ids.Contains(e.idField)
var predicate = Expression.Lambda(containsCall, entity);
// query = query.Where(predicate);
query = Queryable.Where((dynamic)query, (dynamic)predicate);
您还可以避免动态Where
调用,并使用类似的基于Expression.Call
的方法来“调用”它,并结合IQueryProvider.CreateQuery
:
// query.Where(predicate)
// = Queryable.Where<ElementType>(query, predicate)
var whereCall = Expression.Call(
typeof(Queryable),
nameof(Queryable.Where),
new Type[] { query.ElementType },
query.Expression,
predicate
);
// query = query.Where(predicate)
query = query.Provider.CreateQuery(whereCall);
我提供了所有这些只是因为你说你渴望学习。处理此类任务的最简单方法(而不是重新发明轮子)是使用一些第三方包。例如,使用System.Linq.Dynamic包时,整个代码将是:
var query = ((IQueryable)db.GetType().GetProperty(linkingTable).GetValue(db))
.Where($"@0.Contains({idField})", ids);