如何在IQueryable LINQ中使用正则表达式搜索CosmosDB数据
问题描述
我希望在CosmosDB容器中使用正则表达式(RegEx)搜索数据。例如,容器中有如下文档:
{ "name": "test" }, { "name": "test_1" }, { "name": "test_data_1" }
当搜索名称为test或test_<number>时,希望匹配前两个文档。
我定义了如下模型:
public class TestObj { public Guid Id { get; set; } public string Name { get; set; } // 其他属性 }
并编写了按名称搜索TestObj对象的方法:
public async Task<List<TestObject>> GetManyByName(string searchName) { var regex = new Regex($"^{searchName}_\\d$", RegexOptions.IgnoreCase); var options = new QueryRequestOptions {PartitionKey = new PartitionKey("partitionId")}; var container = client.GetContainer("dbId", "containerId"); var iterator = container.GetItemLinqQueryable<TestObject>(false, null, options) .Where(x => x.Name == searchName || regex.IsMatch(x.Name)) .ToFeedIterator(); var results = new List<TestObject>(); while (iterator.HasMoreResults) { var feedResponse = await iterator.ReadNextAsync(); if (feedResponse.Any()) results.AddRange(feedResponse); } return results.ToList(); }
运行代码时出现异常:
DocumentClientException with status code 0, message: Method 'IsMatch' is not supported., inner exception: null, and response headers: null
看起来regex.IsMatch(x.Name)无法在IQueryable中使用,尝试Regex.IsMatch()也出现相同异常。已知CosmosDB支持RegEx,想咨询:是否可以在IQueryable中使用正则表达式?若不支持,有什么其他方法可以实现需求?优先使用LINQ方案,也接受其他实现方式。
解决方案
1. 使用LINQ的RegexMatch扩展方法(推荐)
CosmosDB的LINQ提供程序支持RegexMatch扩展方法,需引入Microsoft.Azure.Cosmos.Linq命名空间,该方法会被转换为CosmosDB原生支持的REGEX_MATCH系统函数,直接在服务端执行正则匹配。
修改后的代码:
using Microsoft.Azure.Cosmos.Linq; public async Task<List<TestObject>> GetManyByName(string searchName) { // 合并精确匹配和正则匹配的规则 var regexPattern = $"^{searchName}$|^{searchName}_\\d$"; var options = new QueryRequestOptions {PartitionKey = new PartitionKey("partitionId")}; var container = client.GetContainer("dbId", "containerId"); var iterator = container.GetItemLinqQueryable<TestObject>(false, null, options) .Where(x => x.Name.RegexMatch(regexPattern, RegexOptions.IgnoreCase)) .ToFeedIterator(); var results = new List<TestObject>(); while (iterator.HasMoreResults) { var feedResponse = await iterator.ReadNextAsync(); results.AddRange(feedResponse); } return results; }
2. 使用SQL查询字符串
若LINQ方式存在局限,可直接构造SQL查询,调用CosmosDB的REGEX_MATCH函数:
public async Task<List<TestObject>> GetManyByName(string searchName) { var regexPattern = $"^{searchName}$|^{searchName}_\\d$"; var queryText = $"SELECT * FROM c WHERE REGEX_MATCH(c.name, @pattern, 'i')"; var parameters = new QueryDefinition(queryText) .WithParameter("@pattern", regexPattern); var options = new QueryRequestOptions {PartitionKey = new PartitionKey("partitionId")}; var container = client.GetContainer("dbId", "containerId"); var iterator = container.GetItemQueryIterator<TestObject>(parameters, requestOptions: options); var results = new List<TestObject>(); while (iterator.HasMoreResults) { var feedResponse = await iterator.ReadNextAsync(); results.AddRange(feedResponse); } return results; }
SQL中的'i'参数对应忽略大小写的规则,和RegexOptions.IgnoreCase效果一致。
3. 客户端过滤(不推荐)
仅适合数据量极小的场景,先拉取分区内所有数据,再在客户端用正则过滤,性能较差:
public async Task<List<TestObject>> GetManyByName(string searchName) { var regex = new Regex($"^{searchName}$|^{searchName}_\\d$", RegexOptions.IgnoreCase); var options = new QueryRequestOptions {PartitionKey = new PartitionKey("partitionId")}; var container = client.GetContainer("dbId", "containerId"); var iterator = container.GetItemLinqQueryable<TestObject>(false, null, options) .ToFeedIterator(); var results = new List<TestObject>(); while (iterator.HasMoreResults) { var feedResponse = await iterator.ReadNextAsync(); results.AddRange(feedResponse.Where(x => regex.IsMatch(x.Name))); } return results; }
内容的提问来源于stack exchange,提问作者Koji
相关产品推荐
相关产品推荐

