在运行时对具有属性名称和类型的IQueryable进行的查询

时间:2019-06-28 16:18:48

标签: c# linq reflection expression azure-cosmosdb

我需要在运行时知道属性名称和类型的情况下进行查询。我在IEnumerable<>上进行了反思,但是会因此而导致性能出现问题吗?

我想知道使用IQueryable<>是否有更好的方法? 我对Expressions有所了解,但不确定如何去做。

编辑:

目前,这似乎不是性能问题,但是我还没有在很大的工作负载下进行测试。

我需要搜索运行时已知的不同类型的多个字段。

var cosmosClient = new DocumentClient(new Uri(cosmosDBEndpointUrl), cosmosDBAuthorizationKey);
var feedOptions = new FeedOptions { MaxItemCount = -1, EnableCrossPartitionQuery = true };
var objects = cosmosClient.CreateDocumentQuery<MyObject>(collectionLink, feedOptions).AsEnumerable();

if (!string.IsNullOrEmpty(searchQuery))
{         
    var predicate = PredicateBuilder.New<MyObject>(); 

    foreach (var fieldToSearch in fieldsToSearch)
    {
      predicate = predicate.Or(x => x.GetPropertyValue(fieldToSearch).CheckDateTime().ToString()
                                     .Contains(searchQuery, StringComparison.InvariantCultureIgnoreCase));
      objects = objects.Where(predicate);
    }
}

objects = objects.Skip(index)
                 .Take(pageSize);

return objects.ToList();

此辅助方法:

public static object GetPropertyValue(this object obj, string propertyName)
{
    foreach (var part in propertyName.Split('.'))
    {
        if (obj == null) { return null; }

        Type type = obj.GetType();
        PropertyInfo info = type.GetProperty(part, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
        if (info == null) { return null; }

        obj = info.GetValue(obj, null);
    }
    return obj;
}

在这种特殊情况下,我不知道是否有更好的方法。

1 个答案:

答案 0 :(得分:1)

因此,在这种情况下是I do think you want IQueryable而不是IEnumerable。最容易记住的方法是,IEnumerable将所有数据加载到内存中,然后运行其余的LINQ查询,而IQueryable尝试在源上运行过滤器(如果可以随后将结果数据加载到其中)记忆。您确实需要使用支持IQueriable的ORM,例如LINQ to SQL或Entity Framework。对于CosmosDB,SDK是基于LINQ to SQL构建的。

通过使用IEnumerable,您将所有 all 数据从Cosmos加载到内存中,然后应用过滤器和分页。很好,虽然您只有几条记录,但是随着数据集的增长,您可能可以想象这将如何成为一个主要问题。通常,您希望在数据库中做尽可能多的工作,而只返回最少数量的结果。通过使用反射来构建谓词,这将大大超过任何性能方面的考虑。

IQueryable最好的部分之一是它继承自IEnumerable,因此与IEnumerable兼容的任何内容都将与IQueriable兼容。您需要做的就是摆脱AsEnumerable()


重要的是要注意,并不是所有的LINQ运算符都会自动得到支持,并且一旦系统命中了它就无法翻译,它将执行到目前为止所执行的操作,并执行其余的操作。文档中有list of available operators用于CosmosDB。

对于您的查询,最大的问题是,尽管支持您的Where()子句,但目前不支持Skip()Take()。这意味着每次执行此方法时,所有结果都将从CosmosDB返回,然后将评估分页。

SDK中有几种处理分页的方法。当前支持的方法是将MaxItemCount中的FeedOptions设置为页面大小。当前系统代替了Skip()函数,而是使用延续令牌。为了访问令牌,您可以使用AsDocumentQuery()。由于您必须单步浏览到下一页,因此缓存令牌可能非常有帮助-很难跳转。

第二个选项是使用.Net SDK的v3。当前处于预览状态,但available on Nuget。几个月前skip/take was enabled。在这种情况下,直到调用ToList()之前的所有内容都应转换为SQL并在CosmosDB中进行评估。