.NET 中持久性實體的開發人員指南

在本文中,我們會詳細描述使用 .NET 開發持久性實體的可用介面,包括範例和一般建議。

實體函式為無伺服器應用程式開發人員提供一種便利方式,將應用程式狀態組織為精細實體的集合。 如需基礎概念的詳細資料,請參閱持久性實體:概念一文。

我們目前提供兩個用於定義實體的 API:

  • 類別型語法可將實體和作業表示為類別和方法。 此語法會產生易於讀取的程式碼,並允許透過介面以檢查類型的方式叫用作業。

  • 函式型語法是較低層級的介面,可將實體表示為函式。 其可讓您精確控制實體作業的分派方式,以及實體狀態的管理方式。

本文主要著重於類別型語法,因為我們預期其更加適合大部分的應用程式。 不過,函式型語法可以適用於想要針對實體狀態和作業本身定義或管理其自己抽象的應用程式。 此外,其可能適用於實作需要泛型的程式庫,而類別型語法目前不支援此泛型。

注意

類別型語法只是函式型語法之上的一層,因此這兩個變體可以在相同的應用程式中交換使用。

定義實體類別

下列範例是 Counter 實體的實作,該實體會儲存整數類型的單一值,並提供四個作業 AddResetGetDelete

[JsonObject(MemberSerialization.OptIn)]
public class Counter
{
    [JsonProperty("value")]
    public int Value { get; set; }

    public void Add(int amount) 
    {
        this.Value += amount;
    }

    public Task Reset() 
    {
        this.Value = 0;
        return Task.CompletedTask;
    }

    public Task<int> Get() 
    {
        return Task.FromResult(this.Value);
    }

    public void Delete() 
    {
        Entity.Current.DeleteState();
    }

    [FunctionName(nameof(Counter))]
    public static Task Run([EntityTrigger] IDurableEntityContext ctx)
        => ctx.DispatchAsync<Counter>();
}

Run 函式包含使用類別型語法所需的樣板。 其必須是「靜態」Azure 函式。 其會針對實體所處理的每個作業訊息執行一次。 呼叫 DispatchAsync<T> 且實體尚未在記憶體中時,其會建構類型 T 的物件,並從儲存體中找到的最後一個保存 JSON 中填入其欄位 (若有的話)。 然後,其會使用相符的名稱叫用方法。

此範例中的 EntityTrigger 函式 Run 不需要位於實體類別本身之中。 它可以位於 Azure 函式的任何有效位置:在最上層命名空間內,或最上層類別內。 不過,如果巢狀結構更深 (例如,函式是在巢狀類別內宣告),則最新的執行階段無法辨識此函式。

注意

類別型實體的狀態會在實體處理作業之前隱含建立,而且可以藉由呼叫 Entity.Current.DeleteState(),在作業中將其明確刪除

注意

您需要 Azure Functions Core Tools4.0.5455 版或更新版本,才能在隔離模型中執行實體。

有兩種方式可將實體定義為 C# 隔離式背景工作角色模型中的類別。 它們會產生具有不同狀態序列化結構的實體。

使用下列方法,整個物件會在定義實體時序列化。

public class Counter
{
    public int Value { get; set; }

    public void Add(int amount) 
    {
        this.Value += amount;
    }

    public Task Reset() 
    {
        this.Value = 0;
        return Task.CompletedTask;
    }

    public Task<int> Get() 
    {
        return Task.FromResult(this.Value);
    }

    // Delete is implicitly defined when defining an entity this way

    [Function(nameof(Counter))]
    public static Task Run([EntityTrigger] TaskEntityDispatcher dispatcher)
        => dispatcher.DispatchAsync<Counter>();
}

TaskEntity<TState> 型實作,可讓您輕鬆使用相依性插入。 在此情況下,狀態會還原序列化為 State 屬性,且不會有任何其他屬性序列化/還原序列化。

public class Counter : TaskEntity<int>
{
    readonly ILogger logger; 

    public Counter(ILogger<Counter> logger)
    {
        this.logger = logger; 
    }

    public int Add(int amount) 
    {
        this.State += amount;
    }

    public Reset() 
    {
        this.State = 0;
        return Task.CompletedTask;
    }

    public Task<int> Get() 
    {
        return Task.FromResult(this.State);
    }

    // Delete is implicitly defined when defining an entity this way

    [Function(nameof(Counter))]
    public static Task Run([EntityTrigger] TaskEntityDispatcher dispatcher)
        => dispatcher.DispatchAsync<Counter>();
}

警告

撰寫衍生自 ITaskEntityTaskEntity<TState> 的實體時,請務必不要將實體觸發程序命名為 RunAsync 方法。 這會在叫用實體時造成執行階段錯誤,因為 ITaskEntity 已經定義執行個體層級 "RunAsync",所以會與方法名稱 "RunAsync" 發生模稜兩可的相符。

刪除隔離模型中的實體

刪除隔離模型中的實體,是藉由將實體狀態設定為 null 來完成。 完成此作業的方式取決於所使用的實體實作路徑。

  • ITaskEntity 衍生或使用函式型語法時,可藉由呼叫 TaskEntityOperation.State.SetState(null) 來完成刪除。
  • TaskEntity<TState> 衍生時,則已隱含定義刪除。 不過,您可以在實體上定義方法 Delete 來覆寫它。 您也可以透過 this.State = null 從任何作業中刪除狀態。
    • 若要藉由將狀態設定為 null 來刪除,TState 必須是可為 Null。
    • 隱含定義的刪除作業會刪除不可為 Null 的 TState
  • 使用 POCO 作為您的狀態時 (不是從 TaskEntity<TState> 衍生),已隱含定義刪除。 您可以在 POCO 上定義方法 Delete,以覆寫刪除作業。 不過,無法在 POCO 路由中將狀態設定為 null,因此隱含定義的刪除作業是唯一真正的刪除。

類別需求

實體類別是不需要特殊超類別、介面或屬性的 POCO (純舊式 CLR 物件)。 但是:

此外,任何要以作業形式叫用的方法都必須滿足其他需求:

  • 作業最多只有一個引數,而且不得有任何多載或泛型型別引數。
  • 要使用介面從協調流程呼叫的作業必須傳回 TaskTask<T>
  • 引數和傳回值必須是可序列化的值或物件。

作業可以做什麼?

所有實體作業都可以讀取和更新實體狀態,而且狀態的變更會自動保存到儲存體。 此外,作業可以在所有 Azure Functions 通用的一般限制內執行外部 I/O 或其他計算。

作業也可以存取 Entity.Current 內容所提供的功能:

  • EntityName:目前執行中實體的名稱。
  • EntityKey:目前執行中實體的金鑰。
  • EntityId:目前執行中實體的識別碼 (包括名稱和金鑰)。
  • SignalEntity:將單向訊息傳送至實體。
  • CreateNewOrchestration:啟動新的協調流程。
  • DeleteState:刪除此實體的狀態。

例如,我們可以修改計數器實體,讓其在計數器達到 100 時啟動協調流程,並將實體識別碼當作輸入引數傳遞:

public void Add(int amount) 
{
    if (this.Value < 100 && this.Value + amount >= 100)
    {
        Entity.Current.StartNewOrchestration("MilestoneReached", Entity.Current.EntityId);
    }
    this.Value += amount;      
}

直接存取實體

您可以直接存取類別型實體,方法是將明確字串名稱用於實體及其作業的。 本節提供範例。 如需基礎概念 (例如訊號與呼叫) 的更深入說明,請參閱存取實體中的討論。

注意

可能的話,您應透過介面存取實體,因為可提供更多類型檢查。

範例:用戶端訊號實體

下列 Azure Http 函式會使用 REST 慣例來實作 DELETE 作業。 其會將刪除訊號傳送至其金鑰在 URL 路徑中傳遞的計數器實體。

[FunctionName("DeleteCounter")]
public static async Task<HttpResponseMessage> DeleteCounter(
    [HttpTrigger(AuthorizationLevel.Function, "delete", Route = "Counter/{entityKey}")] HttpRequestMessage req,
    [DurableClient] IDurableEntityClient client,
    string entityKey)
{
    var entityId = new EntityId("Counter", entityKey);
    await client.SignalEntityAsync(entityId, "Delete");    
    return req.CreateResponse(HttpStatusCode.Accepted);
}

範例:用戶端讀取實體狀態

下列 Azure Http 函式會使用 REST 慣例來實作 GET 作業。 此函式會讀取其金鑰在 URL 路徑中傳遞的計數器實體目前狀態。

[FunctionName("GetCounter")]
public static async Task<HttpResponseMessage> GetCounter(
    [HttpTrigger(AuthorizationLevel.Function, "get", Route = "Counter/{entityKey}")] HttpRequestMessage req,
    [DurableClient] IDurableEntityClient client,
    string entityKey)
{
    var entityId = new EntityId("Counter", entityKey);
    var state = await client.ReadEntityStateAsync<Counter>(entityId); 
    return req.CreateResponse(state);
}

注意

ReadEntityStateAsync 傳回的物件只是本機複本,也就是來自某些先前時間點的實體狀態快照集。 特別要注意的是,它可能過時,而且修改此物件不會影響實際實體。

範例:協調流程會先發出訊號,然後呼叫實體

下列協調流程會對計數器實體發出訊號來將其遞增,然後呼叫相同的實體來讀取其最新值。

[FunctionName("IncrementThenGet")]
public static async Task<int> Run(
    [OrchestrationTrigger] IDurableOrchestrationContext context)
{
    var entityId = new EntityId("Counter", "myCounter");

    // One-way signal to the entity - does not await a response
    context.SignalEntity(entityId, "Add", 1);

    // Two-way call to the entity which returns a value - awaits the response
    int currentValue = await context.CallEntityAsync<int>(entityId, "Get");

    return currentValue;
}

範例:用戶端訊號實體

下列 Azure Http 函式會使用 REST 慣例來實作 DELETE 作業。 其會將刪除訊號傳送至其金鑰在 URL 路徑中傳遞的計數器實體。

[Function("DeleteCounter")]
public static async Task<HttpResponseData> DeleteCounter(
    [HttpTrigger(AuthorizationLevel.Function, "delete", Route = "Counter/{entityKey}")] HttpRequestData req,
    [DurableClient] DurableTaskClient client, string entityKey)
{
    var entityId = new EntityInstanceId("Counter", entityKey);
    await client.Entities.SignalEntityAsync(entityId, "Delete");
    return req.CreateResponse(HttpStatusCode.Accepted);
}

範例:用戶端讀取實體狀態

下列 Azure Http 函式會使用 REST 慣例來實作 GET 作業。 此函式會讀取其金鑰在 URL 路徑中傳遞的計數器實體目前狀態。

[Function("GetCounter")]
public static async Task<HttpResponseData> GetCounter(
    [HttpTrigger(AuthorizationLevel.Function, "get", Route = "Counter/{entityKey}")] HttpRequestData req,
    [DurableClient] DurableTaskClient client, string entityKey)
{
    var entityId = new EntityInstanceId("Counter", entityKey);
    EntityMetadata<int>? entity = await client.Entities.GetEntityAsync<int>(entityId);
    HttpResponseData response = request.CreateResponse(HttpStatusCode.OK);
    await response.WriteAsJsonAsync(entity.State);

    return response;
}

範例:協調流程會先發出訊號,然後呼叫實體

下列協調流程會對計數器實體發出訊號來將其遞增,然後呼叫相同的實體來讀取其最新值。

[Function("IncrementThenGet")]
public static async Task<int> Run([OrchestrationTrigger] TaskOrchestrationContext context)
{
    var entityId = new EntityInstanceId("Counter", "myCounter");

    // One-way signal to the entity - does not await a response
    await context.Entities.SignalEntityAsync(entityId, "Add", 1);

    // Two-way call to the entity which returns a value - awaits the response
    int currentValue = await context.Entities.CallEntityAsync<int>(entityId, "Get");

    return currentValue; 
}

透過介面存取實體

介面可以用來透過產生的 Proxy 物件存取實體。 此方法可確保作業的名稱和引數類型符合實作的項目。 建議您儘可能使用介面存取實體。

例如,我們可以修改計數器範例,如下所示:

public interface ICounter
{
    void Add(int amount);
    Task Reset();
    Task<int> Get();
    void Delete();
}

public class Counter : ICounter
{
    ...
}

實體類別和實體介面類似於 Orleans 所推廣的粒紋和粒紋介面。 如需持久性實體與 Orlean 之間相似性和差異的詳細資訊,請參閱與虛擬執行者的比較

除了提供類型檢查之外,介面也有助於更清楚地區隔應用程式內的考量。 例如,由於一個實體可以實作多個介面,因此單一實體可以提供多個角色。 此外,由於一個介面可由多個實體實作,因此一般通訊模式可以作為可重複使用的程式庫來實作。

範例:用戶端透過介面對實體發出訊號

用戶端程式碼可以使用 SignalEntityAsync<TEntityInterface> 將訊號傳送至實作 TEntityInterface 的實體。 例如:

[FunctionName("DeleteCounter")]
public static async Task<HttpResponseMessage> DeleteCounter(
    [HttpTrigger(AuthorizationLevel.Function, "delete", Route = "Counter/{entityKey}")] HttpRequestMessage req,
    [DurableClient] IDurableEntityClient client,
    string entityKey)
{
    var entityId = new EntityId("Counter", entityKey);
    await client.SignalEntityAsync<ICounter>(entityId, proxy => proxy.Delete());    
    return req.CreateResponse(HttpStatusCode.Accepted);
}

在此範例中,proxy 參數是動態產生的 ICounter 執行個體,其會在內部將 Delete 的呼叫轉譯為訊號。

注意

SignalEntityAsync API 只能用於單向作業。 即使作業傳回 Task<T>T 參數的值一律將會是 Null 或 default,而不是實際的結果。 例如,對 Get 作業發出訊號沒有意義,因為不會傳回任何值。 相反地,用戶端可以使用 ReadStateAsync 直接存取計數器狀態,也可以啟動一個呼叫 Get 作業的協調器函式。

範例:協調流程會先發出訊號,然後透過 Proxy 呼叫實體

若要從協調流程內呼叫實體或發出訊號,CreateEntityProxy 可與介面類型搭配使用來產生實體的 Proxy。 然後,此 Proxy 可以用來呼叫作業或發出訊號:

[FunctionName("IncrementThenGet")]
public static async Task<int> Run(
    [OrchestrationTrigger] IDurableOrchestrationContext context)
{
    var entityId = new EntityId("Counter", "myCounter");
    var proxy = context.CreateEntityProxy<ICounter>(entityId);

    // One-way signal to the entity - does not await a response
    proxy.Add(1);

    // Two-way call to the entity which returns a value - awaits the response
    int currentValue = await proxy.Get();

    return currentValue;
}

隱含地對任何傳回 void 的作業發出訊號,也會隱含地呼叫任何傳回 TaskTask<T> 的作業。 即使其明確使用 SignalEntity<IInterfaceType> 方法傳回工作,也可以變更此預設行為和訊號作業。

較短選項用於指定目標

使用介面呼叫實體或發出訊號時,第一個引數必須指定目標實體。 您可以指定實體識別碼來指定目標,或者,如果只有一個實作實體的類別,只需指定實體金鑰:

context.SignalEntity<ICounter>(new EntityId(nameof(Counter), "myCounter"), ...);
context.SignalEntity<ICounter>("myCounter", ...);

如果僅指定實體金鑰,而且在執行階段找不到唯一的實作,則會擲回 InvalidOperationException

實體介面的限制

一如往常,所有參數和傳回型別都必須是可 JSON 序列化。 否則,會在執行階段擲回序列化例外狀況。

我們也會再強制執行一些規則:

  • 實體介面必須與實體類別定義在相同的組件中。
  • 實體介面必須只定義方法。
  • 實體介面不得包含泛型參數。
  • 實體介面方法不得具有多個參數。
  • 實體介面方法必須傳回 voidTaskTask<T>

如果違反上述任何規則,則在介面用作 SignalEntitySignalEntityAsyncCreateEntityProxy 的類型引數時,會在執行階段擲回 InvalidOperationException。 例外狀況訊息說明違反了哪個規則。

注意

傳回 void 的介面方法只能發出訊號 (單向),而不能進行呼叫 (雙向)。 傳回 TaskTask<T> 的介面方法可以進行呼叫或發出訊號。 如果進行呼叫,其會傳回作業的結果,或重新擲回作業擲回的例外狀況。 不過,當發出訊號時,其不會從作業傳回實際結果或例外狀況,只會傳回預設值。

.NET 隔離式背景工作角色目前不支援這樣做。

實體序列化

由於實體的狀態會永久保存,因此實體類別必須可序列化。 Durable Functions 執行階段會針對此目的使用 Json.NET 程式庫,這會支援原則和屬性來控制序列化和還原序列化流程。 最常使用的 C# 資料類型 (包括陣列和集合類型) 已可序列化,而且可以輕鬆地用於定義持久性實體的狀態。

例如,Json.NET 可以輕鬆地序列化和還原序列化下列類別:

[JsonObject(MemberSerialization = MemberSerialization.OptIn)]
public class User
{
    [JsonProperty("name")]
    public string Name { get; set; }

    [JsonProperty("yearOfBirth")]
    public int YearOfBirth { get; set; }

    [JsonProperty("timestamp")]
    public DateTime Timestamp { get; set; }

    [JsonProperty("contacts")]
    public Dictionary<Guid, Contact> Contacts { get; set; } = new Dictionary<Guid, Contact>();

    [JsonObject(MemberSerialization = MemberSerialization.OptOut)]
    public struct Contact
    {
        public string Name;
        public string Number;
    }

    ...
}

序列化屬性

在上述範例中,我們選擇了包含數個屬性,讓基礎序列化更易看見:

  • 我們會使用 [JsonObject(MemberSerialization.OptIn)] 標註類別,以提醒我們類別必須可序列化,並且只保存明確標示為 JSON 屬性的成員。
  • 我們會使用 [JsonProperty("name")] 標注要保存的欄位,以提醒我們欄位是所保存實體狀態的一部分,並指定要用在 JSON 表示法中使用的屬性名稱。

不過,這些屬性不是必要的;只要其他慣例或屬性使用 Json.NET,就允許它們。 例如,一個可能會使用 [DataContract] 屬性,或完全沒有屬性:

[DataContract]
public class Counter
{
    [DataMember]
    public int Value { get; set; }
    ...
}

public class Counter
{
    public int Value;
    ...
}

根據預設,類別的名稱不會*儲存為 JSON 表示法的一部分:亦即,我們會使用 TypeNameHandling.None 作為預設設定。 您可以使用 JsonObjectJsonProperty 屬性覆寫此預設行為。

對類別定義進行變更

在執行了應用程式之後對類別定義進行變更時,請小心,因為儲存的 JSON 物件可能不再符合新的類別定義。 通常,只要一個了解 JsonConvert.PopulateObject 所使用的還原序列化流程,仍有可能正確地處理變更資料格式。

例如,以下是變更及其效果的一些範例:

  • 若已新增屬性,但該屬性不存在於預存 JSON 中,則會採用其預設值。
  • 如果移除了存在於預存 JSON 中的屬性,則會失去先前的內容。
  • 如果重新命名了屬性,效果就如同移除舊的屬性並新增屬性一樣。
  • 如果屬性的類型已變更,使其無法再從預存 JSON 還原序列化,則會擲回例外狀況。
  • 如果屬性的類型已變更,但其仍能從預存 JSON 還原序列化,則其會這樣做。

有許多選項可用於自訂 Json.NET 的行為。 例如,若要在預存 JSON 包含類別中不存在的欄位時強制發生例外狀況,請指定屬性 JsonObject(MissingMemberHandling = MissingMemberHandling.Error)。 您也可以撰寫自訂程式碼進行還原序列化,讀取以任意格式儲存的 JSON。

序列化預設行為已從 Newtonsoft.Json 變更為 System.Text.Json。 如需詳細資訊,請參閱此處

實體建構

有時候我們想要對實體物件的建構方式施加更多控制。 我們現在描述數個選項,您可以在建構實體物件時用於變更預設行為。

第一次存取時進行自訂初始化

有時候,我們需要先執行一些特殊初始化,然後再將作業分派至從未存取或已刪除的實體。 若要指定此行為,可以在 DispatchAsync 之前新增條件:

[FunctionName(nameof(Counter))]
public static Task Run([EntityTrigger] IDurableEntityContext ctx)
{
    if (!ctx.HasState)
    {
        ctx.SetState(...);
    }
    return ctx.DispatchAsync<Counter>();
}

實體類別中的繫結

不同於一般函式,實體類別方法無法直接存取輸入和輸出繫結。 相反地,其必須在進入點函式宣告中擷取繫結資料,然後再傳遞至 DispatchAsync<T> 方法。 任何傳遞至 DispatchAsync<T> 的物件都會自動以引數的形式傳遞至實體類別建構函式。

下列範例會說明如何讓以類別為基礎的實體可以使用來自 Blob 輸入繫結CloudBlobContainer 參考。

public class BlobBackedEntity
{
    [JsonIgnore]
    private readonly CloudBlobContainer container;

    public BlobBackedEntity(CloudBlobContainer container)
    {
        this.container = container;
    }

    // ... entity methods can use this.container in their implementations ...

    [FunctionName(nameof(BlobBackedEntity))]
    public static Task Run(
        [EntityTrigger] IDurableEntityContext context,
        [Blob("my-container", FileAccess.Read)] CloudBlobContainer container)
    {
        // passing the binding object as a parameter makes it available to the
        // entity class constructor
        return context.DispatchAsync<BlobBackedEntity>(container);
    }
}

如需 Azure Functions 中繫結的詳細資訊,請參閱 Azure Functions 觸發程序和繫結文件。

在實體類別中插入相依性

實體類別支援 Azure Functions 相依性插入。 下列範例會示範如何將 IHttpClientFactory 服務註冊到以類別為基礎的實體。

[assembly: FunctionsStartup(typeof(MyNamespace.Startup))]

namespace MyNamespace
{
    public class Startup : FunctionsStartup
    {
        public override void Configure(IFunctionsHostBuilder builder)
        {
            builder.Services.AddHttpClient();
        }
    }
}

下列程式碼片段會示範如何將插入的服務併入到實體類別。

public class HttpEntity
{
    [JsonIgnore]
    private readonly HttpClient client;

    public HttpEntity(IHttpClientFactory factory)
    {
        this.client = factory.CreateClient();
    }

    public Task<int> GetAsync(string url)
    {
        using (var response = await this.client.GetAsync(url))
        {
            return (int)response.StatusCode;
        }
    }

    [FunctionName(nameof(HttpEntity))]
    public static Task Run([EntityTrigger] IDurableEntityContext ctx)
        => ctx.DispatchAsync<HttpEntity>();
}

第一次存取時進行自訂初始化

public class Counter : TaskEntity<int>
{
    protected override int InitializeState(TaskEntityOperation operation)
    {
        // This is called when state is null, giving a chance to customize first-access of entity.
        return 10;
    }
}

實體類別中的繫結

下列範例示範如何在類別型實體中使用 Blob 輸入繫結

public class BlobBackedEntity : TaskEntity<object?>
{
    private BlobContainerClient Container { get; set; }

    [Function(nameof(BlobBackedEntity))]
    public Task DispatchAsync(
        [EntityTrigger] TaskEntityDispatcher dispatcher, 
        [BlobInput("my-container")] BlobContainerClient container)
    {
        this.Container = container;
        return dispatcher.DispatchAsync(this);
    }
}

如需 Azure Functions 中繫結的詳細資訊,請參閱 Azure Functions 觸發程序和繫結文件。

在實體類別中插入相依性

實體類別支援 Azure Functions 相依性插入

以下示範如何在 program.cs 檔案中設定 HttpClient,以便稍後在實體類別中匯入。

public class Program
{
    public static void Main()
    {
        IHost host = new HostBuilder()
            .ConfigureFunctionsWorkerDefaults((IFunctionsWorkerApplicationBuilder workerApplication) =>
            {
                workerApplication.Services.AddHttpClient<HttpEntity>()
                    .ConfigureHttpClient(client => {/* configure http client here */});
             })
            .Build();

        host.Run();
    }
}

以下示範如何將插入的服務併入到實體類別。

public class HttpEntity : TaskEntity<object?>
{
    private readonly HttpClient client;

     public HttpEntity(HttpClient client)
    {
        this.client = client;
    }

    public async Task<int> GetAsync(string url)
    {
        using var response = await this.client.GetAsync(url);
        return (int)response.StatusCode;
    }

    [Function(nameof(HttpEntity))]
    public static Task Run([EntityTrigger] TaskEntityDispatcher dispatcher)
        => dispatcher.DispatchAsync<HttpEntity>();
}

注意

若要避免序列化的問題,請務必從序列化中排除要儲存插入值的欄位。

注意

不同於在一般的 .NET Azure Functions 中使用建構函式插入時,以類別為基礎的實體「必須」將函式進入點方法宣告為 static。 宣告非靜態函式進入點可能會導致一般 Azure Functions 物件初始設定式與持久性實體物件初始設定式之間發生衝突。

函式型語法

到目前為止,本文主著重於類別型語法,因為我們預期其更加適合大部分的應用程式。 不過,函式型語法可以適用於想要針對實體狀態和作業本身定義或管理其自己抽象的應用程式。 此外,其可能適用於實作需要泛型的程式庫時,而類別型語法目前不支援此泛型。

使用函式型語法,實體函式會明確處理作業分派,並明確管理實體的狀態。 例如,下列程式碼會顯示使用函式型語法實作的「計數器」實體。

[FunctionName("Counter")]
public static void Counter([EntityTrigger] IDurableEntityContext ctx)
{
    switch (ctx.OperationName.ToLowerInvariant())
    {
        case "add":
            ctx.SetState(ctx.GetState<int>() + ctx.GetInput<int>());
            break;
        case "reset":
            ctx.SetState(0);
            break;
        case "get":
            ctx.Return(ctx.GetState<int>());
            break;
        case "delete":
            ctx.DeleteState();
            break;
    }
}

實體內容物件

實體特定功能可以透過 IDurableEntityContext 類型的內容物件來存取。 此內容物件可用作實體函式的參數,以及透過 async-local 屬性 Entity.Current 取得。

下列成員提供目前作業的相關資訊,並允許我們指定傳回值。

  • EntityName:目前執行中實體的名稱。
  • EntityKey:目前執行中實體的金鑰。
  • EntityId:目前執行中實體的識別碼 (包括名稱和金鑰)。
  • OperationName:目前作業的名稱。
  • GetInput<TInput>():取得目前作業的輸入。
  • Return(arg):將一值傳回至已呼叫作業的協調流程。

下列成員會管理實體的狀態 (建立、讀取、更新、刪除)。

  • HasState:實體是否存在,亦即,有一些狀態。
  • GetState<TState>():取得實體的目前狀態。 如果實體尚未存在,則建立該實體。
  • SetState(arg):建立或更新實體的狀態。
  • DeleteState():如果實體存在,則會刪除實體的狀態。

如果 GetState 傳回的狀態是物件,應用程式碼可以直接將其修改。 不需要在結束時再次呼叫 SetState (但呼叫也不會造成任何損害)。 如果多次呼叫 GetState<TState>,則必須使用相同的類型。

最後,下列成員是用來對其他實體發出訊號,或啟動新的協調流程:

  • SignalEntity(EntityId, operation, input):將單向訊息傳送至實體。
  • CreateNewOrchestration(orchestratorFunctionName, input):啟動新的協調流程。
[Function(nameof(Counter))]
public static Task DispatchAsync([EntityTrigger] TaskEntityDispatcher dispatcher)
{
    return dispatcher.DispatchAsync(operation =>
    {
        if (operation.State.GetState(typeof(int)) is null)
        {
            operation.State.SetState(0);
        }

        switch (operation.Name.ToLowerInvariant())
        {
            case "add":
                int state = operation.State.GetState<int>();
                state += operation.GetInput<int>();
                operation.State.SetState(state);
                return new(state);
            case "reset":
                operation.State.SetState(0);
                break;
            case "get":
                return new(operation.State.GetState<int>());
            case "delete": 
                operation.State.SetState(null);
                break; 
        }

        return default;
    });
}

下一步