نقل تطبيق Durable Functions من نموذج العامل المعزول (.NET)

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

تحذير

ينتهي دعم النموذج قيد التنفيذ في 10 نوفمبر 2026. نوصي بالانتقال الآن. للحصول على خلفية حول نموذج العامل المعزول، انظر .NET نظرة عامة على عملية العامل المعزول.

قائمة التحقق من الترحيل

استخدم القائمة التالية لتتبع تقدمك في كل خطوة ترحيل:

خطوة قسم
1. التحقق من المتطلبات المسبقة المتطلبات الأساسية
2. تحديث ملف المشروع تحديث ملف المشروع
3. أضف Program.cs أضف Program.cs
4. تحديث مراجع الحزم تحديث مراجع الحزم
5. تحديث كود الدالة تحديث كود الدالة
6. تحديث local.settings.json تحديث local.settings.json
7. اختبار محلي اختبار محلي
8. النشر إلى Azure النشر على Azure

المتطلبات المسبقه

  • دالات Azure Core Tools v4.x أو أحدث
  • .NET 8.0 SDK (أو النسخة المستهدفة .NET لديك)
  • Visual Studio 2022 أو VS Code مع دالات Azure الامتداد

تحديد التطبيقات التي يجب نقلها (اختياري)

إذا لم تكن متأكدا من التطبيقات التي لا تزال تستخدم نموذج العملية، قم بتشغيل سكريبت Azure PowerShell هذا:

$FunctionApps = Get-AzFunctionApp

$AppInfo = @{}

foreach ($App in $FunctionApps)
{
     if ($App.Runtime -eq 'dotnet')
     {
          $AppInfo.Add($App.Name, $App.Runtime)
     }
}

$AppInfo

التطبيقات التي تظهر dotnet كوقت تشغيل تستخدم نموذج العملية. التطبيقات التي تظهر dotnet-isolated تستخدم نموذج العامل المعزول.

تحديث ملف المشروع

قبل (قيد التنفيذ)

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <AzureFunctionsVersion>v4</AzureFunctionsVersion>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.NET.Sdk.Functions" Version="4.1.1" />
    <PackageReference Include="Microsoft.Azure.WebJobs.Extensions.DurableTask" Version="2.13.0" />
  </ItemGroup>
</Project>

بعد (عامل معزول)

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <AzureFunctionsVersion>v4</AzureFunctionsVersion>
    <OutputType>Exe</OutputType>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>
  <ItemGroup>
    <FrameworkReference Include="Microsoft.AspNetCore.App" />
    <PackageReference Include="Microsoft.Azure.Functions.Worker" Version="1.21.0" />
    <PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="1.17.2" />
    <PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore" Version="1.2.1" />
    <PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.DurableTask" Version="1.14.1" />
    <PackageReference Include="Microsoft.ApplicationInsights.WorkerService" Version="2.22.0" />
    <PackageReference Include="Microsoft.Azure.Functions.Worker.ApplicationInsights" Version="1.2.0" />
  </ItemGroup>
  <ItemGroup>
    <Using Include="System.Threading.ExecutionContext" Alias="ExecutionContext"/>
  </ItemGroup>
</Project>

التغييرات الرئيسية هي التحول إلى نوع إخراج قابل للتنفيذ واستبدال جميع حزم Microsoft.Azure.WebJobs.* بنظيراتها Microsoft.Azure.Functions.Worker.*.

أضف Program.cs

نموذج العامل المعزول يتطلب نقطة Program.cs دخول. أنشئ هذا الملف في جذر مشروعك. إذا كان FunctionsStartup لديك فئة في Startup.cs، انقل تسجيلات الخدمات إلى ConfigureServices الحظر واحذف Startup.cs.

using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

var host = new HostBuilder()
    .ConfigureFunctionsWebApplication()
    .ConfigureServices(services => {
        services.AddApplicationInsightsTelemetryWorkerService();
        services.ConfigureFunctionsApplicationInsights();
        
        // Add your custom services here (previously in FunctionsStartup)
        // services.AddSingleton<IMyService, MyService>();
    })
    .Build();

host.Run();

تحديث مراجع الحزم

تعيين حزمة Durable Functions

حزمة قيد المعالجة حزمة العمال المعزولة
Microsoft.Azure.WebJobs.Extensions.DurableTask Microsoft.Azure.Functions.Worker.Extensions.DurableTask
Microsoft.DurableTask.SqlServer.AzureFunctions Microsoft.Azure.Functions.Worker.Extensions.DurableTask.SqlServer
Microsoft.Azure.DurableTask.Netherite.AzureFunctions Microsoft.Azure.Functions.Worker.Extensions.DurableTask.Netherite

تعيين حزم الامتدادات الشائعة

قيد التنفيذ عامل معزول
Microsoft.Azure.WebJobs.Extensions.Storage Microsoft.Azure.Functions.Worker.Extensions.Storage.Blobs، .Queues، .Tables
Microsoft.Azure.WebJobs.Extensions.CosmosDB Microsoft.Azure.Functions.Worker.Extensions.CosmosDB
Microsoft.Azure.WebJobs.Extensions.ServiceBus Microsoft.Azure.Functions.Worker.Extensions.ServiceBus
Microsoft.Azure.WebJobs.Extensions.EventHubs Microsoft.Azure.Functions.Worker.Extensions.EventHubs
Microsoft.Azure.WebJobs.Extensions.EventGrid Microsoft.Azure.Functions.Worker.Extensions.EventGrid

مهم

قم بإزالة أي مراجع إلى Microsoft.Azure.WebJobs.* وMicrosoft.Azure.Functions.Extensions من مشروعك.

تحديث كود الدالة

يغطي هذا القسم تغييرات الكود لكل نوع من Durable Functions. انتقل إلى قسم أنواع الوظائف التي يستخدمها تطبيقك:

للحصول على تعيين API بواجهة برمجة تطبيقات كاملة، راجع مرجع واجهة برمجة التطبيقات.

تغييرات مساحة الأسماء

// Before (In-Process)
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.DurableTask;
using Microsoft.Azure.WebJobs.Extensions.Http;

// After (Isolated Worker)
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.DurableTask;
using Microsoft.DurableTask.Client;
using Microsoft.DurableTask.Entities;

تغييرات سمات الدالة

// Before (In-Process)
[FunctionName("MyOrchestrator")]

// After (Isolated Worker)
[Function(nameof(MyOrchestrator))]

تغييرات وظيفة الأوركستراتور

قبل (In-Process):

[FunctionName("OrderOrchestrator")]
public static async Task<OrderResult> RunOrchestrator(
    [OrchestrationTrigger] IDurableOrchestrationContext context,
    ILogger log)
{
    var order = context.GetInput<Order>();
    
    await context.CallActivityAsync("ValidateOrder", order);
    await context.CallActivityAsync("ProcessPayment", order.Payment);
    await context.CallActivityAsync("ShipOrder", order);
    
    return new OrderResult { Success = true };
}

بعد (عامل معزول):

[Function(nameof(OrderOrchestrator))]
public static async Task<OrderResult> OrderOrchestrator(
    [OrchestrationTrigger] TaskOrchestrationContext context)
{
    ILogger logger = context.CreateReplaySafeLogger(nameof(OrderOrchestrator));
    var order = context.GetInput<Order>();
    
    await context.CallActivityAsync("ValidateOrder", order);
    await context.CallActivityAsync("ProcessPayment", order.Payment);
    await context.CallActivityAsync("ShipOrder", order);
    
    return new OrderResult { Success = true };
}

الاختلافات الرئيسية

الجانب In-Process عامل معزول
نوع السياق IDurableOrchestrationContext TaskOrchestrationContext
Logger ILogger المعامل context.CreateReplaySafeLogger()
السمة [FunctionName] [Function]

تغيرات دالة النشاط

قبل (In-Process):

[FunctionName("ValidateOrder")]
public static bool ValidateOrder(
    [ActivityTrigger] Order order,
    ILogger log)
{
    log.LogInformation("Validating order {OrderId}", order.Id);
    return order.Items.Any() && order.TotalAmount > 0;
}

بعد (عامل معزول):

[Function(nameof(ValidateOrder))]
public static bool ValidateOrder(
    [ActivityTrigger] Order order,
    FunctionContext executionContext)
{
    ILogger logger = executionContext.GetLogger(nameof(ValidateOrder));
    logger.LogInformation("Validating order {OrderId}", order.Id);
    return order.Items.Any() && order.TotalAmount > 0;
}

تغييرات وظيفة العميل

قبل (In-Process):

[FunctionName("StartOrder")]
public static async Task<IActionResult> StartOrder(
    [HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequest req,
    [DurableClient] IDurableOrchestrationClient client,
    ILogger log)
{
    var order = await req.ReadFromJsonAsync<Order>();
    string instanceId = await client.StartNewAsync("OrderOrchestrator", order);
    
    return client.CreateCheckStatusResponse(req, instanceId);
}

بعد (عامل معزول):

[Function("StartOrder")]
public static async Task<HttpResponseData> StartOrder(
    [HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequestData req,
    [DurableClient] DurableTaskClient client,
    FunctionContext executionContext)
{
    ILogger logger = executionContext.GetLogger("StartOrder");
    var order = await req.ReadFromJsonAsync<Order>();
    string instanceId = await client.ScheduleNewOrchestrationInstanceAsync(
        nameof(OrderOrchestrator), 
        order
    );
    
    return await client.CreateCheckStatusResponseAsync(req, instanceId);
}

تغييرات نوع العميل

قيد التنفيذ عامل معزول
IDurableOrchestrationClient DurableTaskClient
StartNewAsync() ScheduleNewOrchestrationInstanceAsync()
CreateCheckStatusResponse() CreateCheckStatusResponseAsync()
HttpRequest / IActionResult HttpRequestData / HttpResponseData

تغييرات سياسة إعادة المحاولة

الاستخدامات RetryOptions أثناء العملية مع CallActivityWithRetryAsync. يستخدم TaskOptions العامل المعزول مع المعيار CallActivityAsync.

قبل (In-Process):

var retryOptions = new RetryOptions(
    firstRetryInterval: TimeSpan.FromSeconds(5),
    maxNumberOfAttempts: 3);

string result = await context.CallActivityWithRetryAsync<string>(
    "MyActivity", retryOptions, input);

بعد (عامل معزول):

var retryOptions = new TaskOptions(
    new TaskRetryOptions(new RetryPolicy(
        maxNumberOfAttempts: 3,
        firstRetryInterval: TimeSpan.FromSeconds(5))));

string result = await context.CallActivityAsync<string>(
    "MyActivity", input, retryOptions);

تغييرات دالة الكيان

قبل (In-Process):

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

بعد (عامل معزول):

[Function(nameof(Counter))]
public static Task Counter([EntityTrigger] TaskEntityDispatcher dispatcher)
{
    return dispatcher.DispatchAsync<CounterEntity>();
}

public class CounterEntity
{
    public int Value { get; set; }
    
    public void Add(int amount) => Value += amount;
    public int Get() => Value;
}

تغيرات سلوك الكسر

راجع هذه التغييرات قبل اختبار تطبيقك المنتقل. للحصول على الربط الكامل لواجهة برمجة التطبيقات (API) بواجهة برمجة التطبيقات (API) الكاملة، راجع مرجع واجهة برمجة التطبيقات (API).

تحذير

تم تغيير الإعداد السريعي: العامل المعزول يستخدم System.Text.Json بشكل افتراضي بدلا من Newtonsoft.Json. إذا كانت التوزيع الموسيقي لديك تمر بأشياء معقدة، اختبر التسلسل بعناية. انظر اختلافات تسلسل JSON لخيارات التكوين.

تحذير

تغيير الإعداد الافتراضي ContinueAsNew: تغير الإعداد preserveUnprocessedEvents الافتراضي للمعلمة من false (2.x) إلى true (معزول). إذا كان تنسيقك يستخدم ContinueAsNew ويعتمد على التخلص من أحداث غير معالجة، فرفض preserveUnprocessedEvents: falseبشكل صريح .

ملاحظة

تغيير الإعداد الافتراضي RestartAsync: تغير الوضع restartWithNewInstanceId الافتراضي للمعلمات من true (2.x) إلى false (معزول). إذا كان كودك يستدعي RestartAsync ويعتمد على معرف مثيل جديد يتم توليده، قم بتمرير restartWithNewInstanceId: true.

تغييرات أخرى ملحوظة:

  • تمت إزالة بروكسيات الكياناتCreateEntityProxy<T> غير متوفرة. استخدم Entities.CallEntityAsync أو Entities.SignalEntityAsync بشكل مباشر.
  • تمت إزالة عمليات مركز المهام المشتركة — التحميل الزائد الذي تم قبوله taskHubName/connectionName غير متوفر. يتم دعم عمليات مركز المهام نفسها فقط.
  • تاريخ التوزيع الموسيقي لمDurableOrchestrationStatus.History يعد موجودا في كائن الحالة. استخدم DurableTaskClient.GetOrchestrationHistoryAsync.

تحديث local.settings.json

التغيير الرئيسي هو ضبط FUNCTIONS_WORKER_RUNTIME من dotnet إلى dotnet-isolated:

{
    "IsEncrypted": false,
    "Values": {
        "AzureWebJobsStorage": "UseDevelopmentStorage=true",
        "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated"
    }
}

ملاحظة

تكوين التخزين الخلفي (تخزين Azure، MSSQL، Netherite، أو Durable Task Scheduler) لم يتغير بسبب الترحيل. احتفظ بإعدادات التخزين الحالية لديك.

اختبر بشكل محلي

شغل تطبيق الوظائف محليا وتحقق من أن جميع التنسيقات والأنشطة والكيانات تعمل بشكل صحيح.

func start

التحقق من الوظائف

اختبر السيناريوهات التالية حسب الاقتضاء:

  1. ابدأ التوزيع الأوركسترالي باستخدام مشغل HTTP
  2. مراقبة حالة التوزيع الموسيقي
  3. تحقق من ترتيب تنفيذ النشاط
  4. عمليات الكيانات المختبرة إذا كان ذلك ممكنا
  5. تحقق من بيانات Application Insights

النشر إلى Azure

استخدم فتحات النشر لتقليل وقت التوقف:

  1. أنشئ فتحة تجهيز لتطبيق الوظيفة الخاص بك.
  2. تحديث إعدادات فتحة التجريب:
    • ضبطه FUNCTIONS_WORKER_RUNTIME على dotnet-isolated.
    • قم بتحديث نسخة .NET إذا لزم الأمر.
  3. نشر الكود الترحيل إلى فتحة المرحلة.
  4. اختبر جيدا في مكان التجهيز.
  5. قم بتبديل الفتحات لنقل التغييرات إلى الإنتاج.

تحديث إعدادات التطبيق

في بوابة Azure أو عبر CLI:

az functionapp config appsettings set \
    --name <FUNCTION_APP_NAME> \
    --resource-group <RESOURCE_GROUP> \
    --settings FUNCTIONS_WORKER_RUNTIME=dotnet-isolated

تحديث تكوين المكدس

إذا كنت تستهدف نسخة .NET مختلفة:

az functionapp config set \
    --name <FUNCTION_APP_NAME> \
    --resource-group <RESOURCE_GROUP> \
    --net-framework-version v8.0

مشاكل الهجرة الشائعة

المشكلة: أخطاء تحميل التجميع

عَرَض:Could not load file or assembly أخطاء.

Solution: تأكد من إزالة جميع مراجع حزمة Microsoft.Azure.WebJobs.* واستبدالها بمعادلات للعامل المعزولة.

المشكلة: لم يتم العثور على خاصية الربط

عَرَض:The type or namespace 'QueueTrigger' could not be found

الحل: أضف حزمة الامتداد المناسبة وحدث باستخدام العبارات:

// Add using statement
using Microsoft.Azure.Functions.Worker;

// Install package
// dotnet add package Microsoft.Azure.Functions.Worker.Extensions.Storage.Queues

المشكلة: IDurableOrchestrationالسياق غير موجود

عَرَض:The type or namespace 'IDurableOrchestrationContext' could not be found

الحل: استبدل ب TaskOrchestrationContext:

using Microsoft.DurableTask;

[Function(nameof(MyOrchestrator))]
public static async Task MyOrchestrator([OrchestrationTrigger] TaskOrchestrationContext context)
{
    // ...
}

المشكلة: اختلافات في تسلسل JSON

عَرَض: أخطاء التسلسل أو تنسيقات البيانات غير المتوقعة

الحل: النموذج المعزول يستخدم System.Text.Json بشكل افتراضي. تكوين التسلسل في Program.cs:

var host = new HostBuilder()
    .ConfigureFunctionsWebApplication()
    .ConfigureServices(services => {
        services.Configure<JsonSerializerOptions>(options => {
            options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
        });
    })
    .Build();

لاستخدام Newtonsoft.Json بدلا من ذلك:

services.Configure<WorkerOptions>(options => {
    options.Serializer = new NewtonsoftJsonObjectSerializer();
});

المشكلة: ترحيل إعدادات التسلسل المخصص

عَرَض: لقد استخدمت IMessageSerializerSettingsFactory في نموذج العملية وتحتاج إلى ما يعادله في العامل المعزول.

الحل: قم بتكوين المتسلسل على مستوى العامل في Program.cs. للتفاصيل، راجع قسم behavioral changes في مرجع واجهة برمجة التطبيقات وتسلسل واستمرارية في Durable Functions.

لاستخدام Newtonsoft.Json مع إعدادات مخصصة:

// Program.cs
var host = new HostBuilder()
    .ConfigureFunctionsWebApplication()
    .ConfigureServices(services =>
    {
        services.Configure<WorkerOptions>(options =>
        {
            var settings = new JsonSerializerSettings
            {
                TypeNameHandling = TypeNameHandling.None,
                DateFormatHandling = DateFormatHandling.IsoDateFormat,
            };
            options.Serializer = new NewtonsoftJsonObjectSerializer(settings);
        });
    })
    .Build();

ملاحظة

يتطلب هذا النهج حزم NuGet Newtonsoft.Json و Azure.Core.Serialization.

قائمة تحقق

استخدم هذه القائمة لضمان الانتقال الكامل:

  • ملف المشروع المحدث مع <OutputType>Exe</OutputType>
  • تم استبدال Microsoft.NET.Sdk.Functions بحزم العمال
  • تم استبدال Microsoft.Azure.WebJobs.Extensions.DurableTask بحزمة معزولة
  • تم إنشاؤه Program.cs باستخدام تكوين المضيف
  • تم إزالة FunctionsStartup الفئة (إذا كانت موجودة)
  • تم تحديث الجميع [FunctionName] إلى [Function]
  • تم استبداله IDurableOrchestrationContext ب TaskOrchestrationContext
  • تم استبداله IDurableOrchestrationClient ب DurableTaskClient
  • تحديث تسجيل السجلات لاستخدام DI أو FunctionContext
  • تم التحديث local.settings.json مع dotnet-isolated وقت التشغيل
  • تم حذف جميع Microsoft.Azure.WebJobs.* باستخدام العبارات
  • تمت إضافة Microsoft.Azure.Functions.Worker باستخدام العبارات
  • تم استبداله CreateEntityProxy<T> بمكالمات مباشرة CallEntityAsync/SignalEntityAsync
  • استبدال التحميل الزائد لتشغيل محور المهام (إذا تم استخدامه)
  • استبدلت استدعاءات by-ID الدفعية GetStatusAsync/PurgeInstanceHistoryAsync بالاستدعاءات القائمة على المرشح أو الفردية
  • الوصول المهاجر DurableOrchestrationStatus.History إلى GetOrchestrationHistoryAsync
  • تحديث معايير بناء الكيانات DispatchAsync لاستخدام DI
  • اختبرت جميع الدوال محليا
  • تم نشره في موضع التجهيز والتحقق منه
  • تم التحويل إلى الإنتاج

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