Entrainement
Module
Convertir des types de données à l’aide de techniques de cast et de conversion en C# - Training
Explorez l’utilisation de techniques C# pour les casts et les conversions.
Ce navigateur n’est plus pris en charge.
Effectuez une mise à niveau vers Microsoft Edge pour tirer parti des dernières fonctionnalités, des mises à jour de sécurité et du support technique.
Les convertisseurs de valeurs permettent de convertir les valeurs de propriété lors de la lecture ou de l’écriture dans la base de données. Cette conversion peut passer d’une valeur à une autre du même type (par exemple, chiffrement de chaînes) ou d’une valeur d’un type à une valeur d’un autre type (par exemple, conversion de valeurs d’énumération en et à partir de chaînes dans la base de données.)
Conseil
Vous pouvez exécuter et déboguer dans tout le code de ce document en téléchargeant l’exemple de code à partir de GitHub.
Les convertisseurs de valeurs sont spécifiés en termes de ModelClrType
et d’un ProviderClrType
. Le type de modèle est le type .NET de la propriété dans le type d’entité. Le type de fournisseur est le type .NET compris par le fournisseur de base de données. Par exemple, pour enregistrer des énumérations sous forme de chaînes dans la base de données, le type de modèle est le type de l’énumération et le type de fournisseur est String
. Ces deux types peuvent être identiques.
Les conversions sont définies à l’aide de deux arborescences d’expressions Func
: une ModelClrType
à ProviderClrType
et l’autre de ProviderClrType
à ModelClrType
. Les arborescences d’expressions sont utilisées afin qu’elles puissent être compilées dans le délégué d’accès à la base de données pour des conversions efficaces. L’arborescence d’expressions peut contenir un appel simple à une méthode de conversion pour les conversions complexes.
Notes
Une propriété qui a été configurée pour la conversion de valeur peut également avoir besoin de spécifier une ValueComparer<T>. Pour plus d’informations, consultez les exemples ci-dessous et la documentation Comparateurs de valeurs.
Les conversions de valeurs sont configurées dans DbContext.OnModelCreating. Par exemple, considérez un type d’énumération et d’entité défini comme suit :
public class Rider
{
public int Id { get; set; }
public EquineBeast Mount { get; set; }
}
public enum EquineBeast
{
Donkey,
Mule,
Horse,
Unicorn
}
Les conversions peuvent être configurées dans OnModelCreating pour stocker les valeurs d’énumération en tant que chaînes telles que « Donkey », « Mule », etc. dans la base de données ; il vous suffit de fournir une fonction qui se convertit du ModelClrType
au ProviderClrType
, et une autre pour la conversion opposée :
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder
.Entity<Rider>()
.Property(e => e.Mount)
.HasConversion(
v => v.ToString(),
v => (EquineBeast)Enum.Parse(typeof(EquineBeast), v));
}
Notes
Une valeur null
ne sera jamais passée à un convertisseur de valeur. Une valeur Null dans une colonne de base de données est toujours une valeur Null dans l’instance d’entité, et inversement. Cela facilite l’implémentation des conversions et leur permet d’être partagées entre des propriétés nullables et non nullables. Pour plus d’informations, consultez problème GitHub #13850.
Il est courant que le convertisseur de valeur soit configuré pour chaque propriété qui utilise le type CLR approprié. Au lieu d’effectuer cette opération manuellement pour chaque propriété, vous pouvez utiliser configuration de modèle pré-convention pour effectuer cette opération une fois pour l’ensemble de votre modèle. Pour ce faire, définissez votre convertisseur de valeurs en tant que classe :
public class CurrencyConverter : ValueConverter<Currency, decimal>
{
public CurrencyConverter()
: base(
v => v.Amount,
v => new Currency(v))
{
}
}
Ensuite, remplacez ConfigureConventions dans votre type de contexte et configurez le convertisseur comme suit :
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
configurationBuilder
.Properties<Currency>()
.HaveConversion<CurrencyConverter>();
}
EF Core contient de nombreuses conversions prédéfinies qui évitent la nécessité d’écrire manuellement des fonctions de conversion. Au lieu de cela, EF Core choisit la conversion à utiliser en fonction du type de propriété dans le modèle et du type de fournisseur de base de données demandé.
Par exemple, l’énumération en conversions de chaînes est utilisée comme exemple ci-dessus, mais EF Core le fera automatiquement lorsque le type de fournisseur est configuré comme string
à l’aide du type générique de HasConversion:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder
.Entity<Rider>()
.Property(e => e.Mount)
.HasConversion<string>();
}
La même chose peut être obtenue en spécifiant explicitement le type de colonne de base de données. Par exemple, si le type d’entité est défini comme suit :
public class Rider2
{
public int Id { get; set; }
[Column(TypeName = "nvarchar(24)")]
public EquineBeast Mount { get; set; }
}
Ensuite, les valeurs d’énumération sont enregistrées sous forme de chaînes dans la base de données sans configuration supplémentaire dans OnModelCreating.
L’appel de HasConversion comme indiqué ci-dessus crée une instance de ValueConverter<TModel,TProvider> et la définit sur la propriété. Le ValueConverter
peut être créé explicitement. Par exemple :
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
var converter = new ValueConverter<EquineBeast, string>(
v => v.ToString(),
v => (EquineBeast)Enum.Parse(typeof(EquineBeast), v));
modelBuilder
.Entity<Rider>()
.Property(e => e.Mount)
.HasConversion(converter);
}
Cela peut être utile lorsque plusieurs propriétés utilisent la même conversion.
Comme mentionné ci-dessus, EF Core est fourni avec un ensemble de classes ValueConverter<TModel,TProvider> prédéfinies, trouvées dans l’espace de noms Microsoft.EntityFrameworkCore.Storage.ValueConversion. Dans de nombreux cas, EF choisit le convertisseur intégré approprié en fonction du type de la propriété dans le modèle et du type demandé dans la base de données, comme indiqué ci-dessus pour les énumérations. Par exemple, l’utilisation de .HasConversion<int>()
sur une propriété de bool
entraîne la conversion des valeurs bool par zéro numérique et une valeur :
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder
.Entity<User>()
.Property(e => e.IsActive)
.HasConversion<int>();
}
Cela est fonctionnellement identique à la création d’une instance de l’intégrée BoolToZeroOneConverter<TProvider> et à sa définition explicite :
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
var converter = new BoolToZeroOneConverter<int>();
modelBuilder
.Entity<User>()
.Property(e => e.IsActive)
.HasConversion(converter);
}
Le tableau suivant récapitule les conversions prédéfinis couramment utilisées des types de modèles/propriétés vers des types de fournisseurs de base de données. Dans la table any_numeric_type
signifie l’un des int
, short
, long
, byte
, uint
, ushort
, ulong
, sbyte
, char
, decimal
, float
ou double
.
Type de modèle/propriété | Type de fournisseur/de base de données | Conversion | Utilisation |
---|---|---|---|
bool | any_numeric_type | False/true sur 0/1 | .HasConversion<any_numeric_type>() |
any_numeric_type | False/true sur deux nombres | Utilisez BoolToTwoValuesConverter<TProvider>. | |
string | False/true sur "N"/"Y" | .HasConversion<string>() |
|
string | False/true sur deux chaînes | Utilisez BoolToStringConverter. | |
any_numeric_type | bool | 0/1 à false/true | .HasConversion<bool>() |
any_numeric_type | Cast simple | .HasConversion<any_numeric_type>() |
|
string | Nombre sous forme de chaîne | .HasConversion<string>() |
|
Enum | any_numeric_type | Valeur numérique de l’énumération | .HasConversion<any_numeric_type>() |
string | Représentation sous forme de chaîne de la valeur d’énumération | .HasConversion<string>() |
|
string | bool | Analyse la chaîne en tant que bool | .HasConversion<bool>() |
any_numeric_type | Analyse la chaîne en tant que type numérique donné | .HasConversion<any_numeric_type>() |
|
char | Premier caractère de la chaîne | .HasConversion<char>() |
|
Date et heure | Analyse la chaîne en tant que DateTime | .HasConversion<DateTime>() |
|
DateTimeOffset | Analyse la chaîne en tant que DateTimeOffset | .HasConversion<DateTimeOffset>() |
|
TimeSpan | Analyse la chaîne en tant que timeSpan | .HasConversion<TimeSpan>() |
|
GUID | Analyse la chaîne en tant que GUID | .HasConversion<Guid>() |
|
byte[] | Chaîne en tant qu’octets UTF8 | .HasConversion<byte[]>() |
|
char | string | Chaîne de caractères unique | .HasConversion<string>() |
Date et heure | long | Date/heure encodée préservant DateTime.Kind | .HasConversion<long>() |
long | Cycles | Utilisez DateTimeToTicksConverter. | |
string | Chaîne de date/heure de culture invariante | .HasConversion<string>() |
|
DateTimeOffset | long | Date/heure encodées avec décalage | .HasConversion<long>() |
string | Chaîne de date/heure de culture invariante avec décalage | .HasConversion<string>() |
|
TimeSpan | long | Cycles | .HasConversion<long>() |
string | Chaîne d’intervalle de temps de culture invariant | .HasConversion<string>() |
|
Uri | string | URI sous forme de chaîne | .HasConversion<string>() |
PhysicalAddress | string | Adresse sous forme de chaîne | .HasConversion<string>() |
byte[] | Octets dans l’ordre réseau big-endian | .HasConversion<byte[]>() |
|
IPAddress | string | Adresse sous forme de chaîne | .HasConversion<string>() |
byte[] | Octets dans l’ordre réseau big-endian | .HasConversion<byte[]>() |
|
GUID | string | Le GUID au format 'dddddddd-dddd-dddd-dddd-dddddddddddd' | .HasConversion<string>() |
byte[] | Octets dans l’ordre de sérialisation binaire .NET | .HasConversion<byte[]>() |
Notez que ces conversions supposent que le format de la valeur est approprié pour la conversion. Par exemple, la conversion de chaînes en nombres échoue si les valeurs de chaîne ne peuvent pas être analysées en tant que nombres.
La liste complète des convertisseurs intégrés est la suivante :
Notez que tous les convertisseurs intégrés sont sans état et qu’une seule instance peut être partagée en toute sécurité par plusieurs propriétés.
Certains types de base de données ont des facettes qui modifient la façon dont les données sont stockées. Il s’agit notamment des paramètres suivants :
Ces facettes peuvent être configurées de manière normale pour une propriété qui utilise un convertisseur de valeur et s’applique au type de base de données converti. Par exemple, lors de la conversion d’une énumération en chaînes, nous pouvons spécifier que la colonne de base de données doit être non-Unicode et stocker jusqu’à 20 caractères :
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder
.Entity<Rider>()
.Property(e => e.Mount)
.HasConversion<string>()
.HasMaxLength(20)
.IsUnicode(false);
}
Ou, lors de la création du convertisseur explicitement :
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
var converter = new ValueConverter<EquineBeast, string>(
v => v.ToString(),
v => (EquineBeast)Enum.Parse(typeof(EquineBeast), v));
modelBuilder
.Entity<Rider>()
.Property(e => e.Mount)
.HasConversion(converter)
.HasMaxLength(20)
.IsUnicode(false);
}
Cela entraîne une colonne varchar(20)
lors de l’utilisation des migrations EF Core sur SQL Server :
CREATE TABLE [Rider] (
[Id] int NOT NULL IDENTITY,
[Mount] varchar(20) NOT NULL,
CONSTRAINT [PK_Rider] PRIMARY KEY ([Id]));
Toutefois, si, par défaut, toutes les colonnes EquineBeast
doivent être varchar(20)
, ces informations peuvent être fournies au convertisseur de valeurs en tant que ConverterMappingHints. Par exemple :
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
var converter = new ValueConverter<EquineBeast, string>(
v => v.ToString(),
v => (EquineBeast)Enum.Parse(typeof(EquineBeast), v),
new ConverterMappingHints(size: 20, unicode: false));
modelBuilder
.Entity<Rider>()
.Property(e => e.Mount)
.HasConversion(converter);
}
À présent, chaque fois que ce convertisseur est utilisé, la colonne de base de données n’est pas unicode avec une longueur maximale de 20. Toutefois, il s’agit uniquement d’indicateurs, car ils sont substitués par toutes les facettes définies explicitement sur la propriété mappée.
Cet exemple utilise un type simple pour encapsuler un type primitif. Cela peut être utile lorsque vous souhaitez que le type de votre modèle soit plus spécifique (et donc plus sûr de type) qu’un type primitif. Dans cet exemple, ce type est Dollars
, qui encapsule la primitive décimale :
public readonly struct Dollars
{
public Dollars(decimal amount)
=> Amount = amount;
public decimal Amount { get; }
public override string ToString()
=> $"${Amount}";
}
Cela peut être utilisé dans un type d’entité :
public class Order
{
public int Id { get; set; }
public Dollars Price { get; set; }
}
Converti en decimal
sous-jacent lorsqu’il est stocké dans la base de données :
modelBuilder.Entity<Order>()
.Property(e => e.Price)
.HasConversion(
v => v.Amount,
v => new Dollars(v));
Notes
Cet objet valeur est implémenté en tant que structure en lecture seule. Cela signifie qu’EF Core peut prendre une capture instantanée et comparer des valeurs sans problème. Pour plus d’informations, consultez comparaisons de valeurs.
Dans l’exemple précédent, le type d’objet value ne contenait qu’une seule propriété. Il est plus courant pour un type d’objet value de composer plusieurs propriétés qui forment ensemble un concept de domaine. Par exemple, un type de Money
général qui contient à la fois le montant et la devise :
public readonly struct Money
{
[JsonConstructor]
public Money(decimal amount, Currency currency)
{
Amount = amount;
Currency = currency;
}
public override string ToString()
=> (Currency == Currency.UsDollars ? "$" : "£") + Amount;
public decimal Amount { get; }
public Currency Currency { get; }
}
public enum Currency
{
UsDollars,
PoundsSterling
}
Cet objet valeur peut être utilisé dans un type d’entité comme avant :
public class Order
{
public int Id { get; set; }
public Money Price { get; set; }
}
Les convertisseurs de valeurs peuvent actuellement convertir uniquement des valeurs vers et à partir d’une seule colonne de base de données. Cette limitation signifie que toutes les valeurs de propriété de l’objet doivent être encodées en une seule valeur de colonne. Cela est généralement géré en sérialisant l’objet au fur et à mesure qu’il se trouve dans la base de données, puis le désérialisant à nouveau en sortie. Par exemple, à l’aide de System.Text.Json:
modelBuilder.Entity<Order>()
.Property(e => e.Price)
.HasConversion(
v => JsonSerializer.Serialize(v, (JsonSerializerOptions)null),
v => JsonSerializer.Deserialize<Money>(v, (JsonSerializerOptions)null));
Notes
Nous prévoyons d’autoriser le mappage d’un objet à plusieurs colonnes dans une version ultérieure d’EF Core, en supprimant la nécessité d’utiliser la sérialisation ici. Ceci est suivi par problème GitHub #13947.
Notes
Comme dans l’exemple précédent, cet objet valeur est implémenté en tant que struct en lecture seule. Cela signifie qu’EF Core peut prendre une capture instantanée et comparer des valeurs sans problème. Pour plus d’informations, consultez comparaisons de valeurs.
La sérialisation peut également être utilisée pour stocker une collection de valeurs primitives. Par exemple :
public class Post
{
public int Id { get; set; }
public string Title { get; set; }
public string Contents { get; set; }
public ICollection<string> Tags { get; set; }
}
Utilisation de System.Text.Json à nouveau :
modelBuilder.Entity<Post>()
.Property(e => e.Tags)
.HasConversion(
v => JsonSerializer.Serialize(v, (JsonSerializerOptions)null),
v => JsonSerializer.Deserialize<List<string>>(v, (JsonSerializerOptions)null),
new ValueComparer<ICollection<string>>(
(c1, c2) => c1.SequenceEqual(c2),
c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())),
c => (ICollection<string>)c.ToList()));
ICollection<string>
représente un type de référence mutable. Cela signifie qu’un ValueComparer<T> est nécessaire pour qu’EF Core puisse suivre et détecter correctement les modifications. Pour plus d’informations, consultez comparaisons de valeurs.
En combinant les deux exemples précédents, nous pouvons créer une collection d’objets valeur. Par exemple, considérez un type de AnnualFinance
qui modélise les finances de blog pour une seule année :
public readonly struct AnnualFinance
{
[JsonConstructor]
public AnnualFinance(int year, Money income, Money expenses)
{
Year = year;
Income = income;
Expenses = expenses;
}
public int Year { get; }
public Money Income { get; }
public Money Expenses { get; }
public Money Revenue => new Money(Income.Amount - Expenses.Amount, Income.Currency);
}
Ce type compose plusieurs des types Money
que nous avons créés précédemment :
public readonly struct Money
{
[JsonConstructor]
public Money(decimal amount, Currency currency)
{
Amount = amount;
Currency = currency;
}
public override string ToString()
=> (Currency == Currency.UsDollars ? "$" : "£") + Amount;
public decimal Amount { get; }
public Currency Currency { get; }
}
public enum Currency
{
UsDollars,
PoundsSterling
}
Nous pouvons ensuite ajouter une collection de AnnualFinance
à notre type d’entité :
public class Blog
{
public int Id { get; set; }
public string Name { get; set; }
public IList<AnnualFinance> Finances { get; set; }
}
Utilisez à nouveau la sérialisation pour stocker ceci :
modelBuilder.Entity<Blog>()
.Property(e => e.Finances)
.HasConversion(
v => JsonSerializer.Serialize(v, (JsonSerializerOptions)null),
v => JsonSerializer.Deserialize<List<AnnualFinance>>(v, (JsonSerializerOptions)null),
new ValueComparer<IList<AnnualFinance>>(
(c1, c2) => c1.SequenceEqual(c2),
c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())),
c => (IList<AnnualFinance>)c.ToList()));
Notes
Comme précédemment, cette conversion nécessite une ValueComparer<T>. Pour plus d’informations, consultez comparaisons de valeurs.
Parfois, les propriétés de clé primitive peuvent être encapsulées dans des objets valeur pour ajouter un niveau supplémentaire de sécurité de type lors de l’attribution de valeurs. Par exemple, nous pourrions implémenter un type de clé pour les blogs et un type de clé pour les publications :
public readonly struct BlogKey
{
public BlogKey(int id) => Id = id;
public int Id { get; }
}
public readonly struct PostKey
{
public PostKey(int id) => Id = id;
public int Id { get; }
}
Ceux-ci peuvent ensuite être utilisés dans le modèle de domaine :
public class Blog
{
public BlogKey Id { get; set; }
public string Name { get; set; }
public ICollection<Post> Posts { get; set; }
}
public class Post
{
public PostKey Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public BlogKey? BlogId { get; set; }
public Blog Blog { get; set; }
}
Notez que Blog.Id
ne peut pas être affecté accidentellement à un PostKey
, et Post.Id
ne peut pas être affecté accidentellement à un BlogKey
. De même, la propriété de clé étrangère Post.BlogId
doit être affectée à un BlogKey
.
Notes
L’affichage de ce modèle ne signifie pas que nous le recommandons. Déterminez soigneusement si ce niveau d’abstraction aide ou entrave votre expérience de développement. Envisagez également d’utiliser des navigations et des clés générées au lieu de traiter directement les valeurs de clé.
Ces propriétés de clé peuvent ensuite être mappées à l’aide de convertisseurs de valeurs :
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
var blogKeyConverter = new ValueConverter<BlogKey, int>(
v => v.Id,
v => new BlogKey(v));
modelBuilder.Entity<Blog>().Property(e => e.Id).HasConversion(blogKeyConverter);
modelBuilder.Entity<Post>(
b =>
{
b.Property(e => e.Id).HasConversion(v => v.Id, v => new PostKey(v));
b.Property(e => e.BlogId).HasConversion(blogKeyConverter);
});
}
Notes
Les propriétés de clé avec conversions peuvent uniquement utiliser les valeurs de clé générées à partir d’EF Core 7.0.
SQL Server prend en charge la concurrence automatique optimiste à l’aide de colonnes de rowversion
/timestamp
binaires de 8 octets. Celles-ci sont toujours lues et écrites dans la base de données à l’aide d’un tableau de 8 octets. Toutefois, les tableaux d’octets sont un type de référence mutable, ce qui les rend quelque peu douloureux à traiter. Les convertisseurs de valeurs permettent au rowversion
d’être mappés à une propriété ulong
, qui est beaucoup plus appropriée et facile à utiliser que le tableau d’octets. Par exemple, considérez une entité Blog
avec un jeton d’accès concurrentiel ulong :
public class Blog
{
public int Id { get; set; }
public string Name { get; set; }
public ulong Version { get; set; }
}
Cela peut être mappé à une colonne rowversion
SQL Server à l’aide d’un convertisseur de valeurs :
modelBuilder.Entity<Blog>()
.Property(e => e.Version)
.IsRowVersion()
.HasConversion<byte[]>();
SQL Server ignore l’indicateur de DateTime.Kind lors du stockage d’un DateTime en tant que datetime
ou datetime2
. Cela signifie que les valeurs DateTime qui reviennent de la base de données ont toujours une DateTimeKind de Unspecified
.
Les convertisseurs de valeur peuvent être utilisés de deux manières pour résoudre ce problème. Tout d’abord, EF Core a un convertisseur de valeur qui crée une valeur opaque de 8 octets qui conserve l’indicateur de Kind
. Par exemple :
modelBuilder.Entity<Post>()
.Property(e => e.PostedOn)
.HasConversion<long>();
Cela permet aux valeurs DateTime avec différents indicateurs Kind
d’être mélangés dans la base de données.
Le problème avec cette approche est que la base de données n’a plus de colonnes reconnaissables datetime
ou datetime2
. Il est donc courant de toujours stocker l’heure UTC (ou, moins souvent, l’heure locale), puis d’ignorer l’indicateur Kind
ou de le définir sur la valeur appropriée à l’aide d’un convertisseur de valeurs. Par exemple, le convertisseur ci-dessous garantit que la valeur DateTime
lue à partir de la base de données aura la DateTimeKind UTC
:
modelBuilder.Entity<Post>()
.Property(e => e.LastUpdated)
.HasConversion(
v => v,
v => new DateTime(v.Ticks, DateTimeKind.Utc));
Si une combinaison de valeurs locales et UTC est définie dans des instances d’entité, le convertisseur peut être utilisé pour effectuer une conversion appropriée avant l’insertion. Par exemple :
modelBuilder.Entity<Post>()
.Property(e => e.LastUpdated)
.HasConversion(
v => v.ToUniversalTime(),
v => new DateTime(v.Ticks, DateTimeKind.Utc));
Notes
Envisagez soigneusement d’unifier tout le code d’accès à la base de données pour utiliser l’heure UTC à tout moment, uniquement en traitant l’heure locale lors de la présentation des données aux utilisateurs.
Certaines bases de données, y compris SQL Server, effectuent des comparaisons de chaînes non sensibles à la casse par défaut. .NET, d’autre part, effectue des comparaisons de chaînes sensibles à la casse par défaut. Cela signifie qu’une valeur de clé étrangère comme « DotNet » correspond à la valeur de clé primaire « dotnet » sur SQL Server, mais ne la correspondra pas dans EF Core. Un comparateur de valeurs pour les clés peut être utilisé pour forcer EF Core à des comparaisons de chaînes qui ne respectent pas la casse, comme dans la base de données. Par exemple, considérez un modèle de blog/billets avec des clés de chaîne :
public class Blog
{
public string Id { get; set; }
public string Name { get; set; }
public ICollection<Post> Posts { get; set; }
}
public class Post
{
public string Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public string BlogId { get; set; }
public Blog Blog { get; set; }
}
Cela ne fonctionnera pas comme prévu si certaines des valeurs Post.BlogId
ont une casse différente. Les erreurs provoquées par cela dépendent de ce que fait l’application, mais impliquent généralement des graphiques d’objets qui ne sont pas corrigés correctement, et/ou des mises à jour qui échouent, car la valeur de la clé de domaine complet est incorrecte. Un comparateur de valeurs peut être utilisé pour corriger ce problème :
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
var comparer = new ValueComparer<string>(
(l, r) => string.Equals(l, r, StringComparison.OrdinalIgnoreCase),
v => v.ToUpper().GetHashCode(),
v => v);
modelBuilder.Entity<Blog>()
.Property(e => e.Id)
.Metadata.SetValueComparer(comparer);
modelBuilder.Entity<Post>(
b =>
{
b.Property(e => e.Id).Metadata.SetValueComparer(comparer);
b.Property(e => e.BlogId).Metadata.SetValueComparer(comparer);
});
}
Notes
Les comparaisons de chaînes .NET et les comparaisons de chaînes de base de données peuvent différer en plus de la sensibilité de la casse. Ce modèle fonctionne pour les clés ASCII simples, mais peut échouer pour les clés avec n’importe quel type de caractères spécifiques à la culture. Pour plus d’informations, consultez classements et respect de la casse.
L’exemple précédent n’a pas besoin d’un convertisseur de valeur. Toutefois, un convertisseur peut être utile pour les types de chaînes de base de données de longueur fixe comme char(20)
ou nchar(20)
. Les chaînes de longueur fixe sont rembourrées à leur longueur complète chaque fois qu’une valeur est insérée dans la base de données. Cela signifie qu’une valeur clé de «dotnet
» sera lue à partir de la base de données sous la forme «dotnet..............
», où .
représente un espace. Cela ne comparera pas correctement les valeurs de clé qui ne sont pas rembourrées.
Un convertisseur de valeurs peut être utilisé pour découper le remplissage lors de la lecture des valeurs de clé. Cela peut être combiné avec le comparateur de valeurs dans l’exemple précédent pour comparer correctement les clés ASCII qui ne respectent pas la casse fixe. Par exemple :
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
var converter = new ValueConverter<string, string>(
v => v,
v => v.Trim());
var comparer = new ValueComparer<string>(
(l, r) => string.Equals(l, r, StringComparison.OrdinalIgnoreCase),
v => v.ToUpper().GetHashCode(),
v => v);
modelBuilder.Entity<Blog>()
.Property(e => e.Id)
.HasColumnType("char(20)")
.HasConversion(converter, comparer);
modelBuilder.Entity<Post>(
b =>
{
b.Property(e => e.Id).HasColumnType("char(20)").HasConversion(converter, comparer);
b.Property(e => e.BlogId).HasColumnType("char(20)").HasConversion(converter, comparer);
});
}
Les convertisseurs de valeurs peuvent être utilisés pour chiffrer les valeurs de propriété avant de les envoyer à la base de données, puis de les déchiffrer en sortie. Par exemple, en utilisant l’inversion de chaîne comme substitut d’un algorithme de chiffrement réel :
modelBuilder.Entity<User>().Property(e => e.Password).HasConversion(
v => new string(v.Reverse().ToArray()),
v => new string(v.Reverse().ToArray()));
Notes
Il n’existe actuellement aucun moyen d’obtenir une référence à l’état DbContext actuel ou à un autre état de session, à partir d’un convertisseur de valeurs. Cela limite les types de chiffrement qui peuvent être utilisés. Votez pour problème GitHub #11597 de supprimer cette limitation.
Avertissement
Veillez à comprendre toutes les implications si vous déployez votre propre chiffrement pour protéger les données sensibles. Envisagez plutôt d’utiliser des mécanismes de chiffrement prédéfini, tels que Always Encrypted sur SQL Server.
Il existe quelques limitations connues du système de conversion de valeur :
null
ne peut pas être convertie. Votez (👍) pour problème GitHub #13850 si c’est quelque chose dont vous avez besoin.La suppression de ces limitations est envisagée pour les futures versions.
Commentaires sur .NET
.NET est un projet open source. Sélectionnez un lien pour fournir des commentaires :
Entrainement
Module
Convertir des types de données à l’aide de techniques de cast et de conversion en C# - Training
Explorez l’utilisation de techniques C# pour les casts et les conversions.