Migrer depuis Orleans 3.x vers la version 7.0
Orleans 7.0 introduit plusieurs changements positifs, notamment des améliorations de l’hébergement, de la sérialisation personnalisée, de l’immuabilité et des abstractions de grains.
Migration
Les applications existantes qui utilisent des rappels, des flux ou la persistance des grains ne peuvent pas être facilement migrées vers Orleans 7.0 en raison des changements apportés à la façon dont Orleans identifie les grains et les flux. Nous prévoyons de proposer de manière incrémentielle un chemin de migration pour ces applications.
Les applications exécutant des versions précédentes d’Orleans ne peuvent pas être mises à niveau en douceur via une mise à niveau propagée vers Orleans 7.0. Une autre stratégie de mise à niveau doit donc être utilisée, par exemple le déploiement d’un nouveau cluster et la désactivation du cluster précédent. Orleans 7.0 change le protocole de transmission de manière incompatible, ce qui signifie que les clusters ne peuvent pas contenir un mélange d’hôtes Orleans 7,0 et d’hôtes exécutant des versions précédentes d’Orleans.
Nous avons évité de tels changements cassants depuis de nombreuses années, même entre les versions principales, alors pourquoi maintenant ? Il existe deux raisons essentielles : les identités et la sérialisation. En ce qui concerne les identités, les identités de grain et de flux sont désormais composées de chaînes, ce qui permet aux grains de coder correctement les informations de type générique, et aux flux d’être mappés plus facilement au domaine d’application. Les types de grain étaient identifiés à l’aide d’une structure de données complexe qui ne pouvait pas représenter les grains génériques, ce qui menait à des cas extrêmes. Les flux étaient identifiés par un espace de noms string
et une clé Guid, ce qui était difficile à mapper pour les développeurs à leur domaine d’application, même si cela était efficace. La sérialisation tolère désormais les versions, ce qui signifie que vous pouvez modifier vos types de certaines manières compatibles, en suivant un ensemble de règles, et avoir la certitude de pouvoir mettre à niveau votre application sans erreurs de sérialisation. Cela était particulièrement problématique quand les types d’application sont conservés dans les flux ou le stockage de grains. Les sections suivantes détaillent les principaux changements et les décrivent plus en détail.
Changements au niveau du package
Si vous mettez à niveau un projet vers Orleans 7,0, vous devez effectuer les actions suivantes :
- Tous les clients doivent référencer Microsoft.Orleans.Client.
- Tous les silos (serveurs) doivent référencer Microsoft.Orleans.Server.
- Tous les autres packages doivent référencer Microsoft.Orleans.Sdk.
- Les packages client et serveur incluent une référence à Microsoft.Orleans.Sdk.
- Supprimez toutes les références à
Microsoft.Orleans.CodeGenerator.MSBuild
etMicrosoft.Orleans.OrleansCodeGenerator.Build
.- Remplacez les utilisations de
KnownAssembly
par GenerateCodeForDeclaringAssemblyAttribute. - Le package
Microsoft.Orleans.Sdk
référence le package du générateur de source C# (Microsoft.Orleans.CodeGenerator
).
- Remplacez les utilisations de
- Supprimez toutes les références à
Microsoft.Orleans.OrleansRuntime
.- Le package Microsoft.Orleans.Server référence son package de remplacement,
Microsoft.Orleans.Runtime
.
- Le package Microsoft.Orleans.Server référence son package de remplacement,
- Supprimez les appels à
ConfigureApplicationParts
. Les composants d’application ont été supprimés. Le générateur de source C# pour Orleans est ajouté à tous les packages (client et serveur notamment) et génère automatiquement l’équivalent des composants d’application. - Remplacez les références à
Microsoft.Orleans.OrleansServiceBus
par Microsoft.Orleans.Streaming.EventHubs - Si vous utilisez des rappels, ajoutez une référence à Microsoft.Orleans.Reminders
- Si vous utilisez des flux, ajoutez une référence à Microsoft.Orleans.Streaming
Conseil
Tous les exemples Orleans ont été mis à niveau vers Orleans 7,0, et peuvent servir de référence pour identifier les changements apportés. Pour plus d’informations, consultez le problème Orleans n° 8035, qui détaille les changements apportés à chaque exemple.
Directives Orleansglobal using
Tous les projets Orleans référencent directement ou indirectement le package NuGet Microsoft.Orleans.Sdk
. Quand un projet Orleans est configuré pour activer les using implicites (par exemple <ImplicitUsings>enable</ImplicitUsings>
), les espaces de noms Orleans
et Orleans.Hosting
sont tous les deux utilisés de manière implicite. Cela signifie que le code de votre application n’a pas besoin de ces directives.
Pour plus d'informations, voir ImplicitUsings et dotnetorleans//Orleanssrc/.Sdk/build/Microsoft.Orleans.Sdk.targets.
Hosting
Le type ClientBuilder a été remplacé par une méthode d’extension UseOrleansClient sur IHostBuilder. Le type IHostBuilder
provient du package NuGet Microsoft.Extensions.Hosting. Cela signifie que vous pouvez ajouter un client Orleans à un hôte existant sans avoir à créer de conteneur d’injection de dépendances distinct. Le client se connecte au cluster au démarrage. À la fin de l’exécution de IHost.StartAsync, le client se connecte automatiquement. Les services ajoutés à IHostBuilder
démarrent dans l’ordre d’inscription. Ainsi, appeler UseOrleansClient
avant ConfigureWebHostDefaults permet de garantir le démarrage d’Orleans avant le démarrage d’ASP.NET Core par exemple, ce qui vous permet d’accéder au client à partir de votre application ASP.NET Core immédiatement.
Si vous souhaitez émuler le comportement précédent de ClientBuilder
, vous pouvez créer un HostBuilder
distinct et le configurer avec un client Orleans. IHostBuilder
peut disposer d’un client Orleans ou d’un silo Orleans configuré. Tous les silos inscrivent une instance de IGrainFactory et IClusterClient que l’application peut utiliser. Ainsi, la configuration d’un client de manière séparée est inutile et non prise en charge.
Changement de signature pour OnActivateAsync
et OnDeactivateAsync
Orleans autorise les grains à exécuter du code durant l’activation et la désactivation. Cela permet d’effectuer des tâches telles que la lecture de l’état du stockage ou la journalisation des messages relatifs au cycle de vie. Dans Orleans 7,0, la signature des méthodes de cycle de vie suivantes a changé :
- OnActivateAsync() accepte désormais un paramètre CancellationToken. Quand CancellationToken est annulé, le processus d’activation doit être abandonné.
- OnDeactivateAsync() accepte désormais un paramètre DeactivationReason et un paramètre
CancellationToken
. LeDeactivationReason
indique la raison pour laquelle l’activation est désactivée. Les développeurs sont censés utiliser ces informations à des fins de journalisation et de diagnostic. Quand leCancellationToken
est annulé, le processus de désactivation doit être effectué rapidement. Notez que dans la mesure où un hôte peut tomber en panne à tout moment, il n’est pas recommandé de se fier àOnDeactivateAsync
pour effectuer des actions importantes telles que la persistance de l’état critique.
Prenons l’exemple suivant d’un grain qui remplace ces nouvelles méthodes :
public sealed class PingGrain : Grain, IPingGrain
{
private readonly ILogger<PingGrain> _logger;
public PingGrain(ILogger<PingGrain> logger) =>
_logger = logger;
public override Task OnActivateAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("OnActivateAsync()");
return Task.CompletedTask;
}
public override Task OnDeactivateAsync(DeactivationReason reason, CancellationToken token)
{
_logger.LogInformation("OnDeactivateAsync({Reason})", reason);
return Task.CompletedTask;
}
public ValueTask Ping() => ValueTask.CompletedTask;
}
Grains OCT et IGrainBase
Les grains dans Orleans n’ont plus besoin d’hériter de la classe de base Grain ou d’une autre classe. Cette fonctionnalité est appelée grains OCT. Pour accéder aux méthodes d’extension suivantes :
- DeactivateOnIdle
- AsReference
- Cast
- GetPrimaryKey
- GetReminder
- GetReminders
- RegisterOrUpdateReminder
- UnregisterReminder
- GetStreamProvider
Votre grain doit implémenter IGrainBase ou hériter de Grain. Voici un exemple d’implémentation de IGrainBase
sur une classe de grain :
public sealed class PingGrain : IGrainBase, IPingGrain
{
public PingGrain(IGrainContext context) => GrainContext = context;
public IGrainContext GrainContext { get; }
public ValueTask Ping() => ValueTask.CompletedTask;
}
IGrainBase
définit également OnActivateAsync
et OnDeactivateAsync
avec les implémentations par défaut, ce qui permet à votre grain de participer à son cycle de vie, le cas échéant :
public sealed class PingGrain : IGrainBase, IPingGrain
{
private readonly ILogger<PingGrain> _logger;
public PingGrain(IGrainContext context, ILogger<PingGrain> logger)
{
_logger = logger;
GrainContext = context;
}
public IGrainContext GrainContext { get; }
public Task OnActivateAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("OnActivateAsync()");
return Task.CompletedTask;
}
public Task OnDeactivateAsync(DeactivationReason reason, CancellationToken token)
{
_logger.LogInformation("OnDeactivateAsync({Reason})", reason);
return Task.CompletedTask;
}
public ValueTask Ping() => ValueTask.CompletedTask;
}
Sérialisation
Le changement le plus lourd dans Orleans 7,0 est l’introduction du sérialiseur à tolérance de version. Ce changement a été apporté en raison de la tendance des applications à évoluer, ce qui a représenté un écueil important pour les développeurs, dans la mesure où le sérialiseur précédent ne pouvait pas tolérer l’ajout de propriétés à des types existants. D’un autre côté, le sérialiseur était flexible, il permettait aux développeurs de représenter la plupart des types .NET sans modification, en incluant notamment des fonctionnalités telles que les génériques, le polymorphisme et le suivi des références. Un remplacement était attendu depuis longtemps, mais les utilisateurs ont toujours besoin d’une représentation haute-fidélité de leurs types. Un sérialiseur de remplacement a donc été introduit dans Orleans 7,0. Il prend en charge la représentation haute-fidélité des types .NET tout en permettant aux types d’évoluer. Le nouveau sérialiseur est beaucoup plus efficace que le précédent, ce qui se traduit par un débit de bout en bout jusqu’à 170 % plus élevé.
Pour plus d’informations, consultez les articles suivants concernant Orleans 7,0 :
- Vue d’ensemble de la sérialisation
- Configuration de la sérialisation
- Personnalisation de la sérialisation
Identités de grain
Les grains ont chacun une identité unique qui comprend le type de grain et sa clé. Les versions précédentes d’Orleans utilisaient un type composé pour les GrainId
afin de prendre en charge les clés de grain suivantes :
La gestion des clés de grain implique une certaine complexité. Les identités de grain comprennent deux composants : un type et une clé. Le composant relatif au type comportait jusqu’à maintenant un code de type numérique, une catégorie et 3 octets d’informations de type générique.
Les identités de grain prennent désormais la forme type/key
, où type
et key
sont des chaînes. L’interface de clé de grain la plus couramment utilisée est IGrainWithStringKey. Cela simplifie considérablement le fonctionnement de l’identité de grain et améliore la prise en charge des types de grain génériques.
Les interfaces de grain sont également désormais représentées à l’aide d’un nom compréhensible par les utilisateurs, au lieu d’une combinaison associant un code de hachage et une représentation sous forme de chaîne des paramètres de type générique.
Le nouveau système est plus personnalisable, et ces personnalisations peuvent être pilotées par des attributs.
- GrainTypeAttribute(String) sur un grain
class
spécifie la partie Type de son ID de grain. - DefaultGrainTypeAttribute(String) sur un grain
interface
spécifie le Type du grain que IGrainFactory doit résoudre par défaut au moment de l’obtention d’une référence de grain. Par exemple, au moment de l’appel deIGrainFactory.GetGrain<IMyGrain>("my-key")
, la fabrique de grains retourne une référence au grain"my-type/my-key"
si l’attribut mentionné ci-dessus est spécifié pourIMyGrain
. - GrainInterfaceTypeAttribute(String) permet de remplacer le nom de l’interface. Si vous spécifiez explicitement un nom à l’aide de ce mécanisme, vous pouvez renommer le type d’interface sans rompre la compatibilité avec les références de grain existantes. Notez que votre interface doit également avoir le AliasAttribute dans ce cas, car son identité peut être sérialisée. Pour plus d’informations sur la spécification d’un alias de type, consultez la section sur la sérialisation.
Comme indiqué ci-dessus, le remplacement des noms d’interfaces et de classes de grain par défaut pour vos types vous permet de renommer les types sous-jacents sans casser la compatibilité avec les déploiements existants.
Identités de flux
Quand les flux Orleans ont été lancés pour la première fois, ils ne pouvaient être identifiés qu’à l’aide d’un Guid. Cela était efficace au niveau de l’allocation de mémoire, mais il était difficile pour les utilisateurs de créer des identités de flux significatives, ce qui nécessitait souvent un codage ou une indirection pour déterminer l’identité de flux appropriée à une finalité donnée.
Dans Orleans 7,0, les flux sont désormais identifiés à l’aide de chaînes. Le struct
Orleans.Runtime.StreamId contient trois propriétés : StreamId.Namespace, StreamId.Key et StreamId.FullKey. Ces valeurs de propriétés sont des chaînes codées au format UTF-8. Par exemple : StreamId.Create(String, String).
Remplacement de SimpleMessageStreams par BroadcastChannel
SimpleMessageStreams
(également appelé SMS) a été supprimé dans la version 7.0. SMS avait la même interface que Orleans.Providers.Streams.PersistentStreams, mais son comportement était très différent, car il reposait sur des appels directs de grain à grain. Pour éviter toute confusion, SMS a été supprimé et un nouveau remplacement appelé Orleans.BroadcastChannel a été introduit.
BroadcastChannel
prend uniquement en charge les abonnements implicites et peut être un remplacement direct dans ce cas. Si vous avez besoin d’abonnements explicites ou si vous devez utiliser l’interface PersistentStream
(par exemple vous utilisiez SMS au cours des tests alors que vous utilisiez EventHub
en production), MemoryStream
est le meilleur candidat pour vous.
BroadcastChannel
aura les mêmes comportements que SMS, alors que MemoryStream
se comportera comme les autres fournisseurs de flux. Prenons l’exemple d’utilisation du canal de diffusion suivant :
// Configuration
builder.AddBroadcastChannel(
"my-provider",
options => options.FireAndForgetDelivery = false);
// Publishing
var grainKey = Guid.NewGuid().ToString("N");
var channelId = ChannelId.Create("some-namespace", grainKey);
var stream = provider.GetChannelWriter<int>(channelId);
await stream.Publish(1);
await stream.Publish(2);
await stream.Publish(3);
// Simple implicit subscriber example
[ImplicitChannelSubscription]
public sealed class SimpleSubscriberGrain : Grain, ISubscriberGrain, IOnBroadcastChannelSubscribed
{
// Called when a subscription is added to the grain
public Task OnSubscribed(IBroadcastChannelSubscription streamSubscription)
{
streamSubscription.Attach<int>(
item => OnPublished(streamSubscription.ChannelId, item),
ex => OnError(streamSubscription.ChannelId, ex));
return Task.CompletedTask;
// Called when an item is published to the channel
static Task OnPublished(ChannelId id, int item)
{
// Do something
return Task.CompletedTask;
}
// Called when an error occurs
static Task OnError(ChannelId id, Exception ex)
{
// Do something
return Task.CompletedTask;
}
}
}
La migration vers MemoryStream
est plus facile, car seule la configuration doit changer. Tenez compte de la configuration MemoryStream
suivante :
builder.AddMemoryStreams<DefaultMemoryMessageBodySerializer>(
"in-mem-provider",
_ =>
{
// Number of pulling agent to start.
// DO NOT CHANGE this value once deployed, if you do rolling deployment
_.ConfigurePartitioning(partitionCount: 8);
});
OpenTelemetry
Le système de télémétrie a été mis à jour dans Orleans 7,0. Le système précédent a été supprimé au profit d’API .NET normalisées telles que les métriques .NET pour les métriques et ActivitySource pour le suivi.
Dans ce cadre, les packages Microsoft.Orleans.TelemetryConsumers.*
existants ont été supprimés. Nous comptons introduire un nouvel ensemble de packages pour rationaliser le processus d’intégration des métriques émises par Orleans dans la solution de monitoring de votre choix. Comme toujours, vos commentaires et contributions sont les bienvenus.
L’outil dotnet-counters
permet un monitoring des performances pour un monitoring de l’intégrité ad hoc et une investigation des performances de premier niveau. En ce qui concerne les compteurs Orleans, l’outil dotnet-counters permet d’en assurer le monitoring :
dotnet counters monitor -n MyApp --counters Microsoft.Orleans
De même, les métriques OpenTelemetry peuvent ajouter les compteurs Microsoft.Orleans
, comme le montre le code suivant :
builder.Services.AddOpenTelemetry()
.WithMetrics(metrics => metrics
.AddPrometheusExporter()
.AddMeter("Microsoft.Orleans"));
Pour activer le suivi distribué, configurez OpenTelemetry comme le montre le code suivant :
builder.Services.AddOpenTelemetry()
.WithTracing(tracing =>
{
tracing.SetResourceBuilder(ResourceBuilder.CreateDefault()
.AddService(serviceName: "ExampleService", serviceVersion: "1.0"));
tracing.AddAspNetCoreInstrumentation();
tracing.AddSource("Microsoft.Orleans.Runtime");
tracing.AddSource("Microsoft.Orleans.Application");
tracing.AddZipkinExporter(options =>
{
options.Endpoint = new Uri("http://localhost:9411/api/v2/spans");
});
});
Dans le code précédent, OpenTelemetry est configuré pour effectuer le monitoring de :
Microsoft.Orleans.Runtime
Microsoft.Orleans.Application
Pour propager l’activité, appelez AddActivityPropagation :
builder.Host.UseOrleans((_, clientBuilder) =>
{
clientBuilder.AddActivityPropagation();
});
Refactoriser les fonctionnalités du package principal en packages distincts
Dans Orleans 7,0, nous nous sommes efforcés de factoriser les extensions en packages distincts qui ne reposent pas sur Orleans.Core. Plus précisément, Orleans.Streaming, Orleans.Reminders et Orleans.Transactions ont été séparés de la partie principale. Cela signifie que ces packages sont entièrement de type paiement à l’utilisation, et qu’il n’existe aucun code dans la partie principale d’Orleans qui soit dédié à ces fonctionnalités. Cela permet de réduire la taille d’assembly et la surface de l’API principale, de simplifier la partie principale et d’améliorer les performances. En ce qui concerne les performances, les transactions dans Orleans nécessitaient du code qui était exécuté pour toutes les méthodes afin de permettre la coordination des transactions potentielles. Ce comportement a été remplacé.
Il s’agit d’un changement cassant au niveau de la compilation. Vous pouvez avoir du code existant qui interagit avec des rappels ou des flux en appelant des méthodes définies sur la classe de base Grain, mais qui sont désormais des méthodes d’extension. Les appels qui ne spécifient pas this
(par exemple GetReminders) doivent être mis à jour pour inclure this
(par exemple this.GetReminders()
), car les méthodes d’extension doivent être qualifiées. Il se produit une erreur de compilation si vous ne mettez pas à jour ces appels, et le changement de code nécessaire risque de ne pas être évident si vous ne savez pas ce qui a changé.
Client de transactions
Orleans 7,0 introduit une nouvelle abstraction pour la coordination des transactions, Orleans.ITransactionClient. Jusqu’à présent, les transactions pouvaient être uniquement coordonnées par des grains. Avec ITransactionClient
, disponible via l’injection de dépendances, les clients peuvent également coordonner les transactions sans avoir besoin d’un grain intermédiaire. L’exemple suivant permet de retirer des crédits d’un compte, et de les déposer sur un autre en une seule transaction. Ce code peut être appelé au sein d’un grain ou à partir d’un client externe qui a récupéré le ITransactionClient
à partir du conteneur d’injection de dépendances.
await transactionClient.RunTransaction(
TransactionOption.Create,
() => Task.WhenAll(from.Withdraw(100), to.Deposit(100)));
Pour les transactions coordonnées par le client, celui-ci doit ajouter les services nécessaires au moment de la configuration :
clientBuilder.UseTransactions();
L’exemple BankAccount illustre l’utilisation de ITransactionClient
. Pour plus d’informations, consultez Transactions Orleans.
Réentrance de la chaîne d’appels
Les grains sont monothread et traitent les requêtes une par une, du début à la fin, par défaut. En d’autres termes, les grains ne sont pas réentrants par défaut. L’ajout de ReentrantAttribute à une classe de grain permet de traiter plusieurs requêtes simultanément, de manière entrelacée, tout en étant toujours monothread. Cela peut être utile pour les grains qui ne contiennent aucun état interne ou qui effectuent de nombreuses opérations asynchrones, par exemple l’émission d’appels HTTP ou l’écriture dans une base de données. Vous devez prendre des précautions supplémentaires quand des requêtes peuvent s’entrelacer : il est possible que l’état d’un grain observé avant une instruction await
ait changé au moment où l’opération asynchrone se termine et où la méthode reprend son exécution.
Par exemple, le grain suivant représente un compteur. Il a été marqué Reentrant
, ce qui permet l’entrelacement de plusieurs appels. La méthode Increment()
doit incrémenter le compteur interne et retourner la valeur observée. Toutefois, dans la mesure où le corps de la méthode Increment()
observe l’état du grain avant un point await
et le met à jour ensuite, il est possible que plusieurs exécutions entrelacées de Increment()
entraînent un _value
inférieur au nombre total d’appels de Increment()
reçus. Il s’agit d’une erreur introduite par une utilisation inappropriée de la réentrance.
La suppression de ReentrantAttribute suffit à corriger le problème.
[Reentrant]
public sealed class CounterGrain : Grain, ICounterGrain
{
int _value;
/// <summary>
/// Increments the grain's value and returns the previous value.
/// </summary>
public Task<int> Increment()
{
// Do not copy this code, it contains an error.
var currentVal = _value;
await Task.Delay(TimeSpan.FromMilliseconds(1_000));
_value = currentVal + 1;
return currentValue;
}
}
Pour éviter de telles erreurs, les grains sont non réentrants par défaut. Cette situation présente un inconvénient : il en résulte un débit réduit pour les grains qui effectuent des opérations asynchrones dans leur implémentation, car les autres requêtes ne peuvent pas être traitées pendant que le grain attend la fin d’une opération asynchrone. Pour atténuer ce problème, Orleans propose plusieurs options permettant d’autoriser la réentrance dans certains cas :
- Pour une classe entière : le placement de ReentrantAttribute sur le grain permet à n’importe quelle requête envoyée au grain de s’entrelacer avec n’importe quelle autre requête.
- Pour un sous-ensemble de méthodes : le placement de AlwaysInterleaveAttribute sur la méthode d’interface de grain permet aux requêtes envoyées à cette méthode de s’entrelacer avec n’importe quelle autre requête ou d’être entrelacées par n’importe quelle autre requête.
- Pour un sous-ensemble de méthodes : le placement de ReadOnlyAttribute sur la méthode d’interface de grain permet aux requêtes envoyées à cette méthode de s’entrelacer avec n’importe quelle autre requête
ReadOnly
ou d’être entrelacées par n’importe quelle autre requêteReadOnly
. En ce sens, il s’agit d’une forme plus restreinte deAlwaysInterleave
. - Pour toute requête au sein d’une chaîne d’appels : RequestContext.AllowCallChainReentrancy() et <xref:Orleans.Runtime.RequestContext.SuppressCallChainReentrancy?displayProperty=nameWithType permettent d’accepter ou de refuser la nouvelle entrée des requêtes en aval dans le grain. Les appels retournent tous les deux une valeur qui doit être supprimée au moment de la sortie de la requête. L’utilisation appropriée est donc la suivante :
public Task<int> OuterCall(IMyGrain other)
{
// Allow call-chain reentrancy for this grain, for the duration of the method.
using var _ = RequestContext.AllowCallChainReentrancy();
await other.CallMeBack(this.AsReference<IMyGrain>());
}
public Task CallMeBack(IMyGrain grain)
{
// Because OuterCall allowed reentrancy back into that grain, this method
// will be able to call grain.InnerCall() without deadlocking.
await grain.InnerCall();
}
public Task InnerCall() => Task.CompletedTask;
La réentrance de la chaîne d’appels doit être activée par grain et par chaîne d’appels. Par exemple, prenons deux grains, le grain A et le grain B. Si le grain A active la réentrance de la chaîne d’appels avant d’appeler le grain B, le grain B peut rappeler le grain A dans cet appel. Toutefois, le grain A ne peut pas rappeler le grain B si le grain B n’a pas également activé la réentrance de la chaîne d’appels. Il s’agit d’une opération par grain, par chaîne d’appels.
Les grains peuvent également empêcher les informations de réentrance de la chaîne d’appels de circuler dans une chaîne d’appels à l’aide de using var _ = RequestContext.SuppressCallChainReentrancy()
. Cela empêche la réentrance des appels suivants.
Scripts de migration ADO.NET
Pour garantir la compatibilité ascendante avec le clustering, la persistance et les rappels Orleans qui reposent sur ADO.NET, vous avez besoin du script de migration SQL approprié :
Sélectionnez les fichiers de la base de données utilisée, et appliquez-les dans l’ordre.