我在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中传递该参数?
谢谢。
答案 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;
}
希望这会有所帮助,或者如果有人有更好的方法,请一定要告诉我们!
戴夫