我正在使用EF6为应用实现搜索/过滤器UI的后端。我有构建可与Queryable一起使用的Expression的代码,其中对于给定的DbSet,DbSet的类型在运行时确定(DBContext有很多它们,并且它们可能会改变)。如果我先通过将Expression转换为特定类型作弊,则对Where的调用可以正常工作。否则,我会收到此错误:
“'System.Linq.Queryable.Where(System.Linq.IQueryable,System.Linq.Expressions.Expression>)'的最佳重载方法匹配'有一些无效参数'
我正在努力寻找一种过滤DbSet的方法,其中在运行时提供了基础“表”类型。这是一个大大简化的代码版本,用于说明:
void ProcessFilter(AppDbContext context, NameValueCollection filters, Type tableType)
{
// If tableType == typeof(Organisation), expression is a Expression<Func<Organisation, bool>>
var expression = GetFilterExpression(filters);
var dbset = Set(context, tableType);
dynamic dynamicSet = dbset;
// This fails
var results = Queryable.Where(dynamicSet, expression);
// see https://stackoverflow.com/questions/4285598/iqueryable-non-generic-missing-count-and-skip-it-works-with-iqueryablet
// Suppose tableType == typeof(Organisation)
// This works
var typedExpression = expression as Expression<Func<Organisation, bool>>;
var typedResults = Queryable.Where(dynamicSet, typedExpression);
}
public static IQueryable Set(DbContext context, Type T)
{
// Similar to code in
// https://stackoverflow.com/questions/21533506/find-a-specified-generic-dbset-in-a-dbcontext-dynamically-when-i-have-an-entity
var method = typeof(DbContext).GetMethods(BindingFlags.Public | BindingFlags.Instance).Where(x => x.Name == "Set" && x.IsGenericMethod).First();
// Build a method with the specific type argument
method = method.MakeGenericMethod(T);
return method.Invoke(context, null) as IQueryable;
}
答案 0 :(得分:1)
为您解答具体问题。给定
IQueryable source
LambdaExpression predicate
如何调用静态泛型方法
Queryable.Where<T>(IQueryable<T> source, Expression<Func<T, bool>> predicate)
可以使用(A)反射,(B)DLR动态分派和(C)Expression.Call
来完成。
您要尝试执行的是选项(B)。但是
var result = Queryable.Where((dynamic)source, predicate);
对具有第二个参数LambdaExpression
类型的方法进行动态搜索,这当然会失败。
为了能够动态匹配目标方法,还需要设置第二个参数dynamic
:
var result = Queryable.Where((dynamic)source, (dynamic)predicate);
上述等效选项(C)的实现是:
var result = source.Provider.CreateQuery(Expression.Call(
typeof(Queryable), nameof(Queryable.Where), new[] { source.ElementType },
source.Expression, predicate));
答案 1 :(得分:0)
恭喜您第一个问题。
让我们开始研究一种基于一些自定义过滤器过滤数据集合的方法。我将假设您更愿意传递过滤器NameValueCollection
Type
,将PropertyNames作为键,将PropertyValues作为值。
在继续筛选整个集合之前,让我们首先弄清楚如何确定一个对象是否具有与筛选器匹配的属性。而且由于直到运行时我们都不知道对象的Type
,所以我们需要使用Generics in C#来完成此操作。
我们将需要获取通用类的所有属性,例如<TClass>
。使用Reflection进行此操作被认为很慢,Matt Warren解释了Why Reflection is slow in .NET以及解决方法。因此,我们将实现类组件模型的缓存以获取其PropertyDescriptorCollection
,该{存在于命名空间System.ComponentModel.PropertyDescriptorCollection中。
组件缓存
private static IDictionary<string, PropertyDescriptorCollection> _componentsCache
= new Dictionary<string, PropertyDescriptorCollection>();
我们的Dictionary
的键代表泛型类的名称,并且值包含该给定类的PropertyDescriptorCollection
。
internal static bool InnerFilter<T>(T obj, NameValueCollection filters)
where T : class
{
Type type = typeof(T);
PropertyDescriptorCollection typeDescriptor = null;
if (_componentsCache.ContainsKey(type.Name))
typeDescriptor = _componentsCache[type.Name];
else
{
typeDescriptor = TypeDescriptor.GetProperties(type);
_componentsCache.Add(type.Name, typeDescriptor);
}
}
如上所述,在变量PropertyDescriptorCollection
中获得通用类T
的{{1}}之后,现在让我们遍历过滤器,看看其属性名称是否与任何我们的过滤键如果typeDescriptor
的属性名称与我们的任何过滤器键都匹配,那么现在我们检查该属性的实际值是否与我们的过滤器值匹配。为了提高搜索/过滤器功能的质量,我们将使用Regular Expressions in C#来确定比较是命中还是未命中。
T
为了使我们编写的代码易于使用和重用,我们将实现Extension Methods in C#,以便我们可以在项目中的任何地方更好地重用我们的功能。
由于for (int i = 0; i < filters.Count; i++)
{
string filterName = filters.GetKey(i);
string filterValue = filters[i];
PropertyDescriptor propDescriptor = typeDescriptor[filterName];
if (propDescriptor == null)
continue;
else
{
string propValue = propDescriptor.GetValue(obj).ToString();
bool isMatch = Regex.IsMatch(propValue, $"({filterValue})");
if (isMatch)
return true;
else
continue;
}
}
中的。IQueryable<T>
函数可以将IEnumerable<T>
转换为Where()
,因此我们将在函数调用中利用它,如下所示。
System.Linq
现在我们有了所需的一切,让我们来看一下最终的/完整的代码在单个public static IEnumerable<T> Filter<T>(this IEnumerable<T> collection, NameValueCollection filters)
where T : class
{
if (filters.Count < 1)
return collection;
return collection.Where(x => x.InnerFilter(filters));
}
类中作为一个代码块的样子。
static
public static class Question54484908
{
private static IDictionary<string, PropertyDescriptorCollection> _componentsCache = new Dictionary<string, PropertyDescriptorCollection> ();
public static IEnumerable<T> Filter<T> (this IEnumerable<T> collection, NameValueCollection filters)
where T : class
{
if (filters.Count < 1)
return collection;
return collection.Where (x => x.InnerFilter (filters));
}
internal static bool InnerFilter<T> (this T obj, NameValueCollection filters)
where T : class
{
Type type = typeof (T);
PropertyDescriptorCollection typeDescriptor = null;
if (_componentsCache.ContainsKey (type.Name))
typeDescriptor = _componentsCache[type.Name];
else {
typeDescriptor = TypeDescriptor.GetProperties (type);
_componentsCache.Add (type.Name, typeDescriptor);
}
for (int i = 0; i < filters.Count; i++) {
string filterName = filters.GetKey (i);
string filterValue = filters[i];
PropertyDescriptor propDescriptor = typeDescriptor[filterName];
if (propDescriptor == null)
continue;
else {
string propValue = propDescriptor.GetValue (obj).ToString ();
bool isMatch = Regex.IsMatch (propValue, $"({filterValue})");
if (isMatch)
return true;
else
continue;
}
}
return false;
}
}
,IEnumerable<T>
,数组这就是您将在项目中的任何地方使用以上代码的方式。
List<T>
private IEnumerable<Question> _questions;
_questions = new List<Question>()
{
new Question("Question 1","How do i work with tuples"),
new Question("Question 2","How to use Queryable.Where when type is set at runtime?")
};
var filters = new NameValueCollection
{
{ "Description", "work" }
};
var results = _questions.Filter(filters);
每个DbSet<T>
都有一个函数DbContext
,该函数返回一个可用作.Set<T>
的{{1}},因此可以使用我们的函数,如下所示。 / p>
示例
DbSet<T>
希望这可以回答您的问题,或者可以为您指明正确的方向。