From f17116c8132dd9149fd99fcc519dc36ee53c5503 Mon Sep 17 00:00:00 2001 From: Maxwell Weru <mburumaxwell@gmail.com> Date: Sat, 17 Aug 2024 17:13:31 +0300 Subject: [PATCH] Allow conversion of `Etag` in EFCore to `byte[]`, `int`, `uint`, `long`, or `ulong` (#306) Depending on the database type, the row version may have different types. SQL Server uses `byte[]` for row version, Postgres uses `uint` for `xid`/`xmin`, while the new Mongo driver for EFCore can use `int`, `uint`, `long`, or `ulong` since it is developer driven. This also allows a concurrency setups that are not not entirely database driven. --- .../Converters/EtagConverter.cs | 42 +++++++++++- .../ModelConfigurationBuilderExtensions.cs | 50 ++++++++++++++- .../Extensions/PropertyBuilderExtensions.cs | 64 ++++++++++++++++++- 3 files changed, 148 insertions(+), 8 deletions(-) diff --git a/src/Tingle.Extensions.EntityFrameworkCore/Converters/EtagConverter.cs b/src/Tingle.Extensions.EntityFrameworkCore/Converters/EtagConverter.cs index a61e9caa..c43d7580 100644 --- a/src/Tingle.Extensions.EntityFrameworkCore/Converters/EtagConverter.cs +++ b/src/Tingle.Extensions.EntityFrameworkCore/Converters/EtagConverter.cs @@ -5,11 +5,47 @@ namespace Tingle.Extensions.EntityFrameworkCore.Converters; /// -public class EtagConverter : ValueConverter<Etag, byte[]> +public class EtagToBytesConverter : ValueConverter<Etag, byte[]> { /// - public EtagConverter() : base(convertToProviderExpression: v => v.ToByteArray(), - convertFromProviderExpression: v => v == null ? default : new Etag(v)) + public EtagToBytesConverter() : base(convertToProviderExpression: v => v.ToByteArray(), + convertFromProviderExpression: v => v == null ? default : new Etag(v)) + { } +} + +/// +public class EtagToInt32Converter : ValueConverter<Etag, int> +{ + /// + public EtagToInt32Converter() : base(convertToProviderExpression: v => Convert.ToInt32((ulong)v), + convertFromProviderExpression: v => new Etag(Convert.ToUInt64(v))) + { } +} + +/// +public class EtagToUInt32Converter : ValueConverter<Etag, uint> +{ + /// + public EtagToUInt32Converter() : base(convertToProviderExpression: v => Convert.ToUInt32((ulong)v), + convertFromProviderExpression: v => new Etag(v)) + { } +} + +/// +public class EtagToInt64Converter : ValueConverter<Etag, long> +{ + /// + public EtagToInt64Converter() : base(convertToProviderExpression: v => Convert.ToInt64((ulong)v), + convertFromProviderExpression: v => new Etag(Convert.ToUInt64(v))) + { } +} + +/// +public class EtagToUInt64Converter : ValueConverter<Etag, ulong> +{ + /// + public EtagToUInt64Converter() : base(convertToProviderExpression: v => (ulong)v, + convertFromProviderExpression: v => new Etag(v)) { } } diff --git a/src/Tingle.Extensions.EntityFrameworkCore/Extensions/ModelConfigurationBuilderExtensions.cs b/src/Tingle.Extensions.EntityFrameworkCore/Extensions/ModelConfigurationBuilderExtensions.cs index 959cf009..22bd9b3c 100644 --- a/src/Tingle.Extensions.EntityFrameworkCore/Extensions/ModelConfigurationBuilderExtensions.cs +++ b/src/Tingle.Extensions.EntityFrameworkCore/Extensions/ModelConfigurationBuilderExtensions.cs @@ -17,14 +17,58 @@ namespace Microsoft.EntityFrameworkCore; public static class ModelConfigurationBuilderExtensions { /// <summary> - /// Add fields of type <see cref="Etag"/> to be converted using <see cref="EtagConverter"/>. + /// Add fields of type <see cref="Etag"/> to be converted to a <see cref="T:byte[]"/>. /// </summary> /// <param name="configurationBuilder">The <see cref="ModelConfigurationBuilder"/> to use.</param> - public static void AddEtagConventions(this ModelConfigurationBuilder configurationBuilder) + public static void AddEtagToBytesConventions(this ModelConfigurationBuilder configurationBuilder) { ArgumentNullException.ThrowIfNull(configurationBuilder); - configurationBuilder.Properties<Etag>().HaveConversion<EtagConverter, EtagComparer>(); + configurationBuilder.Properties<Etag>().HaveConversion<EtagToBytesConverter, EtagComparer>(); + } + + /// <summary> + /// Add fields of type <see cref="Etag"/> to be converted to a <see cref="uint"/>. + /// </summary> + /// <param name="configurationBuilder">The <see cref="ModelConfigurationBuilder"/> to use.</param> + public static void AddEtagToInt32Conventions(this ModelConfigurationBuilder configurationBuilder) + { + ArgumentNullException.ThrowIfNull(configurationBuilder); + + configurationBuilder.Properties<Etag>().HaveConversion<EtagToInt32Converter, EtagComparer>(); + } + + /// <summary> + /// Add fields of type <see cref="Etag"/> to be converted to a <see cref="int"/>. + /// </summary> + /// <param name="configurationBuilder">The <see cref="ModelConfigurationBuilder"/> to use.</param> + public static void AddEtagToUInt32Conventions(this ModelConfigurationBuilder configurationBuilder) + { + ArgumentNullException.ThrowIfNull(configurationBuilder); + + configurationBuilder.Properties<Etag>().HaveConversion<EtagToUInt32Converter, EtagComparer>(); + } + + /// <summary> + /// Add fields of type <see cref="Etag"/> to be converted to a <see cref="long"/>. + /// </summary> + /// <param name="configurationBuilder">The <see cref="ModelConfigurationBuilder"/> to use.</param> + public static void AddEtagToInt64Conventions(this ModelConfigurationBuilder configurationBuilder) + { + ArgumentNullException.ThrowIfNull(configurationBuilder); + + configurationBuilder.Properties<Etag>().HaveConversion<EtagToInt64Converter, EtagComparer>(); + } + + /// <summary> + /// Add fields of type <see cref="Etag"/> to be converted to a <see cref="ulong"/>. + /// </summary> + /// <param name="configurationBuilder">The <see cref="ModelConfigurationBuilder"/> to use.</param> + public static void AddEtagToUInt64Conventions(this ModelConfigurationBuilder configurationBuilder) + { + ArgumentNullException.ThrowIfNull(configurationBuilder); + + configurationBuilder.Properties<Etag>().HaveConversion<EtagToUInt64Converter, EtagComparer>(); } /// <summary> diff --git a/src/Tingle.Extensions.EntityFrameworkCore/Extensions/PropertyBuilderExtensions.cs b/src/Tingle.Extensions.EntityFrameworkCore/Extensions/PropertyBuilderExtensions.cs index 3763572a..4a850dbf 100644 --- a/src/Tingle.Extensions.EntityFrameworkCore/Extensions/PropertyBuilderExtensions.cs +++ b/src/Tingle.Extensions.EntityFrameworkCore/Extensions/PropertyBuilderExtensions.cs @@ -22,11 +22,71 @@ public static class PropertyBuilderExtensions /// </summary> /// <param name="propertyBuilder">The <see cref="PropertyBuilder{TProperty}"/> to extend.</param> /// <returns></returns> - public static PropertyBuilder<Etag> HasEtagConversion(this PropertyBuilder<Etag> propertyBuilder) + public static PropertyBuilder<Etag> HasEtagToBytesConversion(this PropertyBuilder<Etag> propertyBuilder) { ArgumentNullException.ThrowIfNull(propertyBuilder); - propertyBuilder.HasConversion(new EtagConverter()); + propertyBuilder.HasConversion(new EtagToBytesConverter()); + propertyBuilder.Metadata.SetValueComparer(new EtagComparer()); + + return propertyBuilder; + } + + /// <summary> + /// Attach conversion of property to/from <see cref="Etag"/> stored in the database as a <see cref="int"/>. + /// </summary> + /// <param name="propertyBuilder">The <see cref="PropertyBuilder{TProperty}"/> to extend.</param> + /// <returns></returns> + public static PropertyBuilder<Etag> HasEtagToInt32Conversion(this PropertyBuilder<Etag> propertyBuilder) + { + ArgumentNullException.ThrowIfNull(propertyBuilder); + + propertyBuilder.HasConversion(new EtagToInt32Converter()); + propertyBuilder.Metadata.SetValueComparer(new EtagComparer()); + + return propertyBuilder; + } + + /// <summary> + /// Attach conversion of property to/from <see cref="Etag"/> stored in the database as a <see cref="uint"/>. + /// </summary> + /// <param name="propertyBuilder">The <see cref="PropertyBuilder{TProperty}"/> to extend.</param> + /// <returns></returns> + public static PropertyBuilder<Etag> HasEtagToUInt32Conversion(this PropertyBuilder<Etag> propertyBuilder) + { + ArgumentNullException.ThrowIfNull(propertyBuilder); + + propertyBuilder.HasConversion(new EtagToUInt32Converter()); + propertyBuilder.Metadata.SetValueComparer(new EtagComparer()); + + return propertyBuilder; + } + + /// <summary> + /// Attach conversion of property to/from <see cref="Etag"/> stored in the database as a <see cref="long"/>. + /// </summary> + /// <param name="propertyBuilder">The <see cref="PropertyBuilder{TProperty}"/> to extend.</param> + /// <returns></returns> + public static PropertyBuilder<Etag> HasEtagToInt64Conversion(this PropertyBuilder<Etag> propertyBuilder) + { + ArgumentNullException.ThrowIfNull(propertyBuilder); + + propertyBuilder.HasConversion(new EtagToInt64Converter()); + propertyBuilder.Metadata.SetValueComparer(new EtagComparer()); + + return propertyBuilder; + } + + /// <summary> + /// Attach conversion of property to/from <see cref="Etag"/> stored in the database as a <see cref="ulong"/>. + /// </summary> + /// <param name="propertyBuilder">The <see cref="PropertyBuilder{TProperty}"/> to extend.</param> + /// <returns></returns> + public static PropertyBuilder<Etag> HasEtagToUInt64Conversion(this PropertyBuilder<Etag> propertyBuilder) + { + ArgumentNullException.ThrowIfNull(propertyBuilder); + + propertyBuilder.HasConversion(new EtagToUInt64Converter()); propertyBuilder.Metadata.SetValueComparer(new EtagComparer()); return propertyBuilder;