遷移您的應用程式,以使用 Azure Cosmos DB .NET SDK v3

適用於:NoSQL

重要

若要了解 Azure Cosmos DB .NET SDK v3,請參閱版本資訊.NET GitHub 存放庫、.NET SDK v3 效能提示疑難排解指南

本文重點說明將現有的 .NET 應用程式升級至較新的適用於 API for NoSQL 的 Azure Cosmos DB .NET SDK v3 時的考量。 Azure Cosmos DB .NET SDK v3 對應至 Microsoft.Azure.Azure Cosmos DB 命名空間。 如果您要從下列任一 Azure Cosmos DB .NET SDK 移轉應用程式,您可以使用本文件中提供的資訊:

  • 適用於 API for NoSQL 的 Azure Cosmos DB .NET Framework SDK v2
  • 適用於 API for NoSQL 的 Azure Cosmos DB .NET Core SDK v2

本文中的指示也可協助您移轉下列外部程式庫,這些程式庫現在屬於適用於 API for NoSQL 的 Azure Cosmos DB .NET SDK v3:

  • .NET 變更摘要處理器程式庫 2.0
  • .NET 大量執行工具程式庫 1.1 或更高版本

.NET V3 SDK 的新功能

V3 SDK 包含許多可用性和效能改進,包括:

  • 直覺程式設計模型命名
  • .NET Standard 2.0 **
  • 透過串流 API 支援提高效能
  • 取代 URI Factory 需求的 Fluent 階層
  • 變更摘要處理器程式庫的內建支援
  • 大量作業的內建支援
  • 可模仿的 API,以簡化單元測試
  • 交易式批次和 Blazor 支援
  • 插入式序列化程式
  • 調整非資料分割和自動調整容器

** SDK 的目標 .NET Standard 2.0 可將現有的 Azure Cosmos DB .NET Framework 和 .NET Core SDK 統一成單一 .NET SDK。 您可以在任何實作 .NET Standard 2.0 的平台 (包括您的 .NET Framework 4.6.1+ 和 .NET Core 2.0+ 應用程式) 中使用 .NET SDK。

大部分的網路、重試邏輯和較低層級的 SDK 大多保持不變。

Azure Cosmos DB .net SDK v3 現在是開放原始碼。 我們歡迎任何提取要求,而且會在 GitHub 上記錄問題並追蹤意見反應。我們將努力採用可改善客戶體驗的任何功能。

為何要移轉至 .NET v3 SDK

除了許多可用性和效能改進之外,最新 SDK 中開發的新功能也不會移植回較舊的版本。 v2 SDK 目前處於維護模式。 為了獲得最佳的開發經驗,建議您一律從最新支援的 SDK 版本開始。

從 v2 SDK 到 v3 SDK 的主要名稱變更

下列名稱變更已套用至整個 .NET 3.0 SDK,與適用於 API for NoSQL 的 API 命名慣例保持一致:

  • DocumentClient 已重新命名為 CosmosClient
  • Collection 已重新命名為 Container
  • Document 已重新命名為 Item

所有資源物件都會以額外的屬性重新命名,以提供更清楚的資源名稱。

以下是一些主要的類別名稱變更:

.NET v2 SDK .NET v3 SDK
Microsoft.Azure.Documents.Client.DocumentClient Microsoft.Azure.Cosmos.CosmosClient
Microsoft.Azure.Documents.Client.ConnectionPolicy Microsoft.Azure.Cosmos.CosmosClientOptions
Microsoft.Azure.Documents.Client.DocumentClientException Microsoft.Azure.Cosmos.CosmosException
Microsoft.Azure.Documents.Client.Database Microsoft.Azure.Cosmos.DatabaseProperties
Microsoft.Azure.Documents.Client.DocumentCollection Microsoft.Azure.Cosmos.ContainerProperties
Microsoft.Azure.Documents.Client.RequestOptions Microsoft.Azure.Cosmos.ItemRequestOptions
Microsoft.Azure.Documents.Client.FeedOptions Microsoft.Azure.Cosmos.QueryRequestOptions
Microsoft.Azure.Documents.Client.StoredProcedure Microsoft.Azure.Cosmos.StoredProcedureProperties
Microsoft.Azure.Documents.Client.Trigger Microsoft.Azure.Cosmos.TriggerProperties
Microsoft.Azure.Documents.SqlQuerySpec Microsoft.Azure.Cosmos.QueryDefinition

.NET v3 SDK 中被取代的類別

在 3.0 SDK 中,下列類別已被取代:

  • Microsoft.Azure.Documents.UriFactory

Fluent Design 取代了 Microsoft.Azure.Documents.UriFactory 類別。

Container container = client.GetContainer(databaseName,containerName);
ItemResponse<SalesOrder> response = await this._container.CreateItemAsync(
        salesOrder,
        new PartitionKey(salesOrder.AccountNumber));

  • Microsoft.Azure.Documents.Document

因為 .NET v3 SDK 可讓使用者設定自訂序列化引擎,所以不會直接取代 Document 類型。 使用 Newtonsoft.Json (預設的序列化引擎) 時,JObject 可以用來達到相同的功能。 使用其他序列化引擎時,您可以使用其基底 json 文件類型 (例如 System.Text.Json 的 JsonDocument)。 建議使用可反映項目結構描述的 C# 類型,而不要仰賴泛型型別。

  • Microsoft.Azure.Documents.Resource

在用於文件時不會直接取代 Resource,請遵循 Document 的指導。

  • Microsoft.Azure.Documents.AccessCondition

IfNoneMatchIfMatch 現在可直接在 Microsoft.Azure.Cosmos.ItemRequestOptions 上取得。

項目識別碼產生的變更

.NET v3 SDK 不再自動填入項目識別碼。 因此,項目識別碼必須明確包含產生的識別碼。 請參閱下列範例:

[JsonProperty(PropertyName = "id")]
public Guid Id { get; set; }

連線模式的預設行為已變更

SDK v3 現在預設為直接 + TCP 連線模式,先前的 v2 SDK 則是預設為閘道 + HTTPS 連線模式。 這種變更提供了增強的效能和可擴展性。

FeedOptions 的變更 (在 v3.0 SDK 中為 QueryRequestOptions)

SDK v2 中的 FeedOptions 類別現在已在 SDK v3 中重新命名為 QueryRequestOptions ,而且其中有數個屬性的名稱和/或預設值有所變更,或已完全移除。

.NET v2 SDK .NET v3 SDK
FeedOptions.MaxDegreeOfParallelism QueryRequestOptions.MaxConcurrency - 預設值和相關聯的行為保持不變,在平行查詢執行期間執行用戶端的作業會以無平行處理順序的方式執行。
FeedOptions.PartitionKey QueryRequestOptions.PartitionKey - 維護的行為。
FeedOptions.EnableCrossPartitionQuery 移除。 SDK 3.0 中的預設行為是跨分割區查詢會執行,而不需要特別啟用屬性。
FeedOptions.PopulateQueryMetrics 移除。 現在預設為啟用,並且是診斷的一部分。
FeedOptions.RequestContinuation 移除。 現在已升階為查詢方法本身。
FeedOptions.JsonSerializerSettings 移除。 如需其他資訊,請參閱如何自訂序列化
FeedOptions.PartitionKeyRangeId 移除。 您可以使用 FeedRange 作為查詢方法的輸入,取得相同的結果。
FeedOptions.DisableRUPerMinuteUsage 移除。

建構用戶端

.NET SDK v3 提供 Fluent CosmosClientBuilder 類別,取代了 SDK v2 URI Factory 的需求。

Fluent Design 會在內部建立 URL,並允許傳遞單一 Container 物件,而不是 DocumentClientDatabaseNameDocumentCollection

下列範例會使用強式 ConsistencyLevel 和慣用位置清單來建立新的 CosmosClientBuilder

CosmosClientBuilder cosmosClientBuilder = new CosmosClientBuilder(
    accountEndpoint: "https://testcosmos.documents.azure.com:443/",
    authKeyOrResourceToken: "SuperSecretKey")
.WithConsistencyLevel(ConsistencyLevel.Strong)
.WithApplicationRegion(Regions.EastUS);
CosmosClient client = cosmosClientBuilder.Build();

例外狀況

如果 v2 SDK 於作業期間使用 DocumentClientException 發出錯誤訊號,v3 SDK 會使用 CosmosException 公開 StatusCodeDiagnostics 和其他回應相關資訊。 使用 ToString() 時,所有完整的資訊都會序列化:

catch (CosmosException ex)
{
    HttpStatusCode statusCode = ex.StatusCode;
    CosmosDiagnostics diagnostics = ex.Diagnostics;
    // store diagnostics optionally with diagnostics.ToString();
    // or log the entire error details with ex.ToString();
}

診斷

如果 v2 SDK 有僅直接診斷可透過 RequestDiagnosticsString 屬性使用,v3 SDK 會在所有回應和例外狀況中使用 Diagnostics,而這些回應和例外狀況更豐富且不限於直接模式。 其中不僅包含用於作業的 SDK 所花費的時間,也包含作業所連接的區域:

try
{
    ItemResponse<MyItem> response = await container.ReadItemAsync<MyItem>(
                    partitionKey: new PartitionKey("MyPartitionKey"),
                    id: "MyId");
    
    TimeSpan elapsedTime = response.Diagnostics.GetElapsedTime();
    if (elapsedTime > somePreDefinedThreshold)
    {
        // log response.Diagnostics.ToString();
        IReadOnlyList<(string region, Uri uri)> regions = response.Diagnostics.GetContactedRegions();
    }
}
catch (CosmosException cosmosException) {
    string diagnostics = cosmosException.Diagnostics.ToString();
    
    TimeSpan elapsedTime = cosmosException.Diagnostics.GetElapsedTime();
    
    IReadOnlyList<(string region, Uri uri)> regions = cosmosException.Diagnostics.GetContactedRegions();
    
    // log cosmosException.ToString()
}

ConnectionPolicy

ConnectionPolicy 中的某些設定已重新命名或由 CosmosClientOptions 取代:

.NET v2 SDK .NET v3 SDK
EnableEndpointRediscovery LimitToEndpoint - 此值現在會反轉,如果 EnableEndpointRediscovery 設定為 true,則 LimitToEndpoint 應設定為 false。 使用此設定之前,您必須了解它對用戶端的影響
ConnectionProtocol 移除。 通訊協定會系結至模式,也就是閘道 (HTTPS) 或直接 (TCP)。 V3 SDK 不再支援具有 HTTPS 通訊協定的直接模式,建議使用 TCP 通訊協定。
MediaRequestTimeout 移除。 不再支援附件。
SetCurrentLocation CosmosClientOptions.ApplicationRegion 可以用來達成相同效果。
PreferredLocations CosmosClientOptions.ApplicationPreferredRegions 可以用來達成相同效果。
UserAgentSuffix CosmosClientBuilder.ApplicationName 可以用來達成相同效果。
UseMultipleWriteLocations 移除。 SDK 會自動偵測帳戶是否支援多個寫入端點。

編製索引原則

在編制索引原則中,無法設定這些屬性。 若未指定,現在這些屬性將使用下列預設值:

屬性名稱 新值 (無法設定)
Kind range
dataType StringNumber

請參閱這一節,查看包含和排除路徑的編製索引原則範例。 由於查詢引擎的改進,即使使用較舊的 SDK 版本,設定這些屬性也不會影響效能。

工作階段權杖

在需要擷取工作階段權杖的情況下,如果 v2 SDK 將回應的工作階段權杖公開為 ResourceResponse.SessionToken,因為工作階段權杖是標頭,v3 SDK 會在任何回應的 Headers.Session 屬性中公開該值。

時間戳記

如果 v2 SDK 透過 Timestamp 屬性公開文件的時間戳記,因為無法再使用 Document,使用者可以將 _ts系統屬性對應到模型中的屬性。

OpenAsync

針對使用 OpenAsync() 來準備 v2 SDK 用戶端的使用案例,您可以使用 CreateAndInitializeAsync建立和準備 v3 SDK 用戶端。

直接從 v3 SDK 使用變更摘要處理器 API

v3 SDK 提供變更摘要處理器 API 內建支援,可讓您使用相同的 SDK 來建立應用程式及變更摘要處理器實作。 先前,您必須使用另外的變更摘要處理器程式庫。

如需詳細資訊,請參閱如何從變更摘要處理器程式庫移轉至 Azure Cosmos DB .NET v3 SDK

變更摘要查詢

在 v3 SDK 上執行變更摘要查詢,會視為使用變更摘要提取模型。 請按照下表移轉設定:

.NET v2 SDK .NET v3 SDK
ChangeFeedOptions.PartitionKeyRangeId FeedRange - 若要平行處理原則讀取,可使用變更摘要 FeedRange。 這不再是必要參數,您現在可以輕鬆讀取整個容器的變更摘要
ChangeFeedOptions.PartitionKey FeedRange.FromPartitionKey - 代表所需分割區索引鍵的 FeedRange,可用來讀取該分割區索引鍵值的變更摘要
ChangeFeedOptions.RequestContinuation ChangeFeedStartFrom.Continuation - 可儲存接續,並在建立新列舉程式時使用,藉此隨時停止和繼續變更摘要列舉程式。
ChangeFeedOptions.StartTime ChangeFeedStartFrom.Time
ChangeFeedOptions.StartFromBeginning ChangeFeedStartFrom.Beginning
ChangeFeedOptions.MaxItemCount ChangeFeedRequestOptions.PageSizeHint - 可儲存接續,並在建立新列舉程式時使用,藉此隨時停止和繼續變更摘要列舉程式。
IDocumentQuery.HasMoreResults response.StatusCode == HttpStatusCode.NotModified - 變更摘要在概念上為無限,因此可能總是有更多結果。 回應包含 HttpStatusCode.NotModified 狀態碼時,表示目前沒有可讀取的新變更。 您可以用此停止和儲存接續,或暫時睡眠或等候,然後再次呼叫 ReadNextAsync 以測試是否有新變更。
分割處理 使用者不再需要在讀取變更摘要時,處理分割例外狀況,分割將以透明方式處理,而不需使用者互動。

直接從 V3 SDK 使用大量執行工具程式庫

v3 SDK 提供大量執行工具程式庫內建支援,可讓您使用相同的 SDK 來建立應用程式及執行大量作業。 先前,您必須使用另外的大量執行工具程式庫。

如需詳細資訊,請參閱如何從大量執行工具程式庫移轉至 Azure Cosmos DB .NET V3 SDK 的大量支援

自訂序列化

.NET V2 SDK 允許在用來還原串行化結果文件的作業層級於 RequestOptions 中設定 JsonSerializerSettings

// .NET V2 SDK
var result = await container.ReplaceDocumentAsync(document, new RequestOptions { JsonSerializerSettings = customSerializerSettings })

.NET SDK v3 提供序列化程式介面來完全自訂序列化引擎,或提供更多一般序列化選項作為用戶端建構的一部分。

自訂作業層級的序列化可以透過使用 Stream API 來達成:

// .NET V3 SDK
using(Response response = await this.container.ReplaceItemStreamAsync(stream, "itemId", new PartitionKey("itemPartitionKey"))
{

    using(Stream stream = response.ContentStream)
    {
        using (StreamReader streamReader = new StreamReader(stream))
        {
            // Read the stream and do dynamic deserialization based on type with a custom Serializer
        }
    }
}

程式碼片段比較

下列程式碼片段顯示如何在 .NET v2 和 v3 SDK 之間建立資源的差異:

資料庫作業

建立資料庫

// Create database with no shared provisioned throughput
DatabaseResponse databaseResponse = await client.CreateDatabaseIfNotExistsAsync(DatabaseName);
Database database = databaseResponse;
DatabaseProperties databaseProperties = databaseResponse;

// Create a database with a shared manual provisioned throughput
string databaseIdManual = new string(DatabaseName + "_SharedManualThroughput");
database = await client.CreateDatabaseIfNotExistsAsync(databaseIdManual, ThroughputProperties.CreateManualThroughput(400));

// Create a database with shared autoscale provisioned throughput
string databaseIdAutoscale = new string(DatabaseName + "_SharedAutoscaleThroughput");
database = await client.CreateDatabaseIfNotExistsAsync(databaseIdAutoscale, ThroughputProperties.CreateAutoscaleThroughput(4000));

按識別碼讀取資料庫

// Read a database
Console.WriteLine($"{Environment.NewLine} Read database resource: {DatabaseName}");
database = client.GetDatabase(DatabaseName);
Console.WriteLine($"{Environment.NewLine} database { database.Id.ToString()}");

// Read all databases
string findQueryText = "SELECT * FROM c";
using (FeedIterator<DatabaseProperties> feedIterator = client.GetDatabaseQueryIterator<DatabaseProperties>(findQueryText))
{
    while (feedIterator.HasMoreResults)
    {
        FeedResponse<DatabaseProperties> databaseResponses = await feedIterator.ReadNextAsync();
        foreach (DatabaseProperties _database in databaseResponses)
        {
            Console.WriteLine($"{ Environment.NewLine} database {_database.Id.ToString()}");
        }
    }
}

刪除資料庫

// Delete a database
await client.GetDatabase(DatabaseName).DeleteAsync();
Console.WriteLine($"{ Environment.NewLine} database {DatabaseName} deleted.");

// Delete all databases in an account
string deleteQueryText = "SELECT * FROM c";
using (FeedIterator<DatabaseProperties> feedIterator = client.GetDatabaseQueryIterator<DatabaseProperties>(deleteQueryText))
{
    while (feedIterator.HasMoreResults)
    {
        FeedResponse<DatabaseProperties> databaseResponses = await feedIterator.ReadNextAsync();
        foreach (DatabaseProperties _database in databaseResponses)
        {
            await client.GetDatabase(_database.Id).DeleteAsync();
            Console.WriteLine($"{ Environment.NewLine} database {_database.Id} deleted");
        }
    }
}

容器作業

建立容器 (自動調整 + 有期限的存留時間)

private static async Task CreateManualThroughputContainer(Database database)
{
    // Set throughput to the minimum value of 400 RU/s manually configured throughput
    string containerIdManual = ContainerName + "_Manual";
    ContainerResponse container = await database.CreateContainerIfNotExistsAsync(
        id: containerIdManual,
        partitionKeyPath: partitionKeyPath,
        throughput: 400);
}

// Create container with autoscale
private static async Task CreateAutoscaleThroughputContainer(Database database)
{
    string autoscaleContainerId = ContainerName + "_Autoscale";
    ContainerProperties containerProperties = new ContainerProperties(autoscaleContainerId, partitionKeyPath);

    Container container = await database.CreateContainerIfNotExistsAsync(
        containerProperties: containerProperties,
        throughputProperties: ThroughputProperties.CreateAutoscaleThroughput(autoscaleMaxThroughput: 4000);
}

// Create a container with TTL Expiration
private static async Task CreateContainerWithTtlExpiration(Database database)
{
    string containerIdManualwithTTL = ContainerName + "_ManualTTL";

    ContainerProperties properties = new ContainerProperties
        (id: containerIdManualwithTTL,
        partitionKeyPath: partitionKeyPath);

    properties.DefaultTimeToLive = (int)TimeSpan.FromDays(1).TotalSeconds; //expire in 1 day

    ContainerResponse containerResponse = await database.CreateContainerIfNotExistsAsync(containerProperties: properties);
    ContainerProperties returnedProperties = containerResponse;
}

讀取容器屬性

private static async Task ReadContainerProperties(Database database)
{
    string containerIdManual = ContainerName + "_Manual";
    Container container = database.GetContainer(containerIdManual);
    ContainerProperties containerProperties = await container.ReadContainerAsync();
}

刪除容器

private static async Task DeleteContainers(Database database)
{
    string containerIdManual = ContainerName + "_Manual";

    // Delete a container
    await database.GetContainer(containerIdManual).DeleteContainerAsync();

    // Delete all CosmosContainer resources for a database
    using (FeedIterator<ContainerProperties> feedIterator = database.GetContainerQueryIterator<ContainerProperties>())
    {
        while (feedIterator.HasMoreResults)
        {
            foreach (ContainerProperties _container in await feedIterator.ReadNextAsync())
            {
                await database.GetContainer(_container.Id).DeleteContainerAsync();
                Console.WriteLine($"{Environment.NewLine}  deleted container {_container.Id}");
            }
        }
    }
}

項目和查詢作業

建立項目

private static async Task CreateItemAsync(Container container)
{
    // Create a SalesOrder POCO object
    SalesOrder salesOrder1 = GetSalesOrderSample("Account1", "SalesOrder1");
    ItemResponse<SalesOrder> response = await container.CreateItemAsync(salesOrder1,
        new PartitionKey(salesOrder1.AccountNumber));
}

private static async Task RunBasicOperationsOnDynamicObjects(Container container)
{
    // Dynamic Object
    dynamic salesOrder = new
    {
        id = "SalesOrder5",
        AccountNumber = "Account1",
        PurchaseOrderNumber = "PO18009186470",
        OrderDate = DateTime.UtcNow,
        Total = 5.95,
    };
    Console.WriteLine("\nCreating item");
    ItemResponse<dynamic> response = await container.CreateItemAsync<dynamic>(
        salesOrder, new PartitionKey(salesOrder.AccountNumber));
    dynamic createdSalesOrder = response.Resource;
}

讀取容器中的所有項目

private static async Task ReadAllItems(Container container)
{
    // Read all items in a container
    List<SalesOrder> allSalesForAccount1 = new List<SalesOrder>();

    using (FeedIterator<SalesOrder> resultSet = container.GetItemQueryIterator<SalesOrder>(
        queryDefinition: null,
        requestOptions: new QueryRequestOptions()
        {
            PartitionKey = new PartitionKey("Account1"),
            MaxItemCount = 5
        }))
    {
        while (resultSet.HasMoreResults)
        {
            FeedResponse<SalesOrder> response = await resultSet.ReadNextAsync();
            SalesOrder salesOrder = response.First();
            Console.WriteLine($"\n1.3.1 Account Number: {salesOrder.AccountNumber}; Id: {salesOrder.Id}");
            allSalesForAccount1.AddRange(response);
        }
    }
}

查詢項目

SqlQuerySpec (v3.0 SDK 中的 QueryDefinition) 的變更

SDK v2 中的 SqlQuerySpec 類別現已在 SDK v3 中重新命名為 QueryDefinition

已移除 SqlParameterCollectionSqlParameter。 參數現在會以使用 QueryDefinition.WithParameter 的建立器模型,新增至 QueryDefinition。 使用者可以使用 QueryDefinition.GetQueryParameters 存取參數

private static async Task QueryItems(Container container)
{
    // Query for items by a property other than Id
    QueryDefinition queryDefinition = new QueryDefinition(
        "select * from sales s where s.AccountNumber = @AccountInput")
        .WithParameter("@AccountInput", "Account1");

    List<SalesOrder> allSalesForAccount1 = new List<SalesOrder>();
    using (FeedIterator<SalesOrder> resultSet = container.GetItemQueryIterator<SalesOrder>(
        queryDefinition,
        requestOptions: new QueryRequestOptions()
        {
            PartitionKey = new PartitionKey("Account1"),
            MaxItemCount = 1
        }))
    {
        while (resultSet.HasMoreResults)
        {
            FeedResponse<SalesOrder> response = await resultSet.ReadNextAsync();
            SalesOrder sale = response.First();
            Console.WriteLine($"\n Account Number: {sale.AccountNumber}; Id: {sale.Id};");
            allSalesForAccount1.AddRange(response);
        }
    }
}

刪除項目

private static async Task DeleteItemAsync(Container container)
{
    ItemResponse<SalesOrder> response = await container.DeleteItemAsync<SalesOrder>(
        partitionKey: new PartitionKey("Account1"), id: "SalesOrder3");
}

變更摘要查詢

private static async Task QueryChangeFeedAsync(Container container)
{
    FeedIterator<SalesOrder> iterator = container.GetChangeFeedIterator<SalesOrder>(ChangeFeedStartFrom.Beginning(), ChangeFeedMode.Incremental);

    string continuation = null;
    while (iterator.HasMoreResults)
    {
        FeedResponse<SalesOrder> response = await iteratorForTheEntireContainer.ReadNextAsync();
    
        if (response.StatusCode == HttpStatusCode.NotModified)
        {
            // No new changes
            continuation = response.ContinuationToken;
            break;
        }
        else 
        {
            // Process the documents in response
        }
    }
}

下一步