Freigeben über


Serialisierung in Orleans

Es gibt im Allgemeinen zwei Arten der Serialisierung, die in Orleans verwendet werden:

  • Serialisierung von Grain-Aufrufen: Wird verwendet zur Serialisierung von Objekten, die an Grains übergeben werden und von Grains kommen.
  • Grain Storage Serialization: Wird verwendet, um Objekte in und von Speichersystemen zu serialisieren.

Der Großteil dieses Artikels konzentriert sich auf die Serialisierung des Kornaufrufs über das Serialisierungsframework, das in Orleans enthalten ist. Im Abschnitt "Grain Storage Serializers " wird die Serialisierung des Getreidespeichers erläutert.

Verwenden von Orleans-Serialisierung

Orleans enthält ein fortschrittliches und anpassungsfähiges Serialisierungs-Framework, das als Orleans.Serialization bezeichnet wird. Das in Orleans enthaltene Serialisierungsframework ist so konzipiert, dass es die folgenden Ziele erfüllt:

  • Hohe Leistung: Der Serialisierer ist für die Leistung ausgelegt und optimiert. Weitere Informationen finden Sie in dieser Präsentation.
  • High-fidelity: Der Serializer bildet den größten Teil des .NET-Typsystems getreu ab, einschließlich Unterstützung für Generika, Polymorphismus, Vererbungshierarchien, Objektidentität und zyklische Graphen. Zeiger werden nicht unterstützt, da sie nicht über Prozesse hinweg portierbar sind.
  • Flexibilität: Sie können den Serialisierer anpassen, um Bibliotheken von Drittanbietern zu unterstützen, indem Sie Surrogate erstellen oder an externe Serialisierungsbibliotheken wie System.Text.Json, Newtonsoft.Json und Google.Protobuf delegieren.
  • Versionstoleranz: Der Serialisierer ermöglicht es Anwendungstypen, sich im Laufe der Zeit zu entwickeln und zu unterstützen:
    • Hinzufügen und Entfernen von Mitgliedern
    • Unterklassen
    • Numerische Verbreiterung und Verengung (z. B. int zu/von long, float zu/von double)
    • Umbenennung von Typen

Die detailgetreue Darstellung von Typen ist bei Serialisierern ziemlich selten, weshalb einige Punkte näher erläutert werden sollten.

  1. Dynamische Typen und beliebige Polymorphismen: Orleans Erzwingt keine Einschränkungen für die typen, die in Getreideaufrufen übergeben werden, und behält die dynamische Natur des tatsächlichen Datentyps bei. Dies bedeutet zum Beispiel, dass wenn eine Methode in einer Grain-Schnittstelle deklariert ist, IDictionary zu akzeptieren, aber zur Laufzeit der Absender ein SortedDictionary<TKey,TValue> übergibt, der Empfänger tatsächlich ein SortedDictionary erhält (auch wenn die "statische Vereinbarung"/Grain-Schnittstelle dieses Verhalten nicht angegeben hat).

  2. Aufrechterhaltung der Objektidentität: Wenn dasselbe Objekt mehrmals in den Argumenten eines Kornaufrufs übergeben wird oder indirekt von den Argumenten aus mehrfach darauf verwiesen wird, Orleans serialisiert es nur einmal. Auf der Empfängerseite werden alle Verweise ordnungsgemäß wiederhergestellt, Orleans sodass zwei Zeiger auf dasselbe Objekt nach der Deserialisierung weiterhin auf dasselbe Objekt zeigen. Das Beibehalten der Objektidentität ist in Szenarien wie der folgenden wichtig: Stellen Sie sich vor, dass "Grain A" ein Wörterbuch mit 100 Einträgen an das Körnen B sendet, und 10 Schlüssel im Wörterbuch verweisen auf dasselbe Objekt, objauf der A-Seite. Ohne Beibehaltung der Objektidentität würde B ein Wörterbuch mit 100 Einträgen erhalten, von denen diese 10 Schlüssel auf 10 verschiedene Klone von obj verweisen. Bei beibehaltener Objektidentität sieht das Wörterbuch auf der Seite von B genau wie auf der A-Seite aus, wobei diese 10 Schlüssel auf ein einzelnes Objekt objzeigen. Beachten Sie, dass die Sortierung von Werten in Wörterbüchern und Hashsätzen (z. B.) nicht beibehalten wird, da standardmäßige Zeichenfolgen-Hashcodeimplementierungen in .NET pro Prozess zufällig sind.

Zur Unterstützung der Versionstoleranz muss der Serialisierer explizit festlegen, welche Typen und Mitglieder serialisiert werden. Wir haben versucht, dies so schmerzlos wie möglich zu machen. Markieren Sie alle serialisierbaren Typen mit Orleans.GenerateSerializerAttribute, um Orleans anzuweisen, Serialisierungscode für Ihren Typ zu generieren. Nachdem Sie dies getan haben, können Sie den enthaltenen Codefix verwenden, um die erforderlichen Orleans.IdAttribute-Elemente zu den serialisierbaren Mitgliedern in Ihren Typen hinzuzufügen, wie hier gezeigt.

Ein animiertes Bild der verfügbaren Codekorrektur, die vorgeschlagen und auf GenerateSerializerAttribute angewendet wird, wenn der enthaltende Typ keine IdAttribute-Instanzen für seine Member enthält.

Hier sehen Sie ein Beispiel für einen serialisierbaren Typ in Orleans, der veranschaulicht, wie die Attribute angewendet werden.

[GenerateSerializer]
public class Employee
{
    [Id(0)]
    public string Name { get; set; }
}

Orleans unterstützt vererbung und serialisiert die einzelnen Ebenen in der Hierarchie separat, sodass sie unterschiedliche Member-IDs aufweisen können.

[GenerateSerializer]
public class Publication
{
    [Id(0)]
    public string Title { get; set; }
}

[GenerateSerializer]
public class Book : Publication
{
    [Id(0)]
    public string ISBN { get; set; }
}

Beachten Sie im vorangehenden Code, dass sowohl Publication als auch Book Mitglieder mit [Id(0)] haben, obwohl Book von Publication abgeleitet wird. Dies ist die empfohlene Praxis in Orleans, da Mitgliedsidentifikatoren auf die Ebene der Vererbung und nicht auf den Typ als Ganzes begrenzt sind. Sie können Mitglieder aus Publication und Book unabhängig voneinander hinzufügen und entfernen, aber Sie können nach der Bereitstellung der Anwendung keine neue Basisklasse in die Hierarchie einfügen, ohne besondere Rücksichtnahme.

Orleans unterstützt auch serialisierende Typen mit internal-, private- und readonly-Membern, z. B. in diesem Beispieltyp:

[GenerateSerializer]
public struct MyCustomStruct
{
    public MyCustom(int intProperty, int intField)
    {
        IntProperty = intProperty;
        _intField = intField;
    }

    [Id(0)]
    public int IntProperty { get; }

    [Id(1)] private readonly int _intField;
    public int GetIntField() => _intField;

    public override string ToString() => $"{nameof(_intField)}: {_intField}, {nameof(IntProperty)}: {IntProperty}";
}

Standardmäßig serialisiert Orleans Ihren Typ, indem er den vollständigen Namen codiert. Sie können dies außer Kraft setzen, indem Sie Orleans.AliasAttribute hinzufügen. Dies führt dazu, dass Ihr Typ serialisiert wird, wobei ein Name verwendet wird, der unabhängig von der Umbenennung der zugrunde liegenden Klasse oder deren Verschiebung zwischen Assemblys stabil bleibt. Aliase von Typen gelten global, weshalb in einer Anwendung nicht zwei Aliase mit demselben Wert möglich sind. Bei generischen Typen muss der Aliaswert die Anzahl der generischen Parameter enthalten, denen ein Backtick vorangestellt ist; Beispielsweise MyGenericType<T, U> könnte der Alias [Alias("mytype`2")]enthalten.

Serialisieren von record-Typen

Elemente, die im primären Konstruktor eines Datensatzes definiert sind, weisen standardmäßig implizite IDs auf. Anders ausgedrückt: Orleans unterstützt die Serialisierung von record-Typen. Dies bedeutet, dass Sie die Parameterreihenfolge für einen bereits bereitgestellten Typ nicht ändern können, da die Kompatibilität mit früheren Versionen Ihrer Anwendung (in einem rollierenden Upgradeszenario) und mit serialisierten Instanzen dieses Typs in Speicher und Datenströmen unterbrochen wird. Im Körper eines Datensatztyps definierte Member geben Identitäten nicht für die primären Konstruktorparameter frei.

[GenerateSerializer]
public record MyRecord(string A, string B)
{
    // ID 0 won't clash with A in primary constructor as they don't share identities
    [Id(0)]
    public string C { get; init; }
}

Wenn Sie nicht möchten, dass die primären Konstruktorparameter automatisch als serialisierbare Felder eingeschlossen werden, verwenden Sie [GenerateSerializer(IncludePrimaryConstructorParameters = false)].

Ersatztypen für die Serialisierung fremder Typen

Manchmal müssen Sie möglicherweise Datentypen zwischen Grains übergeben, über die Sie keinen vollen Zugriff haben. In diesen Fällen kann die manuelle Konvertierung in und aus einem benutzerdefinierten Typ innerhalb Ihres Anwendungscodes unpraktisch sein. Orleans bietet eine Lösung für diese Situationen: Ersatztypen. Ersatztypen werden anstelle ihres Zieltyps serialisiert und verfügen über Funktionen zum Konvertieren in und aus dem Zieltyp. Betrachten Sie das folgende Beispiel eines fremden Typs und eines entsprechenden Ersatztyps und Konverters:

// This is the foreign type, which you do not have control over.
public struct MyForeignLibraryValueType
{
    public MyForeignLibraryValueType(int num, string str, DateTimeOffset dto)
    {
        Num = num;
        String = str;
        DateTimeOffset = dto;
    }

    public int Num { get; }
    public string String { get; }
    public DateTimeOffset DateTimeOffset { get; }
}

// This is the surrogate which will act as a stand-in for the foreign type.
// Surrogates should use plain fields instead of properties for better performance.
[GenerateSerializer]
public struct MyForeignLibraryValueTypeSurrogate
{
    [Id(0)]
    public int Num;

    [Id(1)]
    public string String;

    [Id(2)]
    public DateTimeOffset DateTimeOffset;
}

// This is a converter that converts between the surrogate and the foreign type.
[RegisterConverter]
public sealed class MyForeignLibraryValueTypeSurrogateConverter :
    IConverter<MyForeignLibraryValueType, MyForeignLibraryValueTypeSurrogate>
{
    public MyForeignLibraryValueType ConvertFromSurrogate(
        in MyForeignLibraryValueTypeSurrogate surrogate) =>
        new(surrogate.Num, surrogate.String, surrogate.DateTimeOffset);

    public MyForeignLibraryValueTypeSurrogate ConvertToSurrogate(
        in MyForeignLibraryValueType value) =>
        new()
        {
            Num = value.Num,
            String = value.String,
            DateTimeOffset = value.DateTimeOffset
        };
}

Im vorhergehenden Code:

  • MyForeignLibraryValueType ist ein Typ außerhalb Ihrer Kontrolle, der in einer verwendenden Bibliothek definiert ist.
  • MyForeignLibraryValueTypeSurrogate ist eine Ersatztypzuordnung zu MyForeignLibraryValueType.
  • RegisterConverterAttribute Gibt an, dass MyForeignLibraryValueTypeSurrogateConverter als Konverter fungiert, der zwischen den beiden Typen zugeordnet werden soll. Die Klasse implementiert die IConverter<TValue,TSurrogate> Schnittstelle.

Orleans unterstützt die Serialisierung von Typen in Typhierarchien (Typen, die von anderen Typen abgeleitet werden). Wenn ein fremder Typ in einer Typhierarchie (z. B. als Basisklasse für einen Ihrer eigenen Typen) angezeigt wird, müssen Sie die Orleans.IPopulator<TValue,TSurrogate> Schnittstelle zusätzlich implementieren. Betrachten Sie das folgenden Beispiel:

// The foreign type is not sealed, allowing other types to inherit from it.
public class MyForeignLibraryType
{
    public MyForeignLibraryType() { }

    public MyForeignLibraryType(int num, string str, DateTimeOffset dto)
    {
        Num = num;
        String = str;
        DateTimeOffset = dto;
    }

    public int Num { get; set; }
    public string String { get; set; }
    public DateTimeOffset DateTimeOffset { get; set; }
}

// The surrogate is defined as it was in the previous example.
[GenerateSerializer]
public struct MyForeignLibraryTypeSurrogate
{
    [Id(0)]
    public int Num;

    [Id(1)]
    public string String;

    [Id(2)]
    public DateTimeOffset DateTimeOffset;
}

// Implement the IConverter and IPopulator interfaces on the converter.
[RegisterConverter]
public sealed class MyForeignLibraryTypeSurrogateConverter :
    IConverter<MyForeignLibraryType, MyForeignLibraryTypeSurrogate>,
    IPopulator<MyForeignLibraryType, MyForeignLibraryTypeSurrogate>
{
    public MyForeignLibraryType ConvertFromSurrogate(
        in MyForeignLibraryTypeSurrogate surrogate) =>
        new(surrogate.Num, surrogate.String, surrogate.DateTimeOffset);

    public MyForeignLibraryTypeSurrogate ConvertToSurrogate(
        in MyForeignLibraryType value) =>
        new()
    {
        Num = value.Num,
        String = value.String,
        DateTimeOffset = value.DateTimeOffset
    };

    public void Populate(
        in MyForeignLibraryTypeSurrogate surrogate, MyForeignLibraryType value)
    {
        value.Num = surrogate.Num;
        value.String = surrogate.String;
        value.DateTimeOffset = surrogate.DateTimeOffset;
    }
}

// Application types can inherit from the foreign type, assuming they're not sealed
// since Orleans knows how to serialize it.
[GenerateSerializer]
public sealed class DerivedFromMyForeignLibraryType : MyForeignLibraryType
{
    public DerivedFromMyForeignLibraryType() { }

    public DerivedFromMyForeignLibraryType(
        int intValue, int num, string str, DateTimeOffset dto) : base(num, str, dto)
    {
        IntValue = intValue;
    }

    [Id(0)]
    public int IntValue { get; set; }
}

Regeln zur Versionsverwaltung

Die Versionstoleranz wird unterstützt, wenn Sie beim Ändern von Typen eine Reihe von Regeln befolgen. Wenn Sie mit Systemen wie Google-Protokollpuffern (Protobuf) vertraut sind, sind diese Regeln vertraut.

Zusammengesetzte Typen (class & struct)

  • Die Vererbung wird unterstützt, aber das Ändern der Vererbungshierarchie eines Objekts wird nicht unterstützt. Sie können die Basisklasse einer Klasse nicht hinzufügen, ändern oder entfernen.
  • Mit Ausnahme einiger numerischer Typen, die unten im Abschnitt "Numerics " beschrieben werden, können Sie feldtypen nicht ändern.
  • Sie können Felder an einem beliebigen Punkt in einer Vererbungshierarchie hinzufügen oder entfernen.
  • Feld-IDs können nicht geändert werden.
  • Feld-IDs müssen für jede Ebene in einer Typhierarchie eindeutig sein, können jedoch zwischen Basisklassen und Unterklassen wiederverwendet werden. Beispielsweise kann eine Base Klasse ein Feld mit ID 0deklarieren, und eine Sub : Base Klasse kann ein anderes Feld mit derselben ID deklarieren. 0

Numerik

  • Sie können die Signiertheit eines numerischen Felds nicht ändern.
    • Konvertierungen zwischen int & uint sind ungültig.
  • Sie können die Breite eines numerischen Felds ändern.
    • Z.B. werden Konvertierungen von int zu long oder ulong zu ushort unterstützt.
    • Konvertierungen, die die Breite einschränken, lösen eine Ausnahme aus, wenn der Laufzeitwert des Felds einen Überlauf verursacht.
    • Konvertierung von ulong in ushort wird nur unterstützt, wenn der Laufzeitwert kleiner als ushort.MaxValueist.
    • Konvertierungen von double in float werden nur unterstützt, wenn der Laufzeitwert zwischen float.MinValue und float.MaxValue liegt.
    • Ebenso für decimal, das einen engeren Bereich als double und float aufweist.

Kopierer

Orleans fördert standardmäßig die Sicherheit, einschließlich des Schutzes vor bestimmten Klassen von Nebenläufigkeitsfehlern. Insbesondere werden Objekte, die in Grain-Aufrufen standardmäßig übergeben werden, Orleans sofort kopiert. Orleans. Die Serialisierung erleichtert dieses Kopieren. Wenn Sie Orleans.CodeGeneration.GenerateSerializerAttribute auf einen Typ anwenden, werden durch Orleans auch Kopierer für diesen Typ generiert. Orleans verhindert das Kopieren von Typen oder einzelnen Membern, die mit ImmutableAttributemarkiert sind. Weitere Informationen finden Sie unter Serialisierung unveränderlicher Typen in Orleans.

Bewährte Methoden für die Serialisierung

  • Versehen Sie Ihre Typen mithilfe des Attributs [Alias("my-type")] mit Aliasen. Typen mit Aliasen können ohne Störung der Kompatibilität umbenannt werden.

  • Ändern Sie nicht ein record-Element in ein reguläres class-Element oder umgekehrt. Datensätze und Klassen werden nicht identisch dargestellt, da Datensätze zusätzlich zu regulären Membern über primäre Konstruktoren verfügen; daher sind sie nicht austauschbar.

  • ❌Fügen Sie einer vorhandenen Typhierarchie für einen serialisierbaren Typ keine neuen Typen hinzu. Sie dürfen einem vorhandenen Typ keine neue Basisklasse hinzufügen. Sie können einem vorhandenen Typ eine neue Basisklasse sicher hinzufügen.

  • Ersetzen Sie die Verwendung von SerializableAttribute durch GenerateSerializerAttribute und die entsprechenden Deklarationen von IdAttribute.

  • Starten Sie alle Member-IDs bei Null für jeden Typ. IDs in einer Unterklasse und deren Basisklasse können sich sicher überlappen. Beide Eigenschaften im folgenden Beispiel haben IDs gleich 0.

    [GenerateSerializer]
    public sealed class MyBaseClass
    {
        [Id(0)]
        public int MyBaseInt { get; set; }
    }
    
    [GenerateSerializer]
    public sealed class MySubClass : MyBaseClass
    {
        [Id(0)]
        public int MyBaseInt { get; set; }
    }
    
  • Erweitern Sie numerische Membertypen nach Bedarf. Sie können sbyte in short in int in long erweitern.

    • Sie können numerische Membertypen einschränken, aber es führt zu einer Laufzeitausnahme, wenn beobachtete Werte nicht korrekt durch den eingeschränkten Typ dargestellt werden können. Es kann beispielsweise int.MaxValue nicht durch ein short Feld dargestellt werden, sodass das Eingrenzen eines int Felds auf short zu einer Laufzeitausnahme führen kann, wenn ein solcher Wert gefunden wird.
  • Ändern Sie nicht die Vorzeichenbehaftung eines numerischen Typelements. Sie dürfen z. B. den Typ eines Mitglieds nicht von uint in int oder von int in uint ändern.

Serialisierungsmodule für Grainspeicher

Orleans enthält ein vom Anbieter gestütztes Persistenzmodell für Grains, auf das Sie über die State-Eigenschaft zugreifen oder indem Sie mindestens einen IPersistentState<TState>-Wert in Ihr Grain einfügen. Vor Orleans 7.0 hatte jeder Anbieter einen anderen Mechanismus zum Konfigurieren der Serialisierung. In Orleans 7.0 gibt es jetzt eine allgemeine Serialisierungsschnittstelle für den Kornzustand, IGrainStorageSerializerdie eine konsistente Möglichkeit zum Anpassen der Zustands serialisierung für jeden Anbieter bietet. Unterstützte Speicheranbieter implementieren ein Muster, bei dem die IStorageProviderSerializerOptions.GrainStorageSerializer Eigenschaft für die Optionsklasse des Anbieters festgelegt wird, z. B.:

Die Serialisierung des Grainspeichers wird derzeit standardmäßig auf Newtonsoft.Jsonfestgelegt, um den Zustand zu serialisieren. Sie können diese ersetzen, indem Sie diese Eigenschaft zur Konfigurationszeit ändern. Im folgenden Beispiel wird dies mithilfe von OptionsBuilder<TOptions> veranschaulicht:

siloBuilder.AddAzureBlobGrainStorage(
    "MyGrainStorage",
    (OptionsBuilder<AzureBlobStorageOptions> optionsBuilder) =>
    {
        optionsBuilder.Configure<IMySerializer>(
            (options, serializer) => options.GrainStorageSerializer = serializer);
    });

Weitere Informationen finden Sie unter OptionsBuilder-API.

Orleans verfügt über ein fortschrittliches und erweiterbares Serialisierungsframework. Orleans serialisiert Datentypen, die in Grain-Anfrage- und Antwortnachrichten übergeben werden, sowie Grain-persistente Zustandsobjekte. Im Rahmen dieses Frameworks Orleans generiert automatisch Serialisierungscode für diese Datentypen. Zusätzlich zur Generierung einer effizienteren Serialisierung/Deserialisierung für Typen, die bereits .NET-serialisierbar sind, versucht Orleans auch, Serialisierer für Typen zu generieren, die in Grain-Interfaces verwendet werden und nicht .NET-serialisierbar sind. Das Framework enthält außerdem eine Reihe effizienter integrierter Serialisierungsmodule für häufig verwendete Typen: Listen, Wörterbücher, Zeichenfolgen, Primitive, Arrays usw.

Zwei wichtige Features des OrleansSerialisierers unterscheiden sich von vielen anderen Serialisierungsframeworks von Drittanbietern: dynamische Typen/beliebige Polymorphismus und Objektidentität.

  1. Dynamische Typen und beliebige Polymorphismen: Orleans Erzwingt keine Einschränkungen für die typen, die in Getreideaufrufen übergeben werden, und behält die dynamische Natur des tatsächlichen Datentyps bei. Dies bedeutet zum Beispiel, dass wenn eine Methode in einer Grain-Schnittstelle deklariert ist, IDictionary zu akzeptieren, aber zur Laufzeit der Absender ein SortedDictionary<TKey,TValue> übergibt, der Empfänger tatsächlich ein SortedDictionary erhält (auch wenn die "statische Vereinbarung"/Grain-Schnittstelle dieses Verhalten nicht angegeben hat).

  2. Aufrechterhaltung der Objektidentität: Wenn dasselbe Objekt mehrmals in den Argumenten eines Kornaufrufs übergeben wird oder indirekt von den Argumenten aus mehrfach darauf verwiesen wird, Orleans serialisiert es nur einmal. Auf der Empfängerseite werden alle Verweise ordnungsgemäß wiederhergestellt, Orleans sodass zwei Zeiger auf dasselbe Objekt nach der Deserialisierung weiterhin auf dasselbe Objekt zeigen. Das Beibehalten der Objektidentität ist in Szenarien wie der folgenden wichtig: Stellen Sie sich vor, dass "Grain A" ein Wörterbuch mit 100 Einträgen an das Körnen B sendet, und 10 Schlüssel im Wörterbuch verweisen auf dasselbe Objekt, objauf der A-Seite. Ohne Beibehaltung der Objektidentität würde B ein Wörterbuch mit 100 Einträgen erhalten, von denen diese 10 Schlüssel auf 10 verschiedene Klone von obj verweisen. Bei beibehaltener Objektidentität sieht das Wörterbuch auf der Seite von B genau wie auf der A-Seite aus, wobei diese 10 Schlüssel auf ein einzelnes Objekt objzeigen.

Der standardmäßige .NET-Binär-Serializer bietet die oben genannten beiden Verhaltensweisen. Daher war es wichtig, auch dieses Standard- und vertraute Verhalten in Orleans zu unterstützen.

Generierte Serialisierungsmodule

Orleans verwendet die folgenden Regeln, um zu entscheiden, welche Serialisierer generiert werden sollen:

  1. Scannen Sie alle Typen in allen Assemblys, die auf die Kernbibliothek Orleans verweisen.
  2. Generieren Sie aus diesen Assemblys Serialisierer für Typen, auf die direkt in den Signaturen der Grainschnittstellenmethode oder Zustandsklassensignaturen verwiesen wird, oder für jeden Typ, der mit SerializableAttribute markiert ist.
  3. Darüber hinaus kann ein Grainschnittstellen- oder Implementierungsprojekt auf beliebige Typen für die Serialisierungsgenerierung verweisen, indem KnownTypeAttribute- oder KnownAssemblyAttribute-Assemblyebenenattribute hinzugefügt werden. Diese weisen den Codegenerator an, Serialisierer für bestimmte Typen oder alle berechtigten Typen innerhalb einer Assembly zu generieren. Weitere Informationen zu Attributen auf Assemblyebene finden Sie unter Anwenden von Attributen auf Assemblyebene.

Fallbackserialisierung

Orleans unterstützt die Übertragung beliebiger Typen zur Laufzeit. Daher kann der integrierte Codegenerator nicht den gesamten Satz von Typen ermitteln, die vorab übertragen werden. Darüber hinaus können für bestimmte Typen keine Serialisierer erzeugt werden, weil sie unzugänglich sind (z. B. private) oder über unzugängliche Felder verfügen (z. B. readonly). Daher ist die serielle Verarbeitung bei Bedarf von Typen erforderlich, die nicht vorhergesehen wurden oder für die keine Serialisierer im Voraus erzeugt werden konnten. Das für diese Typen zuständige Serialisierungsmodul wird als Fallbackserialisierungsmodul bezeichnet. Orleans bietet zwei Fallbackserialisierungsmodule:

  • Orleans.Serialization.BinaryFormatterSerializer, das BinaryFormatter von .NET verwendet und
  • Orleans.Serialization.ILBasedSerializer, das zur Laufzeit CIL-Anweisungen ausgibt, um Serialisierungsmodule zu erstellen, die das Serialisierungsframework von Orleans zur Serialisierung der einzelnen Felder nutzen. Dies bedeutet, wenn ein unzugänglicher MyPrivateType-Typ ein MyType-Feld enthält, das ein benutzerdefiniertes Serialisierungsmodul aufweist, wird dieses benutzerdefinierte Serialisierungsmodul verwendet, um ihn zu serialisieren.

Konfigurieren Sie den Fallback-Serializer mithilfe der FallbackSerializationProvider-Eigenschaft sowohl auf ClientConfiguration (Client) als auch auf GlobalConfiguration (Silos).

// Client configuration
var clientConfiguration = new ClientConfiguration();
clientConfiguration.FallbackSerializationProvider =
    typeof(FantasticSerializer).GetTypeInfo();

// Global configuration
var globalConfiguration = new GlobalConfiguration();
globalConfiguration.FallbackSerializationProvider =
    typeof(FantasticSerializer).GetTypeInfo();

Alternativ können Sie den Fallback-Serialisierungsanbieter in der XML-Konfiguration angeben.

<Messaging>
    <FallbackSerializationProvider
        Type="GreatCompany.FantasticFallbackSerializer, GreatCompany.SerializerAssembly"/>
</Messaging>

BinaryFormatterSerializer ist das standardmäßige Fallbackserialisierungsmodul.

Warnung

Die binäre Serialisierung mit BinaryFormatter kann gefährlich sein. Weitere Informationen finden Sie im BinaryFormatter-Sicherheitshandbuch und im BinaryFormatter-Migrationshandbuch.

Ausnahmeserialisierung

Ausnahmen werden mithilfe des Fallbackserialisierungsmoduls serialisiert. Bei der Standardkonfiguration BinaryFormatter handelt es sich um den Fallback serializer. Daher müssen Sie dem ISerializable-Muster folgen, um die korrekte Serialisierung aller Eigenschaften in einem Ausnahmetyp sicherzustellen.

Nachfolgend sehen Sie ein Beispiel für einen Ausnahmetyp mit ordnungsgemäß implementierter Serialisierung:

[Serializable]
public class MyCustomException : Exception
{
    public string MyProperty { get; }

    public MyCustomException(string myProperty, string message)
        : base(message)
    {
        MyProperty = myProperty;
    }

    public MyCustomException(string transactionId, string message, Exception innerException)
        : base(message, innerException)
    {
        MyProperty = transactionId;
    }

    // Note: This is the constructor called by BinaryFormatter during deserialization
    public MyCustomException(SerializationInfo info, StreamingContext context)
        : base(info, context)
    {
        MyProperty = info.GetString(nameof(MyProperty));
    }

    // Note: This method is called by BinaryFormatter during serialization
    public override void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        base.GetObjectData(info, context);
        info.AddValue(nameof(MyProperty), MyProperty);
    }
}

Bewährte Methoden für die Serialisierung

Die Serialisierung dient in Orleans vor allem zwei Zwecken:

  1. Als Übertragungsformat zum Übertragen von Daten zwischen Grain und Clients zur Laufzeit.
  2. Als Speicherformat für die Aufbewahrung langlebiger Daten zum späteren Abruf.

Die von Orleans generierten Serialisierungsmodule sind aufgrund ihrer Flexibilität, Leistung und Vielseitigkeit für den ersten Zweck geeignet. Sie sind nicht so geeignet für den zweiten Zweck, da sie nicht explizit versionstolerant sind. Es wird empfohlen, einen versionstoleranten Serialisierer( z. B. Protokollpuffer) für persistente Daten zu konfigurieren. Protokollpuffer werden über Orleans.Serialization.ProtobufSerializer aus dem NuGet-Paket Microsoft.Orleans.OrleansGoogleUtils unterstützt Befolgen Sie die bewährten Methoden für den ausgewählten Serialisierer, um die Versionstoleranz sicherzustellen. Konfigurieren Sie Serialisierer von Drittanbietern mithilfe der SerializationProviders Konfigurationseigenschaft, wie oben beschrieben.