共用方式為


將你的應用程式遷移到使用 Azure Cosmos DB .NET SDK v3

這很重要

想了解 Azure Cosmos DB .NET SDK v3,請參閱 發佈說明.NET GitHub 倉庫、.NET SDK v3 效能提示,以及 故障排除指南

本文將重點介紹將現有 .NET 應用程式升級到較新的 Azure Cosmos DB .NET SDK V3 FOR API FOR NoSQL 的一些考量。 Azure Cosmos DB .NET SDK v3 對應於 Microsoft.Azure.Cosmos DB 命名空間。 如果您正從以下 Azure Cosmos DB .NET SDK 遷移應用程式,可以使用本文檔提供的資訊:

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

本文中的說明也幫助你遷移以下外部函式庫,這些函式庫現在已成為 Azure Cosmos DB .NET SDK v3 for NoSQL API 的一部分:

  • .NET 變更饋源處理器函式庫 2.0
  • .NET bulk executor library 1.1 或更高版本

.NET V3 SDK 有什麼新內容

v3 SDK 包含許多可用性與效能改進,包括:

  • 直覺式程式設計模型命名
  • .NET Standard 2.0 **
  • 透過串流 API 支援提升效能
  • 流暢的層次結構取代了對 URI 生成器的需求
  • 內建對變更資訊流處理函式庫的支援
  • 內建對批量操作的支援
  • 可模擬 API 以簡化單元測試
  • 交易批次與 Blazor 支援
  • 可插拔序列化器
  • 調整未分區容器和自動調整容器的規模

** 該 SDK 針對 .NET Standard 2.0,將現有的 Azure Cosmos DB、.NET Framework 及 .NET Core SDK 統一為單一 .NET SDK。 你可以在任何實作 .NET Standard 2.0 的平台上使用 .NET SDK,包括你的 .NET Framework 4.6.1+ 和 .NET Core 2.0+ 應用程式。

大部分網路、重試邏輯以及 SDK 的低層級基本保持不變。

Azure Cosmos DB .NET SDK v3 現已開放原始碼。 我們歡迎任何拉取請求,並會在 GitHub 上記錄問題並追蹤回饋 我們會著手加入任何能提升客戶體驗的功能。

為什麼要遷移到 .NET v3 SDK

除了眾多的可用性與效能提升外,最新 SDK 中所投資的新功能也不會回溯到舊版本。 v2 SDK 目前處於維護階段。 為了獲得最佳的開發體驗,我們建議始終從最新支援的 SDK 版本開始。

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

以下名稱變更已在 .NET 3.0 SDK 中全面應用,以符合 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

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 文件類型(例如 JsonDocument System.Text.Json)。 建議使用反映你項目結構的 C# 類型,而非依賴通用類型。

  • Microsoft.Azure.Documents.Resource

Resource用於文件中時,沒有直接的替代品,但可依照Document的指導。

  • Microsoft.Azure.Documents.AccessCondition

IfNoneMatch 或者 IfMatch 現在已經可以直接在 Microsoft.Azure.Cosmos.ItemRequestOptions 上獲得。

項目ID生成的變更

.NET v3 SDK 中物品 ID 不再自動填充。 因此,項目 ID 必須明確包含已產生的 ID。 請參考以下範例:

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

更改連線模式的預設行為

SDK v3 現在預設為 Direct + TCP 連線模式 ,相較於先前的 v2 SDK 預設為 Gateway + HTTPS 連線模式。 此變更帶來提升的效能與可擴展性。

FeedOptions 的變更(v3.0 SDK 的 QueryRequestOptions)

FeedOptions SDK v2 中的類別名稱已在 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 提供了一個 CosmosClientBuilder 流暢類別,取代了 SDK v2 URI Factory 的需求。

流暢設計在內部建立 URL,允許使用單一的 Container 物件傳遞,而非 DocumentClientDatabaseName、和 DocumentCollection

以下範例建立一個具有強一致性水準及優先位置清單的新物件 CosmosClientBuilder

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

Exceptions

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();
}

Diagnostics

在 v2 SDK 中,透過 RequestDiagnosticsString 屬性僅能進行 Direct 模式的診斷,而 v3 SDK 則在所有回應與例外中提供 Diagnostics,這些功能不僅更豐富,且不限制於 Direct 模式。 這些資料不僅包含在該行動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
EnableEndpointDiscovery LimitToEndpoint - 值現在被反轉,若 EnableEndpointDiscovery 設定為 trueLimitToEndpoint 則應 設定為 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 內建對 Change Feed Processor API 的支援,讓你能使用相同的 SDK 來建置應用程式和 Change Feed 處理器實作。 以前你必須使用獨立的變更資訊流處理函式庫。

更多資訊請參閱 如何從「Change Feed Processor 函式庫」遷移到 Azure Cosmos DB .NET v3 SDK

變更訂閱查詢

在 v3 SDK 上執行變更導流查詢被視為使用 變更導流拉取模型。 請依照此表遷移設定:

.NET v2 SDK .NET v3 SDK
ChangeFeedOptions.PartitionKeyRangeId FeedRange- 為了實現平行讀取變更資訊流,您可以使用 FeedRanges。 這不再是必備參數,你現在可以輕鬆 讀取整個容器的變更資訊流
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 中的“bulk executor”函式庫

v3 SDK 內建了 bulk executor 函式庫的支援,讓你可以用同一個 SDK 來建置應用程式並執行 bulk 操作。 以前你必須使用獨立的批量執行器函式庫。

欲了解更多資訊,請參閱 如何從 bulk executor 函式庫遷移至 Azure Cosmos DB .NET V3 SDK 的 bulk 支援

自訂序列化

.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));

依 ID 閱讀資料庫

// 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)變更

SqlQuerySpec SDK v2 中的類別現在在 SDK v3 中已改名為QueryDefinition

SqlParameterCollectionSqlParameter已被移除。 現在會用建構模型將參數加入QueryDefinitionQueryDefinition.WithParameter。 使用者可以透過以下方式存取參數 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
        }
    }
}

後續步驟