قراءة دليل المطور للكيانات الدائمة في .NET

في هذه المقالة، نصف الواجهات المتاحة لتطوير كيانات دائمة بـ.NET بالتفصيل، بما في ذلك الأمثلة والمشورة العامة.

توفر وظائف الكيان لمطوري التطبيقات بلا خادم طريقة ملائمة لتنظيم حالة التطبيق كمجموعة من الكيانات الدقيقة. لمزيد من التفاصيل حول المفاهيم الأساسية، راجع مقالة الكيانات الدائمة: المفاهيم .

نقدم حاليًا اثنين من الـ API لتعريف الكيانات:

  • يمثل بناء الجملة المستند إلى الفئة الكيانات والعمليات كفئات وأساليب. بناء الجملة هذا ينتج التعليمات البرمجية المقروءة بسهولة، ويسمح للعمليات التي سيتم استدعاؤها بطريقة متحقق منها من خلال واجهات.

  • بناء الجملة المستند إلى الدالة هو واجهة ذات مستوى أدنى تمثل الكيانات كدالات. ويوفر تحكمًا دقيقًا في كيفية إرسال عمليات الكيان، وكيفية إدارة حالة الكيان.

تركز هذه المقالة بشكل أساسي على بناء الجملة المستندة إلى الفئة، كما نتوقع أن تكون أكثر ملاءمة لمعظم التطبيقات. ومع ذلك، يمكن أن يكون بناء الجملة المستند إلى الدالة مناسبا للتطبيقات التي ترغب في تحديد أو إدارة تجريداتها الخاصة لحالة الكيان وعملياته. أيضا، يمكن أن يكون مناسبا لتنفيذ المكتبات التي تتطلب عمومية غير مدعومة حاليا من قبل بناء الجملة المستندة إلى الفئة.

إشعار

يمثل بناء الجملة المستندة إلى الفئة طبقة رقيقة فوق بناء الجملة المستندة إلى دالة؛ بحيث يمكن استخدام كلا المتغيرين بالتبادل في التطبيق نفسه.

تعريف فئات الكيان

المثال التالي هو تنفيذ Counter كيان يخزن قيمة واحدة من نوع العدد الصحيح، ويقدم أربع عمليات Addو ResetGetو و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>();
}

Run تحتوي الدالة على الصيغة المتداولة المطلوبة لاستخدام بناء الجملة المستند إلى الفئة. يجب أن تكون دالة Azure ثابتة . ينفذ مرة واحدة لكل رسالة عملية تتم معالجتها بواسطة الكيان. عندما DispatchAsync<T> يتم استدعاء والكيان غير موجود بالفعل في الذاكرة، فإنه يقوم بإنشاء كائن من النوع T ويملأ حقوله من آخر JSON مستمر تم العثور عليه في التخزين (إن وجد). ثم استدعاء الأسلوب مع اسم مطابق.

EntityTrigger لا تحتاج الدالة، Run في هذه العينة، إلى الإقامة داخل فئة Entity نفسها. يمكن أن يتواجد داخل أي موقع صالح ل Azure Function: داخل مساحة الاسم ذات المستوى الأعلى، أو داخل فئة المستوى الأعلى. ومع ذلك، إذا كان متداخلا أعمق (على سبيل المثال، يتم الإعلان عن الدالة داخل فئة متداخلة )، فلن يتم التعرف على هذه الدالة بواسطة أحدث وقت تشغيل.

إشعار

يتم إنشاء حالة الكيان المستند إلى الفئة ضمنيا قبل أن يعالج الكيان عملية، ويمكن حذفها بشكل صريح في عملية عن طريق استدعاء Entity.Current.DeleteState().

إشعار

تحتاج إلى إصدار 4.0.5455 Azure Functions Core Tools أو أعلى لتشغيل الكيانات في النموذج المعزول.

هناك طريقتان لتعريف كيان كفئة في نموذج العامل المعزول 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>();
}

تحذير

عند كتابة الكيانات التي تستمد من ITaskEntity أو TaskEntity<TState>، من المهم عدم تسمية أسلوب RunAsyncمشغل الكيان الخاص بك . سيؤدي هذا إلى أخطاء وقت التشغيل عند استدعاء الكيان حيث هناك تطابق غامض مع اسم الأسلوب "RunAsync" بسبب ITaskEntity تعريف "RunAsync" على مستوى المثيل بالفعل.

حذف الكيانات في النموذج المعزول

يتم حذف كيان في النموذج المعزول عن طريق تعيين حالة الكيان إلى null. تعتمد كيفية تحقيق ذلك على مسار تنفيذ الكيان الذي يتم استخدامه.

  • عند اشتقاق بناء ITaskEntity الجملة المستند إلى الدالة أو استخدامه، يتم إنجاز الحذف عن طريق استدعاء TaskEntityOperation.State.SetState(null).
  • عند الاشتقاق من TaskEntity<TState>، يتم تعريف الحذف ضمنيا. ومع ذلك، يمكن تجاوزه عن طريق تعريف أسلوب Delete على الكيان. يمكن أيضا حذف الحالة من أي عملية عبر this.State = null.
    • يتطلب TState الحذف عن طريق تعيين الحالة إلى قيمة خالية أن يكون يقبل القيم الخالية.
    • تحذف عملية الحذف المعرفة ضمنيا غير قابلة للقيمة TStateالخالية .
  • عند استخدام POCO كحالتك (وليس مشتقا من TaskEntity<TState>)، يتم تعريف الحذف ضمنيا. من الممكن تجاوز عملية الحذف عن طريق تعريف أسلوب Delete على POCO. ومع ذلك، لا توجد طريقة لتعيين الحالة إلى null في مسار POCO بحيث تكون عملية الحذف المحددة ضمنيا هي الحذف الحقيقي الوحيد.

متطلبات الفئة

فئات الكيان هي من الـ POCO (كائنات CLR القديمة العادية) التي لا تحتاج إلى أي فئات فائقة خاصة، أو واجهات، أو سمات. ورغم ذلك:

  • يجب أن تكون الفئة قابلة للبناء (راجع إنشاء الكيان).
  • يجب أن تكون الفئة قابلة للتسلسل JSON (راجع تسلسل الكيان).

أيضا، يجب أن يفي أي أسلوب مخصص ليتم استدعاؤه كلعملية بمتطلبات أخرى:

  • يجب أن يكون لدى العملية وسيطة واحدة على الأكثر، ويجب ألا يكون لها أي تحميل زائد، أو وسائط نوع عام.
  • يجب أن ترجع Task عملية من المفترض أن يتم استدعاؤها من تنسيق باستخدام واجهة أو Task<T>.
  • يجب أن تكون الوسائط وقيم الإرجاع قيمًا، أو كائنات قابلة للتسلسل.

ماذا يمكن أن تفعل العمليات؟

يمكن لكافة عمليات الكيان قراءة حالة الكيان وتحديثها، والتغييرات على الحالة تبقى في التخزين تلقائيًا. علاوة على ذلك، يمكن إجراء عمليات الإدخال/ إخراج خارجية، أو حسابات أخرى ضمن الحدود العامة الشائعة لكافة Azure Functions.

تتمتع العمليات أيضا بإمكانية الوصول إلى الوظائف التي يوفرها 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;      
}

الوصول إلى الكيانات مباشرة

يمكن الوصول إلى الكيانات المستندة إلى الفئة مباشرة، باستخدام أسماء سلاسل صريحة للكيان وعملياته. يقدم هذا القسم أمثلة. للحصول على شرح أعمق للمفاهيم الأساسية (مثل الإشارات مقابل المكالمات)، راجع المناقشة في كيانات Access.

إشعار

حيثما أمكن، يجب عليك الوصول إلى الكيانات من خلال الواجهات، لأنها توفر المزيد من التحقق من النوع.

مثال: عميل يرسل إشارة إلى كيان

تقوم دالة Azure Http التالية بتنفيذ عملية DELETE باستخدام اصطلاحات REST. ترسل إشارة حذف إلى كيان العداد الذي يتم تمرير مفتاحه في مسار 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 التالية بتنفيذ عملية GET باستخدام اصطلاحات REST. يقرأ الحالة الحالية للكيان العداد الذي يتم تمرير مفتاحه في مسار 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 التالية بتنفيذ عملية DELETE باستخدام اصطلاحات REST. ترسل إشارة حذف إلى كيان العداد الذي يتم تمرير مفتاحه في مسار 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 التالية بتنفيذ عملية GET باستخدام اصطلاحات REST. يقرأ الحالة الحالية للكيان العداد الذي يتم تمرير مفتاحه في مسار 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; 
}

الوصول إلى الكيانات من خلال الواجهات

يمكن استخدام الواجهات؛ للوصول إلى الكيانات عبر كائنات الوكيل التي تم إنشاؤها. يضمن هذا الأسلوب تطابق اسم العملية ونوع وسيطتها مع ما يتم تنفيذه. نوصي باستخدام واجهات للوصول إلى الكيانات كلما أمكن ذلك.

على سبيل المثال: يمكننا تعديل مثال العداد كما يلي:

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

public class Counter : ICounter
{
    ...
}

تشبه فئات الكيانات وواجهات الكيان الحبوب وواجهات الحبوب التي تحظى بشعبية Orleans. لمزيد من المعلومات حول أوجه التشابه والاختلافات بين الكيانات الدائمة وOrleans، راجع المقارنة مع الجهات الفاعلة الظاهرية.

بالإضافة إلى توفير التحقق من النوع، فإن الواجهات مفيدة لفصل أفضل بين الاهتمامات داخل التطبيق. على سبيل المثال، نظرا لأن الكيان يمكنه تنفيذ واجهات متعددة، يمكن أن يؤدي كيان واحد أدوارا متعددة. أيضا، نظرا لأنه يمكن تنفيذ واجهة من قبل كيانات متعددة، يمكن تنفيذ أنماط الاتصال العامة كمكتبات قابلة لإعادة الاستخدام.

مثال: يرسل العميل إشارات إلى الكيان من خلال واجهة

يمكن استخدام 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 يمكن استخدام واجهات برمجة التطبيقات فقط للعمليات أحادية الاتجاه. حتى إذا كانت العملية ترجع Task<T>، فإن قيمة المعلمة T ستكون دائما خالية أو default، وليس النتيجة الفعلية. على سبيل المثال، ليس من المنطقي الإشارة Get إلى العملية، حيث لا يتم إرجاع أي قيمة. بدلا من ذلك، يمكن للعملاء استخدام إما ReadStateAsync للوصول إلى حالة العداد مباشرة، أو يمكن بدء دالة منسق تستدعي Get العملية.

مثال: يشير التنسيق أولا ثم يستدعي الكيان من خلال الوكيل

لاستدعاء كيان أو الإشارة إليه من داخل تنسيق، CreateEntityProxy يمكن استخدامه، جنبا إلى جنب مع نوع الواجهة، لإنشاء وكيل للكيان. يمكن استخدام هذا الوكيل بعد ذلك للاتصال أو الإشارة إلى العمليات:

[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 ، وأي عمليات ترجع Task أو Task<T> يتم استدعاؤها. يمكن للمرء تغيير هذا السلوك الافتراضي، وعمليات الإشارة حتى إذا كانت ترجع المهمة، باستخدام SignalEntity<IInterfaceType> الأسلوب بشكل صريح.

خيار أقصر لتحديد الهدف

عند استدعاء كيان أو الإشارة إليه باستخدام واجهة، يجب أن تحدد الوسيطة الأولى الكيان المستهدف. يمكن تحديد الهدف إما عن طريق تحديد معرف الكيان، أو، في الحالات التي يوجد فيها فئة واحدة فقط تقوم بتنفيذ الكيان، مفتاح الكيان وحسب:

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

إذا تم تحديد مفتاح الكيان فقط ولا يمكن العثور على تطبيق فريد في وقت التشغيل، InvalidOperationException يتم طرحه.

القيود المفروضة على واجهات الكيان

كالعادة، يجب أن تكون كافة أنواع المعلمات والإرجاع قابلة للتسلسل على JSON. وإلا، يتم طرح استثناءات التسلسل في وقت التشغيل.

كما نفرض بعض القواعد:

  • يجب تعريف واجهات الكيان في نفس تجميع فئة الكيان.
  • يجب أن تقوم واجهات الكيانات بتعريف الأساليب فقط.
  • يجب ألا تحتوي واجهات الكيانات على معلمات عامة.
  • يجب ألا تحتوي أساليب واجهة الكيان على أكثر من معلمة واحدة.
  • يجب أن ترجع voidأساليب واجهة الكيان أو Taskأو .Task<T>

إذا تم انتهاك أي من هذه القواعد، فسيتم طرح InvalidOperationException في وقت التشغيل عند استخدام الواجهة كوسيطة نوع إلى SignalEntity، أو SignalEntityAsync، أو CreateEntityProxy. توضح رسالة الاستثناء القاعدة التي تم كسرها.

إشعار

يمكن الإشارة إلى أساليب الواجهة التي void يتم إرجاعها فقط (أحادية الاتجاه)، ولا تسمى (ثنائية الاتجاه). طرق الواجهة التي يتم إرجاعها Task أو Task<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 كإعداد افتراضي. يمكن تجاوز هذا السلوك الافتراضي باستخدام JsonObject أو JsonProperty السمات.

إجراء تغييرات على تعريفات الفئة

مطلوب بعض العناية عند إجراء تغييرات على تعريف فئة بعد تشغيل تطبيق، لأن كائن 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> تلقائيا إلى منشئ فئة الكيان كوسيطة.

يوضح المثال التالي كيف 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;
    }
}

الربط في فئات الكيان

يوضح المثال التالي كيفية استخدام ربط إدخال كائن ثنائي كبير الحجم في كيان يستند إلى فئة.

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.

يوضح التالي كيفية تكوين HttpClient في program.cs الملف الذي سيتم استيراده لاحقا في فئة الكيان.

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 العادية، يجب تعريف staticأسلوب نقطة إدخال الدالات للكيانات المستندة إلى الفئة . يمكن أن يؤدي الإعلان عن نقطة إدخال دالة غير ثابتة إلى تعارضات بين تهيئة كائن Azure Functions العادي ومهيئ كائن الكيانات الدائمة.

بناء الجملة المستند إلى الدالة

حتى الآن ركزنا على بناء الجملة المستندة إلى الفئة، كما نتوقع أن تكون أكثر ملاءمة لمعظم التطبيقات. ومع ذلك، يمكن أن يكون بناء الجملة المستندة إلى دالة مناسبًا للتطبيقات التي ترغب في تعريف، أو إدارة التجريدات الخاصة بها لحالة الكيان والعمليات. أيضا، يمكن أن يكون مناسبا عند تنفيذ المكتبات التي تتطلب عمومية غير مدعومة حاليا من قبل بناء الجملة المستند إلى الفئة.

مع بناء الجملة المستندة إلى الدالة، تتعامل "الدالة الكيان" بشكل صريح مع إرسال العملية، وتدير بشكل صريح حالة الكيان. على سبيل المثال، تظهر التعليمات البرمجية التالية كيان Counter المُطبق باستخدام بناء الجملة المستندة إلى دالة.

[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. يتوفر كائن السياق هذا كمعلمة لدالة الكيان، وعبر الخاصية 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;
    });
}

الخطوات التالية