У меня есть набор функций, запускаемых по HTTP, в приложении «Функции Azure» (.NET 8, изолированная рабочая модель), которые интегрируются с Dynamics 365 с помощью Dataverse ServiceClient (v1.1.17). Проблема, с которой я сталкиваюсь, заключается в том, что когда я отправляю несколько запросов, базовый HttpClient
неожиданно удаляется.
Сначала я думал, что эта проблема связана с жизненным циклом ServiceClient
. Тем не менее, я протестировал его, зарегистрировав как ограниченный, так и одноэлементный, но разницы не было. Я пробовал создать новый экземпляр и удалять его после каждого использования. Я даже попробовал «трюк», который нашел в репозитории GitHub CdsWeb, который оборачивает клиент и создает повторно используемую копию экземпляра.
Я запустил тот же код в новом минимальном API, и он работает отлично. То же самое касается запуска внутри консольного приложения внутри цикла. При нагрузочном тестировании с k6 (100 VU за 20 с) получается 1 завершенная и 99 прерванных итераций. Однако при реализации в минимальном API это приводит к 100 завершенным и 0 прерванным итерациям.
Запуск функции в Azure (на основе потребления) приводит к той же ошибке. Он также регулярно выдает TaskCanceledException
.
Это минимальный тест:
var query = new QueryExpression(entityName: "contact")
{
TopCount = 1,
ColumnSet = new ColumnSet("contactid", "firstname")
};
// serviceClient injected as a singleton. Access token is cached in memory and reused.
var results = await serviceClient.RetrieveMultipleAsync(query);
var contactEntity = results.Entities.SingleOrDefault();
Вот как настраивается ServiceClient:
// Adding it to servicecollection
services.AddSingleton<IOrganizationServiceAsync, ServiceClient>(provider =>
{
var appSettings = provider.GetRequiredService<IOptions<AppSettings>>();
var cache = provider.GetRequiredService<IMemoryCache>();
var managedIdentity = new DefaultAzureCredential(new DefaultAzureCredentialOptions
{
// Using Managed Identity running in Azure
ManagedIdentityClientId = appSettings.Value.ManagedIdentityClientId,
ExcludeManagedIdentityCredential = false,
// Using Visual Studio/VS Code credentials for local devlopment
ExcludeVisualStudioCredential = false,
ExcludeVisualStudioCodeCredential = false
});
var environment = appSettings.Value.DataverseInstanceUrl;
return new ServiceClient(
tokenProviderFunction: f => GetToken(environment, managedIdentity, cache),
instanceUrl: new Uri(environment),
useUniqueInstance: true);
});
// Getting access token and caching it for 50 minutes
async static Task<string> GetToken(string environment, DefaultAzureCredential credential, IMemoryCache cache)
{
var accessToken = await cache.GetOrCreateAsync(environment, async (cacheEntry) =>
{
cacheEntry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(50);
var token = await credential.GetTokenAsync(new TokenRequestContext([$"{environment}/.default"]));
return token;
});
return accessToken.Token;
}
@jdweng Я не понимаю, что ты просишь меня показать? Я не использую строку подключения. Я обновил вопрос.
Вы отправляете HTTP-сообщение, в котором вы можете отправить только один запрос и получить один ответ при каждом соединении. Убедитесь, что вы получаете статус 200 OK с каждым сообщением, чтобы убедиться, что соединение закрывается правильно. Вы можете повторно использовать токен несколько раз (до истечения срока действия токена). Поэтому создайте токен, а затем цикл для каждого запроса. Либо вы получите ответ со статусом (200 ОК или ошибка 400/500), либо вы получите исключение о том, что соединение неожиданно закрылось. Запрос токена и запрос сообщения должны вернуть статус 200 OK.
Вы сказали, что это работает в Fresh API. Что такое целевая NET/CORE версия Fresh по сравнению с неудачной? В последнее время я видел много случаев сбоя Net 8. Вы пробовали Net 7?
Клиент службы внедряется как синглтон для повторного использования между несколькими запросами, а токен доступа кэшируется. Я делаю это неправильно здесь? Новый API также работает на .NET8. Я не тестировал его на .NET7 (пока).
Клиент можно использовать повторно, если вы вызываете конструктор с «новым». Я не могу сказать, как вы зацикливаетесь на опубликованном коде. Опубликованный код выглядит так, как будто он получает токен и только один клиент для каждого соединения вместо создания нового клиента для каждого соединения.
Не могли бы вы привести пример?
Пакет NuGet Microsoft.PowerPlatform.Dataverse.Client
версии 1.1.17 построен на .NET 6.0. Важно отметить, что он зависит от сборки System.Text.Json Version 7.0.0.0
, которая является частью .NET 7. Однако .NET 7 недоступна на серверах функций Azure. См. это обсуждение на GitHub и на форумах сообщества Power Apps.
Простым решением было бы создать проект функции Azure на базе .NET 6 и использовать пакет NuGet Microsoft.PowerPlatform.Dataverse.Client
версии 1.1.14 вместо последней.
Microsoft планирует добавить поддержку .NET 8 в конце этого года.
Я понизил проект функций до .NET 6 и сумел обработать 1504 запроса за 20 секунд. Ни один из них не был прерван, так что этот подход, похоже, работает. Спасибо! Я искал, но пропустил это обсуждение на форумах сообщества Power Apps. Однако я все еще сталкиваюсь с некоторыми удаленными исключениями, когда отменяю запрос до того, как функция вернула ответ. Я никогда не сталкивался с таким поведением в простом веб-API или в функции в целом.
Экземпляр ServiceClient
не является потокобезопасным. Его нельзя использовать одновременно несколькими запросами. Используйте ServiceClient.Clone()
, чтобы получить отдельные экземпляры IOrganizationService
.
См. строку подключения и тип аутентификации: Learn.microsoft.com/en-us/power-apps/developer/data-platform/…