Not
Åtkomst till denna sida kräver auktorisation. Du kan prova att logga in eller byta katalog.
Åtkomst till denna sida kräver auktorisation. Du kan prova att byta katalog.
Det finns i stort sett två typer av serialisering som används i Orleans:
- Serialisering av grainanrop: Används för att serialisera objekt som skickas till och från grainen.
- Datakorn-serialisering: Används för att serialisera objekt till och från lagringssystem.
Merparten av den här artikeln fokuserar på grainanrop via serialiseringsramverket som ingår i Orleans. I avsnittet Grain Storage serializers beskrivs serialisering av kornlagring.
Använd Orleans serialisering
Orleans innehåller ett avancerat och utökningsbart serialiseringsramverk som kallas Orleans.Serialization. Serialiseringsramverket som ingår i Orleans är utformat för att uppfylla följande mål:
- Höga prestanda: Serialiseraren är utformad och optimerad för prestanda. Mer information finns i den här presentationen.
- Hög återgivning: Serialiseraren representerar troget det mesta av . NET:s typsystem, inklusive stöd för generiska objekt, polymorfism, arvshierarkier, objektidentitet och cykliska grafer. Pekare stöds inte eftersom de inte är portabla mellan processer.
- Flexibilitet: Du kan anpassa serialiseraren så att den stöder bibliotek från tredje part genom att skapa surrogater eller delegera till externa serialiseringsbibliotek som System.Text.Json, Newtonsoft.Json och Google.Protobuf.
-
Versionstolerans: Serialiseraren gör att programtyper kan utvecklas över tid, med stöd för:
- Lägga till och ta bort medlemmar
- Subklassning
- Numerisk breddning och förträngning (t.ex.
inttill/frånlong,floattill/fråndouble) - Byta namn på typer
Hög återgivning av typer är ganska ovanligt för serialiserare, så vissa punkter motiverar ytterligare förklaring:
Dynamiska typer och godtycklig polymorfism: Orleans tillämpar inte begränsningar för de typer som skickas i kornanrop och upprätthåller den faktiska datatypens dynamiska karaktär. Det innebär till exempel att om en metod i ett gränssnitt har deklarerats att acceptera IDictionary, men vid körning skickar avsändaren en SortedDictionary<TKey,TValue>, får mottagaren faktiskt en
SortedDictionary(även om gränssnittet "static contract"/grain inte angav det här beteendet).Upprätthållande av objektidentitet: Om samma objekt skickas flera gånger som argument i ett samtal eller om det pekas indirekt på mer än en gång från argumenten, serialiseras det endast en gång. På mottagarsidan Orleans återställer alla referenser korrekt så att två pekare till samma objekt fortfarande pekar på samma objekt efter deserialisering. Det är viktigt att bevara objektidentiteten i scenarier som följande: Imagine grain A skickar en ordlista med 100 poster till korn B och 10 nycklar i ordlistan pekar på samma objekt,
obj, på A:s sida. Utan att bevara objektidentiteten skulle B få en ordlista med 100 poster med de 10 nycklarna som pekar på 10 olika kloner avobj. Med objektidentiteten bevarad ser ordlistan på B:s sida exakt ut som på A:s sida, med de 10 nycklarna som pekar på ett enda objektobj. Observera att eftersom standardimplementeringar av stränghashkod i .NET randomiseras per process kanske inte ordningen på värden i ordlistor och hashuppsättningar (till exempel) bevaras.
För att stödja versionstolerans kräver serialiseraren att du är explicit om vilka typer och medlemmar som serialiseras. Vi har försökt göra det här så smärtfritt som möjligt. Markera alla serialiserbara typer med Orleans.GenerateSerializerAttribute för att instruera Orleans att generera serialiserarkod för din typ. När du har gjort detta kan du använda den inkluderade kodkorrigeringen för att addera det nödvändiga Orleans.IdAttribute i de serialiserbara medlemmarna i dina typer, vilket visas här:
Här är ett exempel på en serialiserbar typ i Orleans, som visar hur du använder attributen.
[GenerateSerializer]
public class Employee
{
[Id(0)]
public string Name { get; set; }
}
Orleans stöder arv och serialiserar de enskilda lagren i hierarkin separat, så att de kan ha distinkta medlems-ID:t.
[GenerateSerializer]
public class Publication
{
[Id(0)]
public string Title { get; set; }
}
[GenerateSerializer]
public class Book : Publication
{
[Id(0)]
public string ISBN { get; set; }
}
I föregående kod bör du notera att både Publication och Book har medlemmar med [Id(0)], även om Book härstammar från Publication. Detta är den rekommenderade metoden eftersom Orleans medlemsidentifierare är begränsade till arvsnivån, inte typen som helhet. Du kan lägga till och ta bort medlemmar från Publication och Book oberoende av varandra, men du kan inte infoga en ny basklass i hierarkin när programmet har distribuerats utan särskild hänsyn.
Orleans stöder även serialiseringstyper med internal, privateoch readonly medlemmar, till exempel i den här exempeltypen:
[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}";
}
Som standardinställning serialiserar Orleans datatypen genom att koda dess fullständiga namn. Du kan åsidosätta detta genom att lägga till en Orleans.AliasAttribute. Detta resulterar i att din typ serialiseras med ett namn som är motståndskraftigt mot att byta namn på den underliggande klassen eller flytta den mellan sammansättningar. Typalias är globalt begränsade och du kan inte ha två alias med samma värde i ett program. För generiska typer, måste aliasvärdet innehålla antalet generiska parametrar som föregås av en backtick; till exempel, kan MyGenericType<T, U> ha aliaset [Alias("mytype`2")].
Serialisering av record-typer
Medlemmar som definieras i en posts primära konstruktor har implicita ID:er som standard. Med andra ord har Orleans stöd för serialiseringstyper record . Det innebär att du inte kan ändra parameterordningen för en redan distribuerad typ, eftersom det bryter kompatibiliteten med tidigare versioner av ditt program (i ett rullande uppgraderingsscenario) och med serialiserade instanser av den typen i lagring och strömmar. Medlemmar som definierats i en posttyps brödtext delar inte identiteter med de primära konstruktorparametrarna.
[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; }
}
Om du inte vill att de primära konstruktorparametrarna automatiskt ska inkluderas som serialiserbara fält använder du [GenerateSerializer(IncludePrimaryConstructorParameters = false)].
Surrogater för serialisering av externa typer
Ibland kan du behöva skicka typer mellan korn som du inte har fullständig kontroll över. I dessa fall kan det vara opraktiskt att manuellt konvertera till och från en anpassad definierad typ i programkoden. Orleans erbjuder en lösning för dessa situationer: surrogattyper. Surrogater serialiseras i stället för sin måltyp och har funktionen att konvertera till och från sin måltyp. Tänk på följande exempel på en utländsk typ och motsvarande surrogat och konverterare:
// 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
};
}
I koden ovan:
-
MyForeignLibraryValueTypeär en typ som du inte kan kontrollera, definierad i ett bibliotek som använder. -
MyForeignLibraryValueTypeSurrogateär en surrogattypmappning tillMyForeignLibraryValueType. -
RegisterConverterAttribute anger som
MyForeignLibraryValueTypeSurrogateConverterfungerar som en konverterare för att mappa mellan de två typerna. Klassen implementerar IConverter<TValue,TSurrogate> gränssnittet.
Orleans stöder serialisering av typer i typhierarkier (typer som härleds från andra typer). Om en sekundär typ kan visas i en typhierarki (t.ex. som basklass för en av dina egna typer) måste du dessutom implementera Orleans.IPopulator<TValue,TSurrogate> gränssnittet. Tänk på följande exempel:
// 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; }
}
Versionsregler
Versionstolerans stöds förutsatt att du följer en uppsättning regler när du ändrar typer. Om du är bekant med system som Google Protocol Buffers (Protobuf) är dessa regler bekanta.
Sammansatta typer (class & struct)
- Arv stöds, men det går inte att ändra arvshierarkin för ett objekt. Du kan inte lägga till, ändra eller ta bort basklassen för en klass.
- Med undantag för vissa numeriska typer som beskrivs i avsnittet Numeriska värden nedan kan du inte ändra fälttyper.
- Du kan lägga till eller ta bort fält när som helst i en arvshierarki.
- Du kan inte ändra fält-ID:t.
- Fält-ID:t måste vara unika för varje nivå i en typhierarki men kan återanvändas mellan basklasser och underklasser. En klass kan till exempel
Basedeklarera ett fält med ID0och enSub : Baseklass kan deklarera ett annat fält med samma ID,0.
Numeriska värden
- Du kan inte ändra signering av ett numeriskt fält.
- Konverteringar mellan
int&uintär ogiltiga.
- Konverteringar mellan
- Du kan ändra bredden på ett numeriskt fält.
- Till exempel stöds konverteringar från
inttilllongellerulongtillushort. - Typkonverteringar som begränsar bredden utlöser ett undantag om körningsvärdet för fältet orsakar ett överflöde.
- Konvertering från
ulongtillushortstöds endast om värdet vid körning är mindre änushort.MaxValue. - Konverteringar från
doubletillfloatstöds endast om körningsvärdet är mellanfloat.MinValueochfloat.MaxValue. - På samma sätt för
decimal, som har ett smalare intervall än bådedoubleochfloat.
- Till exempel stöds konverteringar från
Kopiatorer
Orleans främjar säkerhet som standard, inklusive säkerhet från vissa klasser av samtidighetsbuggar. I synnerhet kopierar Orleans omedelbart objekt som skickas i grainanrop som standard. Orleans. Serialisering underlättar kopieringen. När du tillämpar Orleans.CodeGeneration.GenerateSerializerAttribute på en typ, genererar Orleans också kopieringsmoduler för den typen. Orleans undviker kopiering av typer eller enskilda medlemmar som har markerats med ImmutableAttribute. Mer information finns i Serialisering av oföränderliga typer i Orleans.
Metodtips för serialisering
✅ Ge dina typer alias med hjälp av attributet
[Alias("my-type")]. Typer med alias kan byta namn utan att bryta kompatibiliteten.❌ Ändra inte en
recordtill en vanligclasseller vice versa. Poster och klasser representeras inte identiskt eftersom poster har primära konstruktormedlemmar utöver vanliga medlemmar. Därför är de två inte utbytbara.❌ Lägg inte till nya typer i en befintlig typhierarki för en serialiserbar typ. Du får inte lägga till en ny basklass i en befintlig typ. Du kan på ett säkert sätt lägga till en ny underklass till en befintlig typ.
✅ Ersätt användning av SerializableAttribute med GenerateSerializerAttribute och motsvarande IdAttribute deklarationer.
✅ Börja alla medlems-ID:n på noll för varje typ. ID:n i en underklass och dess basklass kan säkert överlappa. Båda egenskaperna i följande exempel har ID-nummer lika med
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; } }✅ Vidga numeriska medlemstyper efter behov. Du kan utvidga
sbytetillshorttillintlong.- Du kan begränsa numeriska medlemstyper, men det resulterar i ett körningsfel om observerade värden inte kan representeras korrekt av den begränsade typen. Det kan inte till exempel
int.MaxValuerepresenteras av ettshortfält, så om du begränsar ettintfält tillshortkan det resultera i ett körningsundantag om ett sådant värde påträffas.
- Du kan begränsa numeriska medlemstyper, men det resulterar i ett körningsfel om observerade värden inte kan representeras korrekt av den begränsade typen. Det kan inte till exempel
❌ Ändra inte tecknet på en medlem av numerisk typ. Du får inte ändra en medlemstyp från
uinttillinteller fråninttilluint, till exempel.
Serialiserare för kornlagring
Orleans innehåller en leverantörsbaserad beständighetsmodell för korn, som nås via State egenskapen eller genom att mata in ett eller flera IPersistentState<TState> värden i ditt korn. Före Orleans 7.0 hade varje provider en annan mekanism för att konfigurera serialisering. I Orleans 7.0 finns det nu ett allmänt gränssnitt för serialisering av tillstånd i grain IGrainStorageSerializer som erbjuder ett konsekvent sätt att anpassa tillståndsserialisering för varje leverantör. Stödda lagringsleverantörer implementerar ett mönster som innebär att ange egenskapen IStorageProviderSerializerOptions.GrainStorageSerializer på leverantörens alternativklass, till exempel:
- DynamoDBStorageOptions.GrainStorageSerializer
- AzureBlobStorageOptions.GrainStorageSerializer
- AzureTableStorageOptions.GrainStorageSerializer
- GrainStorageSerializer
Grain Storage-serialisering är för närvarande standard för Newtonsoft.Json serialisera tillstånd. Du kan ersätta detta genom att ändra den egenskapen vid konfigurationstillfället. I följande exempel visas detta med hjälp av OptionsBuilder<TOptions>:
siloBuilder.AddAzureBlobGrainStorage(
"MyGrainStorage",
(OptionsBuilder<AzureBlobStorageOptions> optionsBuilder) =>
{
optionsBuilder.Configure<IMySerializer>(
(options, serializer) => options.GrainStorageSerializer = serializer);
});
Mer information finns i OptionsBuilder API.
Orleans har ett avancerat och utökningsbart serialiseringsramverk. Orleans serialiserar datatyper som skickas i grain-begärande- och svarsmeddelanden, samt grain-beständiga tillståndsobjekt. Som en del av det här ramverket Orleans genererar automatiskt serialiseringskod för dessa datatyper. Förutom att generera mer effektiv serialisering/deserialisering för typer som redan är .NET-serializable, försöker Orleans också generera serialiserare för typer som används i gränssnitt som inte är .NET-serializable. Ramverket innehåller också en uppsättning effektiva inbyggda serialiserare för ofta använda typer: listor, ordlistor, strängar, primitiver, matriser osv.
Två viktiga funktioner i Orleansserialiseraren skiljer den från många andra serialiseringsramverk från tredje part: dynamiska typer/godtycklig polymorfism och objektidentitet.
Dynamiska typer och godtycklig polymorfism: Orleans tillämpar inte begränsningar för de typer som skickas i kornanrop och upprätthåller den faktiska datatypens dynamiska karaktär. Det innebär till exempel att om en metod i ett gränssnitt har deklarerats att acceptera IDictionary, men vid körning skickar avsändaren en SortedDictionary<TKey,TValue>, får mottagaren faktiskt en
SortedDictionary(även om gränssnittet "static contract"/grain inte angav det här beteendet).Upprätthållande av objektidentitet: Om samma objekt skickas flera gånger som argument i ett samtal eller om det pekas indirekt på mer än en gång från argumenten, serialiseras det endast en gång. På mottagarsidan Orleans återställer alla referenser korrekt så att två pekare till samma objekt fortfarande pekar på samma objekt efter deserialisering. Det är viktigt att bevara objektidentiteten i scenarier som följande: Imagine grain A skickar en ordlista med 100 poster till korn B och 10 nycklar i ordlistan pekar på samma objekt,
obj, på A:s sida. Utan att bevara objektidentiteten skulle B få en ordlista med 100 poster med de 10 nycklarna som pekar på 10 olika kloner avobj. Med objektidentiteten bevarad ser ordlistan på B:s sida exakt ut som på A:s sida, med de 10 nycklarna som pekar på ett enda objektobj.
En standard-.NET-binärserialiserare ger ovanstående två beteenden, så det var viktigt för oss att stödja denna standard och välbekanta beteende i Orleans också.
Genererade serialiserare
Orleans använder följande regler för att bestämma vilka serialiserare som ska genereras:
- Genomsök alla typer i alla sammansättningar som refererar till kärnbiblioteket Orleans .
- Från dessa sammansättningar genererar du serialiserare för typer som direkt refereras till i signaturer för korngränssnittsmetoden eller tillståndsklasssignaturer, eller för någon typ som har markerats med SerializableAttribute.
- Dessutom kan ett grain-gränssnitt eller ett implementeringsprojekt peka på vilka typer som helst för serialiseringsgenerering genom att lägga till KnownTypeAttribute eller KnownAssemblyAttribute attribut på samlingsnivå. Dessa talar om för kodgeneratorn att generera serialiserare för specifika typer eller alla berättigade typer i en sammansättning. Mer information om attribut på sammansättningsnivå finns i Tillämpa attribut på sammansättningsnivå.
Återställningsserialisering
Orleans stöder överföring av godtyckliga typer vid körtid. Därför kan den inbyggda kodgeneratorn inte fastställa hela uppsättningen typer som ska överföras i förväg. Dessutom kan vissa typer inte ha serialiserare som genererats för dem eftersom de inte är tillgängliga (t.ex. private) eller har otillgängliga fält (t.ex. readonly). Därför finns det ett behov av just-in-time-serialisering av typer som var oväntade eller där serialiserare inte kunde genereras i förväg. Serialiseraren som ansvarar för dessa typer kallas för återställningsserialiseraren.
Orleans levereras med två reservserialiserare.
- Orleans.Serialization.BinaryFormatterSerializer, som använder . NET:s BinaryFormatter; och
-
Orleans.Serialization.ILBasedSerializer, som genererar CIL-instruktioner vid körning för att skapa serialiserare som använder Orleansserialiseringsramverket för att serialisera varje fält. Det innebär att om en otillgänglig typ
MyPrivateTypeinnehåller ett fältMyTypesom har en anpassad serialiserare används den anpassade serialiseraren för att serialisera den.
Konfigurera återställningsserialiseraren FallbackSerializationProvider med hjälp av egenskapen på både ClientConfiguration (klient) och GlobalConfiguration (silos).
// Client configuration
var clientConfiguration = new ClientConfiguration();
clientConfiguration.FallbackSerializationProvider =
typeof(FantasticSerializer).GetTypeInfo();
// Global configuration
var globalConfiguration = new GlobalConfiguration();
globalConfiguration.FallbackSerializationProvider =
typeof(FantasticSerializer).GetTypeInfo();
Du kan också ange providern för återställningsserialisering i XML-konfigurationen:
<Messaging>
<FallbackSerializationProvider
Type="GreatCompany.FantasticFallbackSerializer, GreatCompany.SerializerAssembly"/>
</Messaging>
BinaryFormatterSerializer är standardåterfalls serialiserare.
Varning
Binär serialisering med BinaryFormatter kan vara farlig. Mer information finns i säkerhetsguiden för BinaryFormatter och migreringsguiden BinaryFormatter.
Undantags serialisering
Undantag serialiseras med hjälp av reservserialiseraren. Med standardkonfigurationen är BinaryFormatter reservserialiseraren. Därför måste du följa mönstret ISerializable för att säkerställa korrekt serialisering av alla egenskaper i en undantagstyp.
Här är ett exempel på en undantagstyp med korrekt implementerad serialisering:
[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);
}
}
Metodtips för serialisering
Serialisering har två huvudsakliga syften i Orleans:
- Som ett wireformat för att överföra data mellan grains och klienter under körning.
- Som ett lagringsformat för att spara långlivade data för senare hämtning.
Serialiserarna som genereras av Orleans är lämpliga för det första ändamålet på grund av deras flexibilitet, prestanda och mångsidighet. De är inte lika lämpliga för det andra ändamålet eftersom de inte uttryckligen är versionstoleranta. Vi rekommenderar att du konfigurerar en versionstolerant serialiserare, till exempel protokollbuffertar, för beständiga data. Protokollbuffertar stöds via Orleans.Serialization.ProtobufSerializer från Microsoft.Orleans.OrleansGoogleUtils NuGet-paketet. Följ metodtipsen för din valda serialiserare för att säkerställa versionstolerans. Konfigurera serialiserare från tredje part med hjälp av konfigurationsegenskapen SerializationProviders enligt beskrivningen ovan.