为了账号安全,请及时绑定邮箱和手机立即绑定

如何使用可查询。在运行时设置类型时的位置?

如何使用可查询。在运行时设置类型时的位置?

C#
呼啦一阵风 2022-08-20 16:59:27
我正在使用 EF6 为应用实现搜索/筛选 UI 的后端。我有一些代码可以构建一个表达式来与Queryable一起使用。对于给定的DbSet,DbSet的类型是在运行时确定的(DBContext有很多,它们可能会改变)。如果我通过先将表达式转换为特定类型来作弊,则对 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 回答

?
慕容708150

TA贡献1831条经验 获得超4个赞

回答你的具体问题。鉴于

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


查看完整回答
反对 回复 2022-08-20
?
GCT1015

TA贡献1827条经验 获得超4个赞

恭喜你的第一个问题。


让我们首先看一下基于某些自定义筛选器筛选数据集合的方法。我将假设您更喜欢传入筛选器,将属性名称保存为键,将属性值保存为值。NameValueCollectionType


在继续筛选整个集合之前,让我们首先弄清楚如何确定一个对象是否具有与筛选器匹配的属性。由于我们直到运行时才知道对象,因此我们需要在 C# 中使用泛型来实现此目的。Type


步骤1

- 获取所有类属性

我们需要获取泛型类的所有属性,例如 .使用反射执行此操作被认为是缓慢的,Matt Warren解释了为什么反射在.NET中很慢以及如何解决它。因此,我们将实现类组件模型的缓存,以获取其存在于命名空间 System.ComponentModel.PropertyDescriptorCollection 中的类组件模型。<TClass>PropertyDescriptorCollection


组件缓存


private static IDictionary<string, PropertyDescriptorCollection> _componentsCache

        = new Dictionary<string, PropertyDescriptorCollection>();

我们的键表示泛型类的名称,值保存该给定类的名称。DictionaryPropertyDescriptorCollection


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

- 循环通过过滤器

在获取了变量中泛型类(如上所示)后,现在让我们遍历筛选器,看看其任何属性名称是否与任何筛选器键匹配。如果属性名称与任何筛选器键匹配,则现在我们检查属性的实际值是否与筛选器值匹配。为了提高搜索/筛选函数的质量,我们将在 C# 中使用正则表达式来确定比较是命中还是未命中。PropertyDescriptorCollectionTtypeDescriptorT


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;

    }

}

步骤3

- 实现扩展方法。

为了使我们编写的代码易于使用和重用,我们将在 C# 中实现扩展方法,以便我们可以在项目中的任何位置更好地重用我们的函数。


- 使用上述函数的通用集合筛选器函数。

由于 可以通过 .函数在 中,我们将在函数调用中使用它,如下所示。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));

}

步骤4

将所有内容放在一起。

现在我们已经拥有了所需的一切,让我们看看最终/完整代码在单个类中如何看起来像一个代码块。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>

每个都有一个函数,该函数返回可以用作 a 的函数,因此我们的函数可以使用,如下所示。DbContext.Set<T>DbSet<T>IQueryable<T>



_dbContext.Set<Question>().Filter(filters);

希望这能回答你的问题,或者更确切地说,为你指出正确的方向。


查看完整回答
反对 回复 2022-08-20
  • 2 回答
  • 0 关注
  • 78 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信