1

我在 Cosmos DB 中收集了文档。文档可以有内部对象数组。所以模型看起来像这样:

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

它翻译成这样:

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

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

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

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

你知道我怎么能在 linq 中传递那个参数吗?

谢谢。

4

3 回答 3

2

据我所知,不幸的是,没有 LINQ 等效于ARRAY_CONTAINS (<arr_expr>, <expr> , bool_expr)重载。为了实现您的场景,现在您可以使用 SQL 查询。我们目前正在进行一组更改,这些更改将为该场景启用 LINQ。

编辑:可用的替代方法是将 Any 运算符与要匹配的属性的过滤器一起使用。例如,SQL 过滤器:ARRAY_CONTAINS(root.addresses, {"city": "Redmond"}, TRUE)等效于以下 LINQ 表达式:addresses.Any(address => address.city == "Redmond")

于 2018-09-19T22:37:14.243 回答
0

我的解决方案比解决方案更像是一个 hack,但它暂时有效,直到 .Any() 的全部功能存在。

我使用 Expressions 为我的文档动态构建 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;
    }  

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

戴夫

于 2018-11-28T11:26:08.770 回答
0

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

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

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

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

根据这个线程,微软最近开始Any为 Cosmos LINQ 提供程序开发一个真正的功能。

于 2018-11-27T15:06:11.890 回答