从旧式宇宙宇宙表库迁移到现代。
#教程 #database #csharp #azure

我想告诉您有关Cosmos DB的信息以及如何改进您的代码。当您看到Microsoft.Azure.Cosmos.Table软件包已弃用时,您可能已经面对了案件。这意味着您需要迁移到Modern Azure.Data.Tables软件包。我会尽可能地告诉你。
为了澄清这一点,我创建了一个解决方案和两个简单​​的控制台项目。这两个项目都在实现Cosmos表数据库的CRUD功能的课堂上。在每个步骤中,我都会解释发生了什么或添加的内容。让我们走。

最初的

为方便起见,我在构造函数中添加了创建客户端,我们不需要在我们想要做某事时传递连接字符串。但是,存在一些差异。如您所见,现代实现最短。我们不再需要解析连接字符串。我们可以立即初始一个客户。

private readonly CloudTableClient _client;
public LegacyCosmosTable(string connectionString)
{
    var storageAccount = CloudStorageAccount.Parse(connectionString);
_client = storageAccount.CreateCloudTableClient();
}


private readonly TableServiceClient _client;
public ModernCosmosTable(string connectionString)
{
    _client = new TableServiceClient(connectionString);
}

在我们开始实现第一个CRUD函数之前,让我们为每个功能创建可重复的调用。

//legacy
private CloudTable GetCloudTable(string tableName)
{
    var table = _client.GetTableReference(tableName);
    return table;
}

//modern
private TableClient GetTableClient(string tableName)
{
    var tableClient = _client.GetTableClient(tableName);
    return tableClient;
}

创造

Cosmos DB能够检查表是否已经存在,然后返回表或创建新表。

//legacy
public async Task<CloudTable> CreateTableIfToExistsAsync(string tableName)
{
    var table = _client.GetTableReference(tableName);
    await     table.CreateIfNotExistsAsync().ConfigureAwait(false);
    return table;
}

//modern
public async Task<TableClient> CreateTableIfToExistsAsync(string tableName)
{
    var table = _client.GetTableClient(tableName);
    await table.CreateIfNotExistsAsync().ConfigureAwait(false);
    return table;
}

让我们实现所有实体。比较最短,最简单的现代实施。我们还使用了ITableEntity而不是TableEntity

//legacy
public async Task<IEnumerable<TEntity>> GetAllAsync<TEntity>(string tableName) where TEntity : TableEntity, new()
{
    var table = GetCloudTable(tableName);
    var tableQuery = new TableQuery<TEntity>();
    var results = new List<TEntity>();
    TableContinuationToken? token = null;
    do
    {
        var segment = await table.ExecuteQuerySegmentedAsync(tableQuery, token).ConfigureAwait(false);
        token = segment.ContinuationToken;
        results.AddRange(segment);
    } while (token != null);
    return results;
}

//modern
public async Task<IEnumerable<TEntity>> GetAllAsync<TEntity>(string tableName)
        where TEntity : class, ITableEntity, new()
{
    TableClient tableClient = GetTableClient(tableName);
    var results = new List<TEntity>();
    await foreach (var entity in tableClient.QueryAsync<TEntity>())
    {
        results.Add(entity);
    }
    return result;
}

一切都很好,但是如果我想过滤数据?让我们创建下一个功能。您如何看到这两种方法相似,并且具有两个过滤器,通过分区键和我们要选择的列。

//legacy
public async Task<IEnumerable<TEntity>> GetByPartitionKeyAsync<TEntity>(string tableName, string partitionKey,
            string[]? columns = null) where TEntity : TableEntity, new()
{
    var table = GetCloudTable(tableName);
    TableQuery<TEntity> tableQuery;
    if (columns != null)
    {
        tableQuery = new TableQuery<TEntity>()
                 .Where(TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, partitionKey))
                    .Select(columns);
    }
    else
    {
        tableQuery = new TableQuery<TEntity>()
                    .Where(TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, partitionKey));
    }
    var results = new List<TEntity>();
    TableContinuationToken token = null;
    do
    {
        var segment = await table.ExecuteQuerySegmentedAsync(tableQuery, token).ConfigureAwait(false);
        token = segment.ContinuationToken;
        results.AddRange(segment);
    } while (token != null);
    return results;
}

//modern
public async Task<IEnumerable<TEntity>> GetByPartitionKeyAsync<TEntity>(string tableName, string partitionKey,
        string[]? columns = null) where TEntity : class, ITableEntity, new()
{
    TableClient tableClient = GetTableClient(tableName);
    var results = new List<TEntity>();
    if (columns != null)
    {
        await foreach (var entity in tableClient.QueryAsync<TEntity>(filter => filter.PartitionKey == partitionKey,
                               select: columns))
        {
            results.Add(entity);
        }
    }
    else
    {
        await foreach (var entity in tableClient.QueryAsync<TEntity>(filter => filter.PartitionKey == partitionKey))
        {
            results.Add(entity);
        }
    }
    return results;
}

如果您只想获得一个实体,我们需要通过键行,然后查看:

//legacy
public async Task<TEntity> GetAsync<TEntity>(string tableName, string partitionKey, string rowKey)
            where TEntity : TableEntity
{
    var table = GetCloudTable(tableName);
    var get = TableOperation.Retrieve<TEntity>(partitionKey, rowKey);
    var result = await table.ExecuteAsync(get).ConfigureAwait(false);

    return (TEntity)result.Result;
}

//modern
public async Task<TEntity> GetAsync<TEntity>(string tableName, string partitionKey, string rowKey)
        where TEntity : class, ITableEntity
{
    TableClient tableClient = GetTableClient(tableName);
    var result = await tableClient.GetEntityAsync<TEntity>(partitionKey, rowKey).ConfigureAwait(false);
    return result;
}

更新

现在,让我们考虑更新表。与关系数据库不同,数据库之间存在一些差异。在这里,我们可以替换或合并数据。让我们通过合并来实现插入。值得注意的是,默认情况下,现代方法将数据合并最短,最简单。

//legacy
public async Task InsertOrMergeAsync(string tableName, TableEntity entity)
{
    var table = GetCloudTable(tableName);
    var insert = TableOperation.InsertOrMerge(entity);
    await table.ExecuteAsync(insert).ConfigureAwait(false);
}

//modern
public async Task InsertOrMergeAsync(string tableName, ITableEntity entity)
{
    TableClient tableClient = GetTableClient(tableName);
    await tableClient.UpsertEntityAsync(entity);
}

如果仅需要插入数据,则可以使用以下代码。但是,在现代方法中,您应该通过ETag属性。您可以阅读link

//legacy
public async Task InsertAsync(string tableName, TableEntity entity)
{
    var table = _client.GetTableReference(tableName);
    var insert = TableOperation.Insert(entity);
    await table.ExecuteAsync(insert).ConfigureAwait(false);
}

//modern
public async Task InsertAsync(string tableName, ITableEntity entity)
{
    TableClient tableClient = GetTableClient(tableName);
    await tableClient.UpdateEntityAsync(entity, entity.ETag);
}

如果您想插入数据并且存在此数据,那么该怎么办?它类似于以前的方法。在现代方法中,您只需要添加更换的标志。

//legacy
public async Task InsertOrReplaceAsync(string tableName, TableEntity entity)
{
    var table = GetCloudTable(tableName);
    var insert = TableOperation.InsertOrReplace(entity);
    await table.ExecuteAsync(insert).ConfigureAwait(false);
}

//modern
public async Task InsertOrReplaceAsync(string tableName, ITableEntity entity)
{
    TableClient tableClient = GetTableClient(tableName);
    await tableClient.UpsertEntityAsync(entity, TableUpdateMode.Replace);
}

删除

现在我们来删除了。让我们考虑批次删除。

//legacy
public async Task<IList<TableResult>> DeleteByPartitionKeyBatchAsync<TEntity>(string tableName,
            string partitionKey, string[]? columns = null) where TEntity : TableEntity, new()
{
    IList<TableResult> results = new List<TableResult>();
    IEnumerable<TEntity> entities =
                await GetByPartitionKeyAsync<TEntity>(tableName, partitionKey, columns)
                    .ConfigureAwait(false);
    if (entities.Any())
    {
        results = await DeleteBatchAsync(tableName, entities).ConfigureAwait(false);
    }
    return results;
}

//modern
public async Task<Response<IReadOnlyList<Response>>?> DeleteByPartitionKeyBatchAsync<TEntity>(string tableName,
        string partitionKey, string[]? columns = null) where TEntity : class, ITableEntity, new()
{
    Response<IReadOnlyList<Response>>? results;
    IEnumerable<TEntity> entities = await GetByPartitionKeyAsync<TEntity>(tableName, partitionKey, columns)
            .ConfigureAwait(false);
    if (!entities.Any()) return null;
    results = await DeleteBatchAsync(tableName, entities).ConfigureAwait(false);
    return results;
}

但是不是全部。我们需要实现删除。为此,您需要以下方法。如您所见,您需要通过可以为null的ETag

//legacy
private async Task<IList<TableResult>> DeleteBatchAsync<TEntity>(string tableName, IEnumerable<TEntity> entities)
            where TEntity : TableEntity, new()
{
    var table = GetCloudTable(tableName);
    var batchOperation = new TableBatchOperation();
    foreach (var e in entities)
    {
        e.ETag = e.ETag ?? "*";
        batchOperation.Delete(e);
    }

    return await      table.ExecuteBatchAsync(batchOperation).ConfigureAwait(false);
}

//modern
private async Task<Response<IReadOnlyList<Response>>?> DeleteBatchAsync<TEntity>(string tableName,
        IEnumerable<TEntity> entities) where TEntity : ITableEntity, new()
{
    TableClient tableClient = GetTableClient(tableName);
    var batch = new List<TableTransactionAction>();

    foreach (var entity in entities)
    {
        batch.Add(new TableTransactionAction(TableTransactionActionType.Delete, entity));
    }

    return await tableClient.SubmitTransactionAsync(batch).ConfigureAwait(false);
}

如果您只需要一个实体,那就更容易了。

//legacy
public async Task DeleteEntityAsync(string tableName, TableEntity entity)
{
    var table = GetCloudTable(tableName);
    entity.ETag ??= "*";
    var delete = TableOperation.Delete(entity);
    await table.ExecuteAsync(delete).ConfigureAwait(false);
}

//modern
public async Task DeleteEntityAsync(string tableName, ITableEntity entity)
{
    TableClient tableClient = GetTableClient(tableName);
    await tableClient.DeleteEntityAsync(entity.PartitionKey, entity.RowKey, entity.ETag).ConfigureAwait(false);
}

现在让我们清除桌子。 Cosmos DB没有一种特殊的方法来清除表中的数据,例如SQL中的截断。但是,我们有可以做到的疑问。两种方法都是相等的。

//legacy and modern
public async Task ClearTableAsync(string tableName)
{
    var entities = await GetAllAsync<TableEntity>(tableName).ConfigureAwait(false);
    if (entities.Any())
    {
        await DeleteBatchAsync(tableName, entities).ConfigureAwait(false);
    }
}

确保您可能想删除表:

//legacy
public async Task DeleteTableAsync(string tableName)
{
    var table = GetCloudTable(tableName);
    await table.DeleteAsync().ConfigureAwait(false);
}

//modern
public async Task DeleteTableAsync(string tableName)
{
    var table = GetTableClient(tableName);
    await table.DeleteAsync().ConfigureAwait(false);
}

结论

通过实现,新的API变得更加可读性和最小。就遗留图书馆的弃用而言,我强烈建议您利用本文迁移到最新的图书馆。
感谢您阅读,像订阅一样,并在下一篇文章中与您见面。愉快的编码!

您可以通过link的源代码。

Buy Me A Beer