如何批量检索实体?
How to batch retrieve entities?
在 Azure table 存储中,如何查询与分区中特定行键匹配的一组实体???
我正在使用 Azure table 存储,需要检索与分区中的一组行键匹配的一组实体。
基本上如果这是 SQL 它可能看起来像这样:
SELECT TOP 1 SomeKey
FROM TableName WHERE SomeKey IN (1, 2, 3, 4, 5);
我想节省成本并减少做一堆 table 检索操作,我可以使用 table 批处理操作来完成。出于某种原因,我收到一个异常:
"A batch transaction with a retrieve operation cannot contain any other operations"
这是我的代码:
public async Task<IList<GalleryPhoto>> GetDomainEntitiesAsync(int someId, IList<Guid> entityIds)
{
try
{
var client = _storageAccount.CreateCloudTableClient();
var table = client.GetTableReference("SomeTable");
var batchOperation = new TableBatchOperation();
var counter = 0;
var myDomainEntities = new List<MyDomainEntity>();
foreach (var id in entityIds)
{
if (counter < 100)
{
batchOperation.Add(TableOperation.Retrieve<MyDomainEntityTableEntity>(someId.ToString(CultureInfo.InvariantCulture), id.ToString()));
++counter;
}
else
{
var batchResults = await table.ExecuteBatchAsync(batchOperation);
var batchResultEntities = batchResults.Select(o => ((MyDomainEntityTableEntity)o.Result).ToMyDomainEntity()).ToList();
myDomainEntities .AddRange(batchResultEntities );
batchOperation.Clear();
counter = 0;
}
}
return myDomainEntities;
}
catch (Exception ex)
{
_logger.Error(ex);
throw;
}
}
如何在不手动遍历行键集并对每个行键执行单独的检索 table 操作的情况下实现目标?我不想承担与执行此操作相关的成本,因为我可能有数百个要过滤的行键。
由于有数百个行键,因此排除了将 $filter
与行键列表一起使用的可能性(无论如何都会导致部分分区扫描)。
根据您收到的错误,批处理似乎同时包含查询和其他类型的操作(这是不允许的)。从您的代码片段中,我不明白您为什么会收到该错误。
您唯一的其他选择是执行单个查询。不过,您可以异步执行这些操作,因此您不必等待每个 return。 Table 存储在给定分区上每秒提供超过 2,000 个事务,因此这是一个可行的解决方案。
不确定我一开始是怎么错过这个的,但这是 TableBatchOperation 类型的 MSDN 文档中的一个片段:
A batch operation may contain up to 100 individual table operations, with the requirement that each operation entity must have same partition key. A batch with a retrieve operation cannot contain any other operations. Note that the total payload of a batch operation is limited to 4MB.
我最终按照 David Makogon 的建议异步执行了各个检索操作。
我创造了自己的贫民窟link-table。我知道它效率不高(也许没问题),但我只在数据未在本地缓存时才发出此请求,这仅意味着切换设备。无论如何,这似乎有效。检查两个数组的长度让我推迟 context.done();
var query = new azure.TableQuery()
.top(1000)
.where('PartitionKey eq ?', 'link-' + req.query.email.toLowerCase() );
tableSvc.queryEntities('linkUserMarker',query, null, function(error, result, response) {
if( !error && result ){
var markers = [];
result.entries.forEach(function(e){
tableSvc.retrieveEntity('markerTable', e.markerPartition._, e.RowKey._.toString() , function(error, marker, response){
markers.push( marker );
if( markers.length == result.entries.length ){
context.res = {
status:200,
body:{
status:'error',
markers: markers
}
};
context.done();
}
});
});
} else {
notFound(error);
}
});
我制作了一个辅助方法来在每个分区的单个请求中完成它。
这样使用:
var items = table.RetrieveMany<MyDomainEntity>(partitionKey, nameof(TableEntity.RowKey),
rowKeysList, columnsToSelect);
这是帮助方法:
public static List<T> RetrieveMany<T>(this CloudTable table, string partitionKey,
string propertyName, IEnumerable<string> valuesRange,
List<string> columnsToSelect = null)
where T : TableEntity, new()
{
var enitites = table.ExecuteQuery(new TableQuery<T>()
.Where(TableQuery.CombineFilters(
TableQuery.GenerateFilterCondition(
nameof(TableEntity.PartitionKey),
QueryComparisons.Equal,
partitionKey),
TableOperators.And,
GenerateIsInRangeFilter(
propertyName,
valuesRange)
))
.Select(columnsToSelect))
.ToList();
return enitites;
}
public static string GenerateIsInRangeFilter(string propertyName,
IEnumerable<string> valuesRange)
{
string finalFilter = valuesRange.NotNull(nameof(valuesRange))
.Distinct()
.Aggregate((string)null, (filterSeed, value) =>
{
string equalsFilter = TableQuery.GenerateFilterCondition(
propertyName,
QueryComparisons.Equal,
value);
return filterSeed == null ?
equalsFilter :
TableQuery.CombineFilters(filterSeed,
TableOperators.Or,
equalsFilter);
});
return finalFilter ?? "";
}
我已经在 rowKeysList
中对少于 100 个值进行了测试,但是,如果有更多值甚至抛出异常,我们总是可以将请求拆分成多个部分。
在 Azure table 存储中,如何查询与分区中特定行键匹配的一组实体???
我正在使用 Azure table 存储,需要检索与分区中的一组行键匹配的一组实体。
基本上如果这是 SQL 它可能看起来像这样:
SELECT TOP 1 SomeKey
FROM TableName WHERE SomeKey IN (1, 2, 3, 4, 5);
我想节省成本并减少做一堆 table 检索操作,我可以使用 table 批处理操作来完成。出于某种原因,我收到一个异常:
"A batch transaction with a retrieve operation cannot contain any other operations"
这是我的代码:
public async Task<IList<GalleryPhoto>> GetDomainEntitiesAsync(int someId, IList<Guid> entityIds)
{
try
{
var client = _storageAccount.CreateCloudTableClient();
var table = client.GetTableReference("SomeTable");
var batchOperation = new TableBatchOperation();
var counter = 0;
var myDomainEntities = new List<MyDomainEntity>();
foreach (var id in entityIds)
{
if (counter < 100)
{
batchOperation.Add(TableOperation.Retrieve<MyDomainEntityTableEntity>(someId.ToString(CultureInfo.InvariantCulture), id.ToString()));
++counter;
}
else
{
var batchResults = await table.ExecuteBatchAsync(batchOperation);
var batchResultEntities = batchResults.Select(o => ((MyDomainEntityTableEntity)o.Result).ToMyDomainEntity()).ToList();
myDomainEntities .AddRange(batchResultEntities );
batchOperation.Clear();
counter = 0;
}
}
return myDomainEntities;
}
catch (Exception ex)
{
_logger.Error(ex);
throw;
}
}
如何在不手动遍历行键集并对每个行键执行单独的检索 table 操作的情况下实现目标?我不想承担与执行此操作相关的成本,因为我可能有数百个要过滤的行键。
由于有数百个行键,因此排除了将 $filter
与行键列表一起使用的可能性(无论如何都会导致部分分区扫描)。
根据您收到的错误,批处理似乎同时包含查询和其他类型的操作(这是不允许的)。从您的代码片段中,我不明白您为什么会收到该错误。
您唯一的其他选择是执行单个查询。不过,您可以异步执行这些操作,因此您不必等待每个 return。 Table 存储在给定分区上每秒提供超过 2,000 个事务,因此这是一个可行的解决方案。
不确定我一开始是怎么错过这个的,但这是 TableBatchOperation 类型的 MSDN 文档中的一个片段:
A batch operation may contain up to 100 individual table operations, with the requirement that each operation entity must have same partition key. A batch with a retrieve operation cannot contain any other operations. Note that the total payload of a batch operation is limited to 4MB.
我最终按照 David Makogon 的建议异步执行了各个检索操作。
我创造了自己的贫民窟link-table。我知道它效率不高(也许没问题),但我只在数据未在本地缓存时才发出此请求,这仅意味着切换设备。无论如何,这似乎有效。检查两个数组的长度让我推迟 context.done();
var query = new azure.TableQuery()
.top(1000)
.where('PartitionKey eq ?', 'link-' + req.query.email.toLowerCase() );
tableSvc.queryEntities('linkUserMarker',query, null, function(error, result, response) {
if( !error && result ){
var markers = [];
result.entries.forEach(function(e){
tableSvc.retrieveEntity('markerTable', e.markerPartition._, e.RowKey._.toString() , function(error, marker, response){
markers.push( marker );
if( markers.length == result.entries.length ){
context.res = {
status:200,
body:{
status:'error',
markers: markers
}
};
context.done();
}
});
});
} else {
notFound(error);
}
});
我制作了一个辅助方法来在每个分区的单个请求中完成它。
这样使用:
var items = table.RetrieveMany<MyDomainEntity>(partitionKey, nameof(TableEntity.RowKey),
rowKeysList, columnsToSelect);
这是帮助方法:
public static List<T> RetrieveMany<T>(this CloudTable table, string partitionKey,
string propertyName, IEnumerable<string> valuesRange,
List<string> columnsToSelect = null)
where T : TableEntity, new()
{
var enitites = table.ExecuteQuery(new TableQuery<T>()
.Where(TableQuery.CombineFilters(
TableQuery.GenerateFilterCondition(
nameof(TableEntity.PartitionKey),
QueryComparisons.Equal,
partitionKey),
TableOperators.And,
GenerateIsInRangeFilter(
propertyName,
valuesRange)
))
.Select(columnsToSelect))
.ToList();
return enitites;
}
public static string GenerateIsInRangeFilter(string propertyName,
IEnumerable<string> valuesRange)
{
string finalFilter = valuesRange.NotNull(nameof(valuesRange))
.Distinct()
.Aggregate((string)null, (filterSeed, value) =>
{
string equalsFilter = TableQuery.GenerateFilterCondition(
propertyName,
QueryComparisons.Equal,
value);
return filterSeed == null ?
equalsFilter :
TableQuery.CombineFilters(filterSeed,
TableOperators.Or,
equalsFilter);
});
return finalFilter ?? "";
}
我已经在 rowKeysList
中对少于 100 个值进行了测试,但是,如果有更多值甚至抛出异常,我们总是可以将请求拆分成多个部分。