Wenn Sie dieses Projekt mögen oder verwenden, geben Sie ihm bitte einen Stern. Danke!
Vogen ( ausgesprochen "voh jen" ) ist ein .NET -Quellgenerator und ein Analysator. Es verwandelt Ihre Primitiven (INTs, Dezimalstellen usw.) in Werteobjekte, die Domänenkonzepte (CustomerID, Kontobalance usw.) darstellen.
Es fügt neue C# -Kompilationsfehler hinzu, um die Erstellung ungültiger Wertobjekte zu stoppen.
Diese Readme deckt einen Teil der Funktionalität ab. Weitere detailliertere Informationen finden Sie im Wiki, z. B. Erste Schritte, Tutorials und How-tos.
Der Quellgenerator generiert stark typisierte Domänenkonzepte . Sie geben dies an:
[ ValueObject < int > ]
public partial struct CustomerId ;... und Vogen erzeugt eine ähnliche Quelle wie:
public partial struct CustomerId :
System . IEquatable < CustomerId > ,
System . IComparable < CustomerId > , System . IComparable
{
private readonly int _value ;
public readonly int Value => _value ;
public CustomerId ( ) {
throw new Vogen . ValueObjectValidationException (
"Validation skipped by attempting to use the default constructor..." ) ;
}
private CustomerId ( int value ) => _value = value ;
public static CustomerId From ( int value ) {
CustomerId instance = new CustomerId ( value ) ;
return instance ;
}
public readonly bool Equals ( CustomerId other ) .. .
public readonly bool Equals ( int primitive ) .. .
public readonly override bool Equals ( object obj ) .. .
public static bool operator == ( CustomerId left , CustomerId right ) .. .
public static bool operator != ( CustomerId left , CustomerId right ) .. .
public static bool operator == ( CustomerId left , int right ) .. .
public static bool operator != ( CustomerId left , int right ) .. .
public static bool operator == ( int left , CustomerId right ) .. .
public static bool operator != ( int left , CustomerId right ) .. .
public readonly override int GetHashCode ( ) .. .
public readonly override string ToString ( ) .. .
} Sie verwenden dann CustomerId anstelle von int in Ihrer Domain in dem vollständigen Wissen, dass es gültig und sicher zu verwenden ist:
CustomerId customerId = CustomerId . From ( 123 ) ;
SendInvoice ( customerId ) ;
.. .
public void SendInvoice ( CustomerId customerId ) { .. . } int ist der Standardtyp für Wertobjekte. Es ist im Allgemeinen eine gute Idee, jeden Typ ausdrücklich ausdrücklich für Klarheit zu deklarieren. Sie können sie auch - individuell oder global - als andere Typen konfigurieren. Siehe den Abschnitt Konfiguration später im Dokument.
Hier sind einige andere Beispiele:
[ ValueObject < decimal > ]
public partial struct AccountBalance ;
[ ValueObject < string > ]
public partial class LegalEntityName ;Das Hauptziel von Vogen ist es, die Gültigkeit Ihrer Wertobjekte zu gewährleisten . Der Codeanalysator hilft Ihnen, Fehler zu vermeiden, die Sie möglicherweise nicht initialisierte Wertobjekte in Ihrer Domäne hinterlassen.
Dies geschieht, indem neue Einschränkungen in Form neuer C# -Kompilationsfehler hinzugefügt werden . Es gibt einige Möglichkeiten, wie Sie nicht initialisierte Wertobjekte erhalten könnten. Ein Weg besteht darin, Ihren Typkonstruktoren zu geben. Durch die Bereitstellung Ihrer eigenen Konstrukteure können Sie vergessen, einen Wert festzulegen, sodass Vogen Sie nicht benutzerdefinierte Konstruktoren haben können :
[ ValueObject ]
public partial struct CustomerId
{
// Vogen deliberately generates this so that you can't create your own:
// error CS0111: Type 'CustomerId' already defines a member called 'CustomerId'
// with the same parameter type
public CustomerId ( ) { }
// error VOG008: Cannot have user defined constructors,
// please use the From method for creation.
public CustomerId ( int value ) { }
}Darüber hinaus erfasst Vogen Probleme beim Erstellen oder Verbrauch von Wertenobjekten:
// catches object creation expressions
var c = new CustomerId ( ) ; // error VOG010: Type 'CustomerId' cannot be constructed with 'new' as it is prohibited
CustomerId c = default ; // error VOG009: Type 'CustomerId' cannot be constructed with default as it is prohibited.
var c = default ( CustomerId ) ; // error VOG009: Type 'CustomerId' cannot be constructed with default as it is prohibited.
var c = GetCustomerId ( ) ; // error VOG010: Type 'CustomerId' cannot be constructed with 'new' as it is prohibited
var c = Activator . CreateInstance < CustomerId > ( ) ; // error VOG025: Type 'CustomerId' cannot be constructed via Reflection as it is prohibited.
var c = Activator . CreateInstance ( < CustomerId > ) ; // error VOG025: Type 'MyVo' cannot be constructed via Reflection as
it is prohibited
// catches lambda expressions
Func < CustomerId > f = ( ) => default ; // error VOG009: Type 'CustomerId' cannot be constructed with default as it is prohibited.
// catches method / local function return expressions
CustomerId GetCustomerId ( ) => default ; // error VOG009: Type 'CustomerId' cannot be constructed with default as it is prohibited.
CustomerId GetCustomerId ( ) => new CustomerId ( ) ; // error VOG010: Type 'CustomerId' cannot be constructed with 'new' as it is prohibited
CustomerId GetCustomerId ( ) => new ( ) ; // error VOG010: Type 'CustomerId' cannot be constructed with 'new' as it is prohibited
// catches argument / parameter expressions
Task < CustomerId > t = Task . FromResult < CustomerId > ( new ( ) ) ; // error VOG010: Type 'CustomerId' cannot be constructed with 'new' as it is prohibited
void Process ( CustomerId customerId = default ) { } // error VOG009: Type 'CustomerId' cannot be constructed with default as it is prohibited. Eines der Hauptziele dieses Projekts ist es , fast die gleiche Geschwindigkeit und Speicherleistung zu erzielen wie die direkte Verwendung von Primitiven . Anders ausgedrückt, wenn Ihr decimal einen Kontostand darstellt, ist es extrem geringer Overhead, stattdessen ein AccountBalance zu verwenden. Bitte beachten Sie die Leistungsmetriken unten.
Vogen ist ein Nuget -Paket. Installieren Sie es mit:
dotnet add package Vogen
Wenn der Quellgenerator zu Ihrem Projekt hinzugefügt wird, generiert er die Wrapper für Ihre Primitiven, und der Codeanalysator informiert Sie, wenn Sie versuchen, ungültige Wertobjekte zu erstellen.
Denken Sie an Ihre Domain -Konzepte und darüber, wie Sie Primitive verwenden, um sie darzustellen, z. B. anstelle dessen:
public void HandlePayment ( int customerId , int accountId , decimal paymentAmount )... haben das:
public void HandlePayment ( CustomerId customerId , AccountId accountId , PaymentAmount paymentAmount )Es ist so einfach wie solche Typen zu erstellen:
[ ValueObject ]
public partial struct CustomerId ;
[ ValueObject ]
public partial struct AccountId ;
[ ValueObject < decimal > ]
public partial struct PaymentAmount ; Der Quellgenerator generiert Wertobjekte. Value-Objekte helfen bei der Bekämpfung der primitiven Besessenheit, indem einfache Primitive wie int , string , double usw. in einem stark typischen Typ eingewickelt werden.
Primitive Besessenheit (auch bekannt als strengte) bedeutet, von Primitiven besessen zu sein. Es ist ein Codegeruch, der die Qualität der Software verschlechtert.
" Primitive Besessenheit verwendet primitive Datentypen, um Domänenideen darzustellen " #
Einige Beispiele:
int age - wir hätten Age age . Age könnte die Validierung haben, dass es nicht negativ sein könntestring postcode - haben wir Postcode postcode . Postcode hat möglicherweise eine Validierung im Format des TextesDer Quellgenerator ist der Meinung. Die Meinungen tragen dazu bei, die Konsistenz zu gewährleisten. Die Meinungen sind:
From Age.From(12)Age.From(12) == Age.From(12) )Validate validiert, die ein Validation zurückgibtValidation.Ok ist ValueObjectValidationException Es ist üblich, Domänenideen als Primitive darzustellen, aber Primitive können die Domänenidee möglicherweise nicht vollständig beschreiben.
Um Wertobjekte anstelle von Primitiven zu verwenden, tauschen wir einfach den Code wie folgt aus:
public class CustomerInfo {
private int _id ;
public CustomerInfo ( int id ) => _id = id ;
}.. dazu:
public class CustomerInfo {
private CustomerId _id ;
public CustomerInfo ( CustomerId id ) => _id = id ;
} Hier gibt es einen Blog -Beitrag, der es beschreibt, aber zusammenfassen:
Die primitive Besessenheit ist besessen von der scheinbar bequemen Art und Weise, dass Primitive wie
intsundstringses uns ermöglichen, Domänenobjekte und -ideen darzustellen.
Es ist das :
int customerId = 42Was ist daran los?
Eine Kunden -ID kann wahrscheinlich nicht vollständig durch ein int dargestellt werden. Ein int kann negativ oder null sein, aber es ist unwahrscheinlich, dass eine Kunden -ID sein kann. Wir haben also Einschränkungen für eine Kunden -ID. Wir können diese Einschränkungen nicht für einen int darstellen oder durchsetzen .
Wir brauchen also eine Validierung, um sicherzustellen, dass die Einschränkungen einer Kunden -ID erfüllt sind. Da es in int ist, können wir nicht sicher sein, ob es vorher überprüft wurde. Deshalb müssen wir es jedes Mal überprüfen, wenn wir es verwenden. Da es ein Primitiv ist, hat jemand möglicherweise den Wert geändert. Selbst wenn wir zu 100% sicher sind, dass wir ihn zuvor überprüft haben, muss es möglicherweise noch einmal überprüft werden.
Bisher haben wir als Beispiel eine Kunden -ID mit Wert 42 verwendet. In C#kann es keine Überraschung sein, dass " 42 == 42 " ( ich habe das in JavaScript nicht überprüft! ). Aber in unserer Domäne sollten 42 immer gleich 42 gleich sind? Wahrscheinlich nicht, wenn Sie eine Lieferanten -ID von 42 mit einer Kunden -ID von 42 vergleichen! Aber Primitive helfen Ihnen hier nicht (denken Sie daran, 42 == 42 !).
( 42 == 42 ) // true
( SuppliedId . From ( 42 ) == SupplierId . From ( 42 ) ) // true
( SuppliedId . From ( 42 ) == VendorId . From ( 42 ) ) // compilation error Aber manchmal müssen wir bezeichnen, dass ein Wertobjekt nicht gültig ist oder nicht festgelegt wurde. Wir wollen nicht, dass jemand außerhalb des Objekts dies tut, da es versehentlich verwendet werden könnte. Es ist üblich, Unspecified Fälle zu haben, z. B.
public class Person {
public Age Age { get ; } = Age . Unspecified ;
} Wir können das mit einem Instance tun:
[ ValueObject ]
[ Instance ( "Unspecified" , - 1 ) ]
public readonly partial struct Age {
public static Validation Validate ( int value ) =>
value > 0 ? Validation . Ok : Validation . Invalid ( "Must be greater than zero." ) ;
} Dies erzeugt public static Age Unspecified = new Age(-1); . Der Konstruktor ist private , sodass nur dieser Typ (absichtlich) ungültige Instanzen erstellen kann.
Wenn wir Age verwenden, wird unsere Validierung klarer:
public void Process ( Person person ) {
if ( person . Age == Age . Unspecified ) {
// age not specified.
}
}Wir können auch andere Instanzeigenschaften angeben:
[ ValueObject < float > ]
[ Instance ( "Freezing" , 0 ) ]
[ Instance ( "Boiling" , 100 ) ]
public readonly partial struct Celsius {
public static Validation Validate ( float value ) =>
value >= - 273 ? Validation . Ok : Validation . Invalid ( "Cannot be colder than absolute zero" ) ;
} Jedes Wertobjekt kann eine eigene optionale Konfiguration haben. Konfiguration umfasst:
Wenn eine dieser oben genannten nicht angegeben ist, wird die globale Konfiguration abgeleitet. Es sieht so aus:
[ assembly : VogenDefaults (
underlyingType : typeof ( int ) ,
conversions : Conversions . Default ,
throws : typeof ( ValueObjectValidationException ) ) ]Diese sind wieder optional. Wenn sie nicht angegeben sind, sind sie standardmäßig:
typeof(int)Conversions.Default ( TypeConverter und System.Text.Json )typeof(ValueObjectValidationException)Es gibt mehrere Codeanalyse -Warnungen für ungültige Konfiguration, darunter:
System.Exception abgeleitet wird (Um diese selbst auszuführen: dotnet run -c Release --framework net9.0 -- --job short --filter * im Ordner Vogen.Benchmarks )
Wie bereits erwähnt, ist das Ziel von Vogen, eine sehr ähnliche Leistung im Vergleich zum Einsatz von Primitiven selbst zu erzielen. Hier ist ein Benchmark, der die Verwendung eines validierten Wertungsobjekts mit zugrunde liegender Art von int vs unter Verwendung eines int nativ ( primitiv ?) Vergleiche?
BenchmarkDotNet =v0.13.2, OS =Windows 11 (10.0.22621.1194)
AMD Ryzen 9 5950X, 1 CPU, 32 logical and 16 physical cores
.NET SDK =7.0.102
[Host] : .NET 7.0.2 (7.0.222.60605), X64 RyuJIT AVX2
ShortRun : .NET 7.0.2 (7.0.222.60605), X64 RyuJIT AVX2
Job =ShortRun IterationCount =3 LaunchCount =1
WarmupCount =3 | Verfahren | Bedeuten | Fehler | Stddev | Verhältnis | Verhältnisse | Gen0 | Zugewiesen |
|---|---|---|---|---|---|---|---|
| Verwendung intend | 14.55 ns | 1.443 ns | 0,079 ns | 1.00 | 0,00 | - - | - - |
| Mit ValueObjectStruct | 14.88 ns | 3.639 ns | 0,199 ns | 1.02 | 0,02 | - - | - - |
Es gibt keinen erkennbaren Unterschied zwischen der Verwendung einer nativen INT und einer VO -Struktur. Beide sind in Bezug auf Geschwindigkeit und Speicher ziemlich gleich.
Das nächsthäufige Szenario besteht darin, eine VO -Klasse zu verwenden, um eine native String darzustellen. Diese Ergebnisse sind:
BenchmarkDotNet =v0.13.2, OS =Windows 11 (10.0.22621.1194)
AMD Ryzen 9 5950X, 1 CPU, 32 logical and 16 physical cores
.NET SDK =7.0.102
[Host] : .NET 7.0.2 (7.0.222.60605), X64 RyuJIT AVX2
ShortRun : .NET 7.0.2 (7.0.222.60605), X64 RyuJIT AVX2
Job =ShortRun IterationCount =3 LaunchCount =1
WarmupCount =3 | Verfahren | Bedeuten | Fehler | Stddev | Verhältnis | Verhältnisse | Gen0 | Zugewiesen | Alloc -Verhältnis |
|---|---|---|---|---|---|---|---|---|
| Usingstringnativ | 151,8 ns | 32.19 | 1.76 | 1.00 | 0,00 | 0,0153 | 256 b | 1.00 |
| Verwenden von ValueObjectAstruct | 184.8 ns | 12.19 | 0,67 | 1.22 | 0,02 | 0,0153 | 256 b | 1.00 |
Es gibt eine winzige Menge an Leistungsaufwand, aber diese Messungen sind unglaublich klein. Es gibt keinen Speicheraufwand.
Standardmäßig ist jede VO mit einem TypeConverter und System.Text.Json (STJ) -Serializer dekoriert. Es gibt andere Konverter/Serializer für:
Sie werden durch die Conversions gesteuert. Das Folgende hat Serialisierer für NSJ und STJ:
[ ValueObject < float > ( conversions : Conversions . NewtonsoftJson | Conversions . SystemTextJson ) ]
public readonly partial struct Celsius ; Wenn Sie keine Conversions wünschen, geben Sie Conversions.None an.
Wenn Sie Ihre eigene Konvertierung wünschen, geben Sie erneut keine an und implementieren Sie sie selbst, genau wie jeder andere Typ. Beachten Sie jedoch, dass selbst Serialisierer beim Versuch, VOS zu erstellen, die gleichen Kompilierungsfehler für new und default erhalten.
Wenn Sie Dapper verwenden möchten, denken Sie daran, es zu registrieren - ungefähr so:
SqlMapper . AddTypeHandler ( new Customer . DapperTypeHandler ( ) ) ;Weitere Informationen finden Sie im Beispiel -Ordner für Beispiele.
Was folgt, ist ein Auszug aus der vollständigen FAQ -Seite im Wiki.
Ja, es ist hier: https://stevedunn.github.io/vogen/vogen.html
Der Quellgenerator ist .NET Standard 2.0. Der von ihm generierte Code unterstützt alle C# -sprachversionen von 6.0 und vor
Wenn Sie den Generator in einem .NET -Framework -Projekt verwenden und die alten Stilprojekte (die vor den Projekten "SDK -Stil" verwenden, müssen Sie ein paar Dinge anders machen:
PackageReference in der .csproj -Datei hinzu: < ItemGroup >
< PackageReference Include = " Vogen " Version = " [LATEST_VERSION_HERE - E.G. 1.0.18] " PrivateAssets = " all " />
</ ItemGroup >latest (oder irgendetwas 8 oder mehr): <PropertyGroup>
+ <LangVersion>latest</LangVersion>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
Das Begriff Value -Objekt repräsentiert ein kleines Objekt, dessen Gleichheit auf Wert und nicht auf Identität basiert. Aus Wikipedia
In der Informatik ist ein Wertobjekt ein kleines Objekt, das eine einfache Entität darstellt, deren Gleichheit nicht auf der Identität basiert: IE zwei Wertobjekte sind gleich, wenn sie denselben Wert haben und nicht unbedingt das gleiche Objekt sind.
In DDD ist ein Wertobjekt (wieder aus Wikipedia)
... Ein Wertobjekt ist ein unveränderliches Objekt, das Attribute enthält, aber keine konzeptionelle Identität hat
public record struct CustomerId(int Value); ? Das gibt Ihnen keine Bestätigung. Um Value zu validieren, können Sie die Kurz -Syntax (Primärkonstruktor) nicht verwenden. Sie müssten also:
public record struct CustomerId
{
public CustomerId ( int value ) {
if ( value <= 0 ) throw new Exception ( .. . )
}
} Sie können auch andere Konstruktoren zur Verfügung stellen, die die Daten möglicherweise nicht validieren, wodurch ungültige Daten in Ihre Domäne ermöglichen . Diese anderen Konstruktoren machen möglicherweise keine Ausnahme oder machen verschiedene Ausnahmen. Eine der Meinungen in Vogen ist, dass alle ungültigen Daten, die einem Wertobjekt gegeben wurden, eine ValueObjectValidationException auswirken.
Sie können auch default(CustomerId) verwenden, um die Validierung zu entgehen. In Vogen gibt es Analysatoren, die dies fangen und den Build scheitern, z. B.:
// error VOG009: Type 'CustomerId' cannot be constructed with default as it is prohibited.
CustomerId c = default ;
// error VOG009: Type 'CustomerId' cannot be constructed with default as it is prohibited.
var c2 = default ( CustomerId ) ; Ja. Standardmäßig ist jede VO mit einem TypeConverter und System.Text.Json (STJ) -Serializer dekoriert. Es gibt andere Konverter/Serialisierer für:
Ja, obwohl es bestimmte Überlegungen gibt. Bitte beachten Sie die EFCORE -Seite auf dem Wiki, aber das TL; dr ist:
[ValueObject<string>(conversions: Conversions.EfCoreValueConverter)] und Sie müssen EFCORE anweisen, diesen Konverter in der OnModelCreating -Methode, z. B. zu verwenden: builder . Entity < SomeEntity > ( b =>
{
b . Property ( e => e . Name ) . HasConversion ( new Name . EfCoreValueConverter ( ) ) ;
} ) ;Sie könnten, aber um eine Konsistenz in Ihrer Domain zu gewährleisten, müssten Sie überall überall validieren . Und das Gesetz von Shallows sagt, dass das nicht möglich ist:
⚖️ Shallowayes Gesetz "Wenn sich die Dinge ändern müssen und n> 1, wird Shalloway höchstens n - 1 dieser Dinge finden."
Konkret: "Wenn sich 5 Dinge ändern müssen, wird Flachay höchstens 4 dieser Dinge finden."
struct ist, kann ich die Verwendung von CustomerId customerId = default(CustomerId); ?Ja . Der Analysator generiert einen Kompilierungsfehler.
struct ist, kann ich die Verwendung von CustomerId customerId = new(CustomerId); ?Ja . Der Analysator generiert einen Kompilierungsfehler.
NEIN . Der parameterlose Konstruktor wird automatisch generiert und der Konstruktor, der den zugrunde liegenden Wert nimmt, wird ebenfalls automatisch generiert.
Wenn Sie weitere Konstruktoren hinzufügen, erhalten Sie einen Kompilierungsfehler vom Codegenerator, z. B.
[ ValueObject < int > ) ]
public partial struct CustomerId {
// Vogen already generates this as a private constructor:
// error CS0111: Type 'CustomerId' already defines a member called 'CustomerId' with the same parameter type
public CustomerId ( ) { }
// error VOG008: Cannot have user defined constructors, please use the From method for creation.
public CustomerId ( int value ) { }
}Sie könnten , aber Sie würden Compiler-Warnung CS0282 erhalten-es gibt keine definierte Reihenfolge zwischen Feldern in mehreren Erklärungen der Teilklasse oder Strukturart "Typ".
Ich möchte, dass die verwendeten Werte beispielsweise die Eingabe normalisieren/sanieren. Ist das möglich?
Ja, fügen Sie die NormalizeInput -Methode hinzu, z. B.
private static string NormalizeInput ( string input ) => input . Trim ( ) ;Weitere Informationen finden Sie in Wiki.
Es wäre großartig, wenn es wäre, aber es ist derzeit nicht. Ich habe einen Artikel darüber geschrieben, aber zusammenfassend gibt es einen langjährigen Sprachvorschlag, der sich auf nicht defaugbare Werttypen konzentriert. Nicht eingestimmte Werttypen zu haben, ist ein großartiger erster Schritt, aber es wäre auch praktisch, etwas in der Sprache zu haben, um die Validierung durchzusetzen. Also habe ich einen Sprachvorschlag für invariante Aufzeichnungen hinzugefügt.
Eine der Antworten im Vorschlag besagt, dass das Sprachteam entschieden hat, dass die Validierungsrichtlinien nicht Teil von C#sein sollten, sondern von Quellgeneratoren bereitgestellt werden sollten.
Starlytypedid Dies konzentriert sich mehr auf IDs. Vogen konzentriert sich mehr auf "Domänenkonzepte" und die mit diesen Konzepten verbundenen Einschränkungen.
STRINCINGYTED Dies ist mein erster Versuch und ist nicht mit Quellen generiert. Es gibt Speicheraufwand, da der Basistyp eine Klasse ist. Es gibt auch keine Analysatoren. Es ist jetzt als veraltet zugunsten von Vogen gekennzeichnet.
Value of ähnlich wie beidlyted - nicht mit Quellen erzeugte und keine Analysatoren. Dies ist auch entspannter und ermöglicht zusammengesetzte "zugrunde liegende" Typen.
ValueObjectGenerator ähnlich wie Vogen, aber weniger auf die Validierung und keinen Codeanalysator ausgerichtet.
Jeder Typ kann verpackt werden. Serialisierung und Typkonvertierungen haben Implementierungen für:
Saite
int
lang
kurz
Byte
float (Single)
dezimal
doppelt
Datetime
DateOnly
Zeitlich
DateTimeOffset
Guid
bool
Für andere Typen wird eine generische Konvertierung und ein Serialisierer angewendet. Wenn Sie Ihre eigenen Konverter für die Typ -Konvertierung und -Serialisierung anbieten, geben Sie None für Konverter an und dekorieren Sie Ihren Typ mit Attributen für Ihre eigenen Typen, z. B.
[ ValueObject < SpecialPrimitive > ( conversions : Conversions . None ) ]
[ System . Text . Json . Serialization . JsonConverter ( typeof ( SpecialPrimitiveJsonConverter ) ) ]
[ System . ComponentModel . TypeConverter ( typeof ( SpecialPrimitiveTypeConverter ) ) ]
public partial struct SpecialMeasurement ; Ja, indem Sie den Ausnahmetyp entweder im ValueObject -Attribut oder global mit VogenConfiguration angeben.
TimeOnly -sagt, dass DateTime nicht in TimeOnly konvertiert werden kann - was soll ich tun? LINQ2DB 4.0 oder größer unterstützt DateOnly und TimeOnly . Vogen erzeugt Wertkonverter für LINQ2DB; Für DateOnly funktioniert es einfach, aber für "ZeitOnly müssen Sie dies Ihrer Bewerbung hinzufügen:
MappingSchema.Default.SetConverter<DateTime, TimeOnly>(dt => TimeOnly.FromDateTime(dt));
Ja. Fügen Sie Protobuf-Net eine Abhängigkeit hinzu und legen Sie ein Ersatzattribut fest:
[ ValueObject < string > ]
[ ProtoContract ( Surrogate = typeof ( string ) ) ]
public partial class BoxId {
//...
}Der BoxID -Typ wird jetzt als Zeichenfolge in allen Nachrichten/GRPC -Aufrufen serialisiert. Wenn man .Proto -Dateien für andere Anwendungen aus C# Code generiert, enthalten Protodateien den Ersatztyp als Typ. Vielen Dank an @Domasm für diese Informationen .
Vielen Dank an alle Leute, die beigetragen haben!
Ich ließ mich viel von Andrew Locks stark inspirieren.
Ich habe auch einige großartige Ideen von Gérald Barrés MeuchtouSou.analyzer bekommen