Panduan pengembang untuk entitas tahan lama di .NET

Dalam artikel ini, kami menjelaskan antarmuka yang tersedia untuk mengembangkan entitas yang tahan lama dengan .NET secara rinci, termasuk contoh dan saran umum.

Fungsi entitas menyediakan pengembang aplikasi tanpa server dengan cara yang mudah untuk mengatur status aplikasi sebagai kumpulan entitas dengan jaringan halus. Untuk rincian selengkapnya tentang konsep yang mendasarinya, lihat artikel Entitas Tahan Lama: Konsep.

Saat ini, kami menawarkan dua API untuk mendefinisikan entitas:

  • Sintaks berbasis kelas mewakili entitas dan operasi sebagai kelas dan metode. Sintaks ini menghasilkan kode yang mudah dibaca dan memungkinkan operasi dipanggil dengan cara yang diperiksa jenisnya melalui antarmuka.

  • Sintaks berbasis fungsi adalah antarmuka tingkat bawah yang mewakili entitas sebagai fungsi. Ini memberikan kontrol yang tepat atas cara mengirim operasi entitas, dan cara mengelola status entitas.

Artikel ini terutama berfokus pada sintaks berbasis kelas, karena kami berharap itu lebih cocok untuk sebagian besar aplikasi. Namun, sintaksis berbasis fungsi dapat sesuai untuk aplikasi yang ingin menentukan atau mengelola abstraksi mereka sendiri untuk status dan operasi entitas. Selain itu, mungkin sesuai untuk menerapkan pustaka yang memerlukan generikitas yang saat ini tidak didukung oleh sintaks berbasis kelas.

Catatan

Sintaks berbasis kelas hanya lapisan di atas sintaks berbasis fungsi, sehingga kedua varian bisa digunakan secara bergantian dalam aplikasi yang sama.

Menentukan kelas entitas

Contoh berikut adalah implementasi entitas Counter yang menyimpan satu nilai bilangan bulat jenis, dan menawarkan empat operasi Add, Reset, Get, dan Delete .

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

Fungsi Run ini memuat boilerplate yang diperlukan untuk menggunakan sintaks berbasis kelas. Ini harus menjadi Fungsi Microsoft Azure yang statis. Ini dijalankan sekali untuk setiap pesan operasi yang diproses oleh entitas. Ketika DispatchAsync<T> dipanggil dan entitas belum ada dalam memori, maka membangun objek jenis T dan mengisi bidangnya dari JSON terakhir yang ditemukan dalam penyimpanan (jika ada). Lalu, memanggil metode dengan nama yang cocok.

EntityTrigger Fungsi, Run dalam sampel ini, tidak perlu berada dalam kelas Entitas itu sendiri. Ini dapat berada di lokasi yang valid untuk Azure Function: di dalam namespace tingkat atas, atau di dalam kelas tingkat atas. Namun, jika berlapis lebih dalam (misalnya, Fungsi dideklarasikan di dalam kelas berlapis ), fungsi ini tidak akan dikenali oleh runtime terbaru.

Catatan

Status entitas berbasis kelas dibuat secara implisit sebelum entitas memproses operasi, dan dapat dihapus secara eksplisit dalam operasi dengan memanggil Entity.Current.DeleteState() .

Catatan

Anda memerlukan versi 4.0.5455 Azure Functions Core Tools atau yang lebih baru untuk menjalankan entitas dalam model yang terisolasi.

Ada dua cara untuk mendefinisikan entitas sebagai kelas dalam model pekerja terisolasi C#. Mereka menghasilkan entitas dengan struktur serialisasi status yang berbeda.

Dengan pendekatan berikut, seluruh objek diserialisasikan saat menentukan entitas.

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>Implementasi berbasis, yang memudahkan penggunaan injeksi dependensi. Dalam hal ini, status dideserialisasi ke State properti, dan tidak ada properti lain yang diserialisasikan/dideserialisasi.

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

Peringatan

Saat menulis entitas yang berasal dari ITaskEntity atau TaskEntity<TState>, penting untuk tidak memberi nama metode RunAsyncpemicu entitas Anda . Ini akan menyebabkan kesalahan runtime saat memanggil entitas karena ada kecocokan ITaskEntity ambigu dengan nama metode "RunAsync" karena sudah menentukan "RunAsync" tingkat instans.

Menghapus entitas dalam model yang terisolasi

Menghapus entitas dalam model terisolasi dilakukan dengan mengatur status entitas ke null. Bagaimana hal ini dicapai tergantung pada jalur implementasi entitas apa yang digunakan.

  • Saat turunan dari ITaskEntity atau menggunakan sintaks berbasis fungsi, penghapusan dilakukan dengan memanggil TaskEntityOperation.State.SetState(null).
  • Saat turunan dari TaskEntity<TState>, hapus ditentukan secara implisit. Namun, ini dapat ditimpa dengan mendefinisikan metode Delete pada entitas. Status juga dapat dihapus dari operasi apa pun melalui this.State = null.
    • Untuk menghapus dengan mengatur status ke null perlu TState nullable.
    • Operasi penghapusan yang ditentukan secara implisit menghapus non-nullable TState.
  • Saat menggunakan POCO sebagai status Anda (tidak berasal dari TaskEntity<TState>), hapus didefinisikan secara implisit. Dimungkinkan untuk mengambil alih operasi penghapusan dengan menentukan metode Delete pada POCO. Namun, tidak ada cara untuk mengatur status ke null dalam rute POCO sehingga operasi penghapusan yang ditentukan secara implisit adalah satu-satunya penghapusan yang benar.

Persyaratan Kelas

Kelas entitas adalah POCO (objek CLR lama biasa) yang tidak memerlukan super-kelas, antarmuka, atau atribut khusus. Akan tetapi:

Selain itu, metode apa pun yang dimaksudkan untuk dipanggil sebagai operasi harus memenuhi persyaratan lain:

  • Operasi harus memiliki paling banyak satu argumen, dan tidak boleh memiliki kelebihan beban atau argumen jenis generik.
  • Operasi yang dimaksudkan untuk dipanggil dari orkestrasi menggunakan antarmuka harus kembali Task atau Task<T>.
  • Argumen dan nilai yang dikembalikan harus nilai atau objek yang dapat diserialkan.

Apa yang bisa dilakukan operasi?

Semua operasi entitas dapat membaca dan memperbarui status entitas, dan perubahan pada status secara otomatis berlanjut ke penyimpanan. Selain itu, operasi dapat melakukan I/O eksternal atau komputasi lainnya, dalam batas umum yang umum untuk semua Fungsi Microsoft Azure.

Operasi juga memiliki akses ke fungsionalitas yang disediakan oleh konteks Entity.Current:

  • EntityName: nama entitas yang saat ini melaksanakan.
  • EntityKey: kunci entitas yang saat ini melaksanakan.
  • EntityId: ID entitas yang saat ini melaksanakan (termasuk nama dan kunci).
  • SignalEntity: mengirim pesan satu arah ke entitas.
  • CreateNewOrchestration: memulai orkestrasi baru.
  • DeleteState: menghapus status entitas ini.

Contohnya, kita dapat memodifikasi entitas penghitung sehingga memulai orkestrasi ketika penghitung mencapai 100 dan meneruskan ID entitas sebagai argumen input:

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

Mengakses entitas secara langsung

Entitas berbasis kelas dapat diakses secara langsung, menggunakan nama string eksplisit untuk entitas dan operasinya. Bagian ini menyediakan contoh. Untuk penjelasan yang lebih mendalam tentang konsep yang mendasar (seperti sinyal vs. panggilan), lihat diskusi di Entitas akses.

Catatan

Jika memungkinkan, Anda harus mengakses entitas melalui antarmuka, karena menyediakan lebih banyak pemeriksaan jenis.

Contoh: klien memberi sinyal entitas

Fungsi Http Microsoft Azure berikut menerapkan operasi DELETE menggunakan konvensi REST. Ini mengirimkan sinyal penghapusan ke entitas penghitung yang kuncinya dilewatkan di jalur 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);
}

Contoh: klien membaca status entitas

Fungsi Http Microsoft Azure berikut menerapkan operasi GET menggunakan konvensi REST. Ini membaca status entitas penghitung saat ini yang kuncinya dilewatkan di jalur 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);
}

Catatan

Objek yang dikembalikan ReadEntityStateAsync hanya salinan lokal, yaitu salinan bayangan status entitas dari beberapa titik waktu sebelumnya. Secara khusus, itu bisa kedaluarsa, dan memodifikasi objek ini tidak berpengaruh pada entitas aktual.

Contoh: orkestrasi sinyal pertama lalu memanggil entitas

Orkestrasi berikut memberi sinyal kepada entitas penghitung untuk menambahnya, lalu memanggil entitas yang sama untuk membaca nilai terbarunya.

[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;
}

Contoh: klien memberi sinyal entitas

Fungsi Http Microsoft Azure berikut menerapkan operasi DELETE menggunakan konvensi REST. Ini mengirimkan sinyal penghapusan ke entitas penghitung yang kuncinya dilewatkan di jalur 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);
}

Contoh: klien membaca status entitas

Fungsi Http Microsoft Azure berikut menerapkan operasi GET menggunakan konvensi REST. Ini membaca status entitas penghitung saat ini yang kuncinya dilewatkan di jalur 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;
}

Contoh: orkestrasi sinyal pertama lalu memanggil entitas

Orkestrasi berikut memberi sinyal kepada entitas penghitung untuk menambahnya, lalu memanggil entitas yang sama untuk membaca nilai terbarunya.

[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; 
}

Mengakses entitas melalui antarmuka

Antarmuka dapat digunakan untuk mengakses entitas melalui objek proksi yang dihasilkan. Pendekatan ini memastikan bahwa nama dan jenis argumen operasi cocok dengan yang diterapkan. Sebaiknya gunakan antarmuka untuk mengakses entitas jika memungkinkan.

Contohnya, kita dapat memodifikasi contoh penghitung sebagai berikut:

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

public class Counter : ICounter
{
    ...
}

Kelas entitas dan antarmuka entitas mirip dengan biji-bijian dan antarmuka biji-bijian yang dipopulerkan Orleans. Untuk informasi selengkapnya tentang kesamaan dan perbedaan antara Entitas Tahan Lama dan Orleans, lihat Perbandingan dengan aktor virtual.

Selain menyediakan pemeriksaan jenis, antarmuka berguna untuk pemisahan kekhawatiran yang lebih baik dalam aplikasi. Misalnya, karena entitas dapat menerapkan beberapa antarmuka, satu entitas dapat melayani beberapa peran. Selain itu, karena antarmuka dapat diimplementasikan oleh beberapa entitas, pola komunikasi umum dapat diimplementasikan sebagai pustaka yang dapat digunakan kembali.

Contoh: entitas sinyal klien melalui antarmuka

Kode klien dapat digunakan SignalEntityAsync<TEntityInterface> untuk mengirimkan sinyal ke entitas yang menerapkan TEntityInterface. Contohnya:

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

Dalam contoh ini, parameter proxy adalah contoh yang dihasilkan secara dinamis dari ICounter, yang secara internal menerjemahkan panggilan ke Delete ke dalam sinyal.

Catatan

API SignalEntityAsync hanya dapat digunakan untuk operasi satu arah. Bahkan jika operasi mengembalikan Task<T>, nilai parameter T akan selalu menjadi null atau default, bukan hasil aktual. Contohnya, tidak masuk akal untuk memberi sinyal operasi Get, karena tidak ada nilai yang dikembalikan. Sebagai gantinya, klien dapat menggunakan salah satu ReadStateAsync untuk mengakses status penghitung secara langsung, atau dapat memulai fungsi orkestrator yang memanggil operasi Get.

Contoh: sinyal orkestrasi pertama kemudian memanggil entitas melalui proksi

Untuk memanggil atau memberi sinyal kepada entitas dari dalam orkestrasi, CreateEntityProxy dapat digunakan, bersama dengan jenis antarmuka, agar menghasilkan proksi untuk entitas. Proksi ini lalu dapat digunakan untuk memanggil atau memberi sinyal operasi:

[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;
}

Secara implisit, setiap operasi yang mengembalikan void diberikan sinyal, dan setiap operasi yang mengembalikan Task atau Task<T> dipanggil. Seseorang dapat mengubah perilaku default ini, dan operasi sinyal bahkan jika mereka mengembalikan Tugas, dengan menggunakan metode SignalEntity<IInterfaceType> secara eksplisit.

Opsi yang lebih singkat untuk menentukan target

Saat memanggil atau memberi sinyal entitas menggunakan antarmuka, argumen pertama harus menentukan entitas target. Target dapat ditentukan baik dengan menentukan ID entitas, maupun, dalam kasus di mana hanya ada satu kelas yang menerapkan entitas, hanya kunci entitas:

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

Jika hanya kunci entitas yang ditentukan dan penerapan unik tidak dapat ditemukan pada waktu proses, InvalidOperationException akan dilemparkan.

Pembatasan antarmuka entitas

Seperti biasa, semua parameter dan jenis pengembalian harus dapat diserialkan oleh JSON. Jika tidak, pengecualian serialisasi dilemparkan pada waktu proses.

Kami juga memberlakukan beberapa aturan lagi:

  • Antarmuka entitas harus didefinisikan dalam perakitan yang sama dengan kelas entitas.
  • Antarmuka entitas hanya boleh menentukan metode.
  • Antarmuka entitas tidak boleh memuat parameter generik.
  • Metode antarmuka entitas tidak boleh memiliki lebih dari satu parameter.
  • Metode antarmuka entitas harus menampilkan void, Task, atau Task<T>.

Jika salah satu aturan ini dilanggar, InvalidOperationException dilemparkan pada waktu proses ketika antarmuka digunakan sebagai argumen jenis ke SignalEntity, SignalEntityAsync, atau CreateEntityProxy. Pesan pengecualian menjelaskan aturan yang dilanggar.

Catatan

Metode antarmuka yang mengembalikan void hanya dapat diberikan sinyal (satu arah), tidak disebut (dua arah). Metode antarmuka mengembalikan Task atau Task<T> dapat dipanggil atau diberikan sinyal. Jika dipanggil, mereka mengembalikan hasil operasi, atau melemparkan kembali pengecualian yang dilemparkan oleh operasi. Namun, ketika diberikan sinyal, mereka tidak mengembalikan hasil aktual atau pengecualian dari operasi, tetapi hanya nilai default.

Ini saat ini tidak didukung di pekerja terisolasi .NET.

Serialisasi entitas

Karena status entitas bertahan lama, kelas entitas harus dapat diserialkan. Runtime Durable Functions menggunakan pustaka Json.NET untuk tujuan ini, yang mendukung kebijakan dan atribut untuk mengontrol proses serialisasi dan deserialisasi. Jenis data C# yang paling umum digunakan (termasuk array dan jenis pengumpulan) sudah dapat diserialkan, dan dapat dengan mudah digunakan untuk mendefinisikan status entitas yang tahan lama.

Contohnya, Json.NET dapat dengan mudah membuat serialisasi dan deserialisasi kelas berikut:

[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;
    }

    ...
}

Atribut Serialisasi

Dalam contoh di atas, kami memilih untuk menyertakan beberapa atribut untuk membuat serialisasi yang mendasarinya lebih terlihat:

  • Kami memberikan anotasi kelas dengan [JsonObject(MemberSerialization.OptIn)] untuk mengingatkan kami bahwa kelas harus dapat diserialisasi, dan untuk bertahan hanya anggota yang secara eksplisit ditandai sebagai properti JSON.
  • Kami memberikan anotasi bidang yang akan dipertahankan dengan [JsonProperty("name")] untuk mengingatkan kami bahwa bidang adalah bagian dari status entitas yang dipertahankan, dan untuk menentukan nama properti yang akan digunakan dalam representasi JSON.

Namun, atribut ini tidak diperlukan; konvensi atau atribut lain diizinkan selama berfungsi dengan Json.NET. Misalnya, seseorang dapat menggunakan [DataContract] atribut, atau tidak ada atribut sekalipun:

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

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

Secara default, nama kelas tidak* disimpan sebagai bagian dari representasi JSON: yaitu, kita gunakan TypeNameHandling.None sebagai pengaturan default. Perilaku default ini dapat ditimpa menggunakan atribut JsonObject atau JsonProperty.

Melakukan perubahan pada definisi kelas

Beberapa perawatan diperlukan saat membuat perubahan pada definisi kelas setelah aplikasi dijalankan, karena objek JSON yang disimpan tidak dapat lagi cocok dengan definisi kelas baru. Namun, sering kali dimungkinkan untuk menangani dengan benar dengan mengubah format data selama seseorang memahami proses deserialisasi yang digunakan oleh JsonConvert.PopulateObject.

Contohnya, berikut adalah beberapa contoh perubahan dan efeknya:

  • Ketika properti baru ditambahkan, yang tidak ada di JSON yang disimpan, properti tersebut mengasumsikan nilai defaultnya.
  • Ketika properti dihapus, yang ada di JSON yang disimpan, konten sebelumnya hilang.
  • Ketika properti diganti namanya, efeknya seolah-olah menghapus properti lama dan menambahkan yang baru.
  • Ketika jenis properti diubah sehingga tidak dapat lagi dideserialisasi dari JSON yang disimpan, pengecualian dilemparkan.
  • Ketika jenis properti diubah, tetapi masih dapat dideserialisasi dari JSON yang disimpan, properti tersebut melakukannya.

Ada banyak opsi yang tersedia untuk menyesuaikan perilaku Json.NET. Misalnya, untuk memaksa pengecualian jika JSON yang disimpan berisi bidang yang tidak ada di kelas, tentukan atribut JsonObject(MissingMemberHandling = MissingMemberHandling.Error). Anda juga dapat menulis kode kustom untuk deserialisasi yang dapat membaca JSON yang disimpan dalam format sewenang-wenang.

Perilaku default serialisasi telah berubah dari Newtonsoft.Json ke System.Text.Json. Untuk informasi selengkapnya, lihat di sini.

Konstruksi entitas

Terkadang, kita ingin mengerahkan lebih banyak kontrol atas cara membuat objek entitas. Kami sekarang menjelaskan beberapa opsi untuk mengubah perilaku default ketika membuat objek entitas.

Inisialisasi kustom pada akses pertama

Terkadang, kita perlu melakukan beberapa inisialisasi khusus sebelum mengirimkan operasi ke entitas yang belum pernah diakses, atau yang telah dihapus. Untuk menentukan perilaku ini, seseorang dapat menambahkan bersyarat sebelum DispatchAsync:

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

Pengikatan di kelas entitas

Tidak seperti fungsi reguler, metode kelas entitas tidak memiliki akses langsung ke pengikatan input dan output. Sebaliknya, data yang mengikat harus diambil dalam deklarasi fungsi titik masuk lalu diteruskan ke metode DispatchAsync<T>. Setiap objek yang diteruskan ke DispatchAsync<T> diteruskan secara otomatis ke konstruktor kelas entitas sebagai argumen.

Contoh berikut menunjukkan cara referensi CloudBlobContainer dari pengikatan input objek besar biner dapat disediakan untuk entitas berbasis kelas.

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

Untuk informasi selengkapnya tentang pengikatan di Azure Functions, lihat dokumentasi Pemicu dan Pengikatan Azure Functions.

Injeksi dependensi di kelas entitas

Kelas entitas mendukung Injeksi Dependensi Azure Functions. Contoh berikut menunjukkan cara mendaftarkan layanan IHttpClientFactory ke entitas berbasis kelas.

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

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

Cuplikan berikut menunjukkan cara memasukkan layanan yang disuntikkan ke kelas entitas Anda.

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

Inisialisasi kustom pada akses pertama

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

Pengikatan di kelas entitas

Contoh berikut menunjukkan cara menggunakan pengikatan input blob di entitas berbasis kelas.

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

Untuk informasi selengkapnya tentang pengikatan di Azure Functions, lihat dokumentasi Pemicu dan Pengikatan Azure Functions.

Injeksi dependensi di kelas entitas

Kelas entitas mendukung Injeksi Dependensi Azure Functions.

Berikut ini menunjukkan cara mengonfigurasi HttpClient dalam program.cs file yang akan diimpor nanti di kelas entitas.

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

Berikut cara memasukkan layanan yang disuntikkan ke dalam kelas entitas Anda.

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

Catatan

Untuk menghindari masalah serialisasi, pastikan untuk mengecualikan bidang yang dimaksudkan untuk menyimpan nilai yang disuntikkan dari serialisasi.

Catatan

Tidak seperti ketika menggunakan injeksi konstruktor di Fungsi .NET Azure reguler, metode titik masuk fungsi untuk entitas berbasis kelas harus dideklarasikan static. Mendeklarasikan titik entri fungsi non-statis dapat menyebabkan konflik antara penginisialisasi objek Azure Functions normal dan penginisialisasi objek Entitas Tahan Lama.

Sintaks berbasis fungsi

Selama ini, kami berfokus pada sintaks berbasis kelas, karena kami berharap itu lebih cocok untuk sebagian besar aplikasi. Namun, sintaks berbasis fungsi dapat sesuai untuk aplikasi yang ingin menentukan atau mengelola abstraksi mereka sendiri untuk status dan operasi entitas. Selain itu, mungkin tepat saat menerapkan pustaka yang memerlukan generikitas yang saat ini tidak didukung oleh sintaks berbasis kelas.

Dengan sintaks berbasis fungsi, Fungsi Entitas secara eksplisit menangani pengiriman operasi, dan secara eksplisit mengelola status entitas. Contohnya, kode berikut menunjukkan entitas Counter yang diterapkan menggunakan sintaks berbasis fungsi.

[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;
    }
}

Objek konteks entitas

Fungsionalitas khusus entitas dapat diakses melalui objek konteks jenis IDurableEntityContext . Objek konteks ini tersedia sebagai parameter untuk fungsi entitas, dan melalui properti asinkron-lokal Entity.Current.

Anggota berikut memberikan informasi tentang operasi saat ini, dan memungkinkan kami menentukan nilai yang dikembalikan.

  • EntityName: nama entitas yang saat ini melaksanakan.
  • EntityKey: kunci entitas yang saat ini melaksanakan.
  • EntityId: ID entitas yang saat ini melaksanakan (termasuk nama dan kunci).
  • OperationName: nama operasi saat ini.
  • GetInput<TInput>(): mendapatkan input untuk operasi saat ini.
  • Return(arg): mengembalikan nilai ke orkestrasi yang disebut operasi.

Anggota berikut mengelola status entitas (membuat, membaca, memperbarui, menghapus).

  • HasState: apakah entitas ada, yaitu, memiliki beberapa status.
  • GetState<TState>(): mendapatkan status entitas saat ini. Jika belum ada, itu dibuat.
  • SetState(arg): membuat atau memperbarui status entitas.
  • DeleteState(): menghapus status entitas, jika ada.

Jika status yang dikembalikan GetState adalah objek, maka dapat langsung dimodifikasi oleh kode aplikasi. Tidak perlu menelepon SetState lagi di akhir (tetapi juga tidak membahayakan). Jika GetState<TState> dipanggil beberapa kali, jenis yang sama harus digunakan.

Akhirnya, anggota berikut digunakan untuk memberi sinyal entitas lain, atau memulai orkestrasi baru:

  • SignalEntity(EntityId, operation, input): mengirim pesan satu arah ke entitas.
  • CreateNewOrchestration(orchestratorFunctionName, input): memulai orkestrasi baru.
[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;
    });
}

Langkah berikutnya