Cosmos DB-在LINQ中使用ARRAY_CONTAINS

时间:2018-09-19 18:57:27

标签: c# asp.net azure-cosmosdb azure-cosmosdb-sqlapi

我在Cosmos DB中收集了文档。文档可以具有对象的内部数组。因此模型如下所示:

jdbc:mysql://127.0.0.1:3306?zeroDateTimeBehavior=convertToNull

如果其中至少一个具有某种类型,我需要获取所有内部文档。

如果我这样创建查询:

public class Document
{
    public string Id { get; set; }

    public IList<InnerDocument> InnerDocuments { get; set; } 
}

public class InnerDocument
{
    public string Type { get; set; }

    public string Created { get; set; }
}

它像这样翻译:

var innerDocument = new InnerDocument()
{
    Type = "foo"
};

context.CreateDocumentQuery<Document>(uri, feedOptions)
    .Where(d => d.id == "sample" && d.InnerDocuments.Contains(innerDocument));

但是它什么也不返回,因为没有内部文档看起来像这样(所有内部文档也都创建了),所以我需要向ARRAY_CONTAINS添加第三个参数(它告诉我们文档上只有部分匹配就足够了),所以看起来应该像这样:

SELECT * FROM root 
WHERE (root[\"id\"] = "sample" 
    AND ARRAY_CONTAINS(root[\"innerDocuments\"], {\"type\":\"foo\"}))

我的问题是我没有弄清楚如何在linq中传递第三个参数。我还尝试编写IEqualityComparer,该方法始终返回true,但没有任何效果(效果是我得到了异常。)。

您是否知道如何在linq中传递该参数?

谢谢。

3 个答案:

答案 0 :(得分:2)

据我所知,不幸的是ARRAY_CONTAINS (<arr_expr>, <expr> , bool_expr)重载没有LINQ等效项。要实现您的方案,现在您可以使用SQL查询。我们目前正在研究一组更改,这些更改将在这种情况下启用LINQ。

答案 1 :(得分:0)

如果我理解正确,则希望检索具有给定属性值(在此示例中为"foo")的数组中具有内部文档的所有文档。

通常,您会使用.Where(d => d.InnerDocuments.Any(i => i.Type == "foo")),但Cosmos LINQ提供程序尚不支持Any

相反,您可以将此构造用作解决方法:

context.CreateDocumentQuery<Document>(uri, feedOptions)
    .Where(d => d.Id == "sample")
    .SelectMany(d => d.InnerDocuments.Where(i => i.Type == "foo").Select(i => d));

根据this thread,Microsoft最近开始为Cosmos LINQ提供程序开发一种真正的Any功能。

答案 2 :(得分:0)

我的解决方案比解决方案更像是一种hack,但是它可以暂时起作用,直到存在.Any()的全部功能为止。

我使用表达式为文档动态构建Where谓词,从而允许我传递CosmosSearchCriteria对象,该对象具有CosmosCriteria对象的列表,如下所示:

public class CosmosCriteria 
{

    public CosmosCriteria()
    {
        ContainsValues = new List<string>();
    }
    public CosmosCriteriaType CriteriaType { get; set; }
    public string PropertyName { get; set; }
    public string PropertyValue { get; set; }
    public ConvertedRuleComparitor Comparitor { get; set; }
    public DateRange Dates { get; set; }
    public List<string> ContainsValues { get; set; }
}

这使我可以通过本质上传入PropertyName和PropertyValue来查询Contact模型的任何属性。

我没有在这里研究其他变通方法,以查看是否可以使其与我的表达式树构建一起工作,而我却没有时间进行调查。

    public async Task<CosmosSearchResponse<Model.Contact>> 
    GetContactsBySearchCriteriaAsync(int pageSize, long companyId, 
    CosmosSearchCriteria searchCriteria, string continuationToken = null)
    {
        var collectionName = CreateCollectionName(companyId, Constants.CollectionType.Contacts);
        var feedOptions = new FeedOptions { MaxItemCount = pageSize };
        if (!String.IsNullOrEmpty(continuationToken))
        {
            feedOptions.RequestContinuation = continuationToken;
        }
        var collection = UriFactory.CreateDocumentCollectionUri(
                Configuration.GetValue<string>(Constants.Settings.COSMOS_DATABASE_SETTING),
                collectionName);

        IOrderedQueryable<Model.Contact> documents = Client.CreateDocumentQuery<Model.Contact>(
            collection,
            feedOptions
        );

        documents = (IOrderedQueryable<Model.Contact>)documents.Where(document => document.deleted != true);
        bool requiresConcatenation = false;
        foreach (var criteria in searchCriteria.Criteria)
        {
            switch (criteria.CriteriaType)
            {
                case Constants.CosmosCriteriaType.ContactProperty:
                    // This is where predicates for the documents.Where(xxxx) 
                    // clauses are built dynamically with Expressions.
                    documents = AddContactPropertyClauses(documents, criteria);
                    break;
                case Constants.CosmosCriteriaType.PushCampaignHistory:
                    requiresConcatenation = true;
                    break;
            }
        }

        documents = (IOrderedQueryable<Model.Contact>)documents.AsDocumentQuery();

        /*
            From this point onwards, we have to do some wizardry to get around the fact that there is no Linq to SQL
            extension overload for the Cosmos DB function ARRAY_CONTAINS (<arr_expr>, <expr> , bool_expr).
            The feature is planned for development but is not yet ready. 
            Keep an eye on the following for updates:
                https://stackoverflow.com/questions/52412557/cosmos-db-use-array-contains-in-linq
                https://feedback.azure.com/forums/263030-azure-cosmos-db/suggestions/11503872-support-linq-any-or-where-for-child-object-collect
        */
        if (requiresConcatenation)
        {
            var sqlString = documents.ToString();
            var jsonDoc = JsonConvert.DeserializeObject<dynamic>(sqlString); // Have to do this to remove the escaping
            var q = (string)jsonDoc.query;
            var queryRootAlias = Util.GetAliasNameFromQuery(q);

            if (queryRootAlias == string.Empty)
            {
                throw new FormatException("Unable to parse root alias from query.");
            }

            foreach (var criteria in searchCriteria.Criteria)
            {
                switch (criteria.CriteriaType)
                {
                    case Constants.CosmosCriteriaType.PushCampaignHistory:
                        q += string.Format(" AND ARRAY_CONTAINS({0}[\"CampaignHistory\"], {{\"CampaignType\":1,\"CampaignId\":{1}, \"IsOpened\": true }}, true) ", queryRootAlias, criteria.PropertyValue);
                        break;
                }
            }

            documents = (IOrderedQueryable<Model.Contact>)Client.CreateDocumentQuery<Model.Contact>(
                collection,
                q,
                feedOptions
            ).AsDocumentQuery();
        }

        var returnValue = new CosmosSearchResponse<Model.Contact>();
        returnValue.Results = new List<Model.Contact>();

        Console.WriteLine(documents.ToString());

        var resultsPage = await ((IDocumentQuery<Model.Contact>)documents).ExecuteNextAsync<Model.Contact>();
        returnValue.Results.AddRange(resultsPage);
        if (((IDocumentQuery<Model.Contact>)documents).HasMoreResults)
        {
            returnValue.ContinuationToken = resultsPage.ResponseContinuation;
        }

        return returnValue;
    }  

希望这会有所帮助,或者如果有人有更好的方法,请一定要告诉我们!

戴夫