在运行时设置类型的地方如何使用Queryable。

时间:2019-02-01 18:06:15

标签: c# entity-framework iqueryable

我正在使用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;
    }

2 个答案:

答案 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#来完成此操作。

步骤1

-获取所有类属性

我们将需要获取通用类的所有属性,例如<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);
        }
}

步骤2

-遍历过滤器

如上所述,在变量PropertyDescriptorCollection中获得通用类T的{​​{1}}之后,现在让我们遍历过滤器,看看其属性名称是否与任何我们的过滤键如果typeDescriptor的属性名称与我们的任何过滤器键都匹配,那么现在我们检查该属性的实际值是否与我们的过滤器值匹配。为了提高搜索/过滤器功能的质量,我们将使用Regular Expressions in C#来确定比较是命中还是未命中。

T

步骤3

-实现扩展方法。

为了使我们编写的代码易于使用和重用,我们将实现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

步骤4

将所有内容放在一起。

现在我们有了所需的一切,让我们来看一下最终的/完整的代码在单个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>

希望这可以回答您的问题,或者可以为您指明正确的方向。