Last active
June 17, 2019 15:32
-
-
Save warappa/f1fa1a46857eb9c144ced518f250dcbd to your computer and use it in GitHub Desktop.
Using global value converters with Entity Framework Core 2.2.5. Based on Andrew Lock's StronglyTypedIdValueConverterSelector https://andrewlock.net/strongly-typed-ids-in-ef-core-using-strongly-typed-entity-ids-to-avoid-primitive-obsession-part-4/
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Microsoft.EntityFrameworkCore; | |
namespace GlobalValueConverterSample | |
{ | |
public class AppDbContext : DbContext | |
{ | |
public AppDbContext(DbContextOptions<AppDbContext> options) | |
: base(options) | |
{ | |
} | |
protected override void OnModelCreating(ModelBuilder modelBuilder) | |
{ | |
modelBuilder.Owned<ArticleId>() | |
.HasConversion(x => x.Value, x => new ArticleId(x)); | |
base.OnModelCreating(modelBuilder); | |
} | |
} | |
public class ArticleId | |
{ | |
public Guid Value { get; private set; } | |
public ArticleId(Guid value) | |
{ | |
Value = value; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class ConverterValueGenerator : ValueGenerator<object> | |
{ | |
private ValueConverter converter; | |
public ConverterValueGenerator(ValueConverter converter) | |
{ | |
this.converter = converter; | |
} | |
public override object Next(EntityEntry entry) | |
{ | |
return converter.ConvertFromProvider(Guid.NewGuid()); | |
} | |
public override bool GeneratesTemporaryValues => false; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class ConverterValueGeneratorSelector : ValueGeneratorSelector | |
{ | |
// The dictionary in the base type is private, so we need our own one here. | |
private static readonly ConcurrentDictionary<Type, ValueGenerator> _generators | |
= new ConcurrentDictionary<Type, ValueGenerator>(); | |
public ConverterValueGeneratorSelector(ValueGeneratorSelectorDependencies dependencies) | |
: base(dependencies) | |
{ | |
} | |
public override ValueGenerator Select(IProperty property, IEntityType entityType) | |
{ | |
if (!_generators.TryGetValue(property.ClrType, out var generator)) | |
{ | |
if (GlobalValueConverterSelector.TryGetConverter(property.ClrType, out var converter)) | |
{ | |
generator = new ConverterValueGenerator(converter); | |
_generators.TryAdd(property.ClrType, generator); | |
return generator; | |
} | |
} | |
else | |
{ | |
generator = base.Select(property, entityType); | |
_generators.TryAdd(property.ClrType, generator); | |
} | |
return generator; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Microsoft.EntityFrameworkCore; | |
using Microsoft.EntityFrameworkCore.Storage.ValueConversion; | |
namespace GlobalValueConverterSample | |
{ | |
public static class DbContextOptionsBuilderValueConverterExtensions | |
{ | |
public static DbContextOptionsBuilder UseGlobalValueConverterSelector(this DbContextOptionsBuilder builder) | |
{ | |
builder.ReplaceService<IValueConverterSelector, GlobalValueConverterSelector>(); | |
builder.ReplaceService<IValueGeneratorSelector, ConverterValueGeneratorSelector>(); | |
return builder; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Concurrent; | |
using System.Collections.Generic; | |
using Microsoft.EntityFrameworkCore.Storage.ValueConversion; | |
namespace GlobalValueConverterSample | |
{ | |
public class GlobalValueConverterSelector : ValueConverterSelector | |
{ | |
// The dictionary in the base type is private, so we need our own one here. | |
private static readonly ConcurrentDictionary<(Type ModelClrType, Type ProviderClrType), ValueConverterInfo> _converters | |
= new ConcurrentDictionary<(Type ModelClrType, Type ProviderClrType), ValueConverterInfo>(); | |
public GlobalValueConverterSelector(ValueConverterSelectorDependencies dependencies) : base(dependencies) | |
{ } | |
public override IEnumerable<ValueConverterInfo> Select(Type modelClrType, Type providerClrType = null) | |
{ | |
var baseConverters = base.Select(modelClrType, providerClrType); | |
foreach (var converter in baseConverters) | |
{ | |
yield return converter; | |
} | |
// Extract the "real" type T from Nullable<T> if required | |
var underlyingModelType = UnwrapNullableType(modelClrType); | |
var underlyingProviderType = UnwrapNullableType(providerClrType); | |
if (_converters.TryGetValue((underlyingModelType, underlyingProviderType), out var globalConverter)) | |
{ | |
yield return globalConverter; | |
} | |
} | |
private static Type UnwrapNullableType(Type type) | |
{ | |
if (type is null) { return null; } | |
return Nullable.GetUnderlyingType(type) ?? type; | |
} | |
public static void RegisterValueConverter<TValueConverter>(Type modelClrType, Type providerClrType) | |
where TValueConverter : ValueConverter | |
{ | |
Func<ValueConverterInfo, ValueConverter> factory = | |
info => (ValueConverter)Activator.CreateInstance(typeof(TValueConverter), info.MappingHints); | |
_converters.TryAdd((modelClrType, providerClrType), new ValueConverterInfo(modelClrType, typeof(Guid), factory)); | |
} | |
public static void RegisterValueConverter<TProperty, TProvider>(ValueConverter<TProperty, TProvider> valueConverter) | |
{ | |
Func<ValueConverterInfo, ValueConverter> factory = | |
info => valueConverter; | |
_converters.TryAdd((typeof(TProperty), typeof(TProvider)), new ValueConverterInfo(typeof(TProperty), typeof(TProvider), factory)); | |
// also register for cases where the underlying provider type could not be resolved | |
_converters.TryAdd((typeof(TProperty), null), new ValueConverterInfo(typeof(TProperty), typeof(TProvider), factory)); | |
} | |
public static bool TryGetConverter(Type modelClrType, out ValueConverter converter) | |
{ | |
if (!_converters.TryGetValue((modelClrType, typeof(Guid)), out var valueConverterInfo)) | |
{ | |
converter = null; | |
return false; | |
} | |
converter = valueConverterInfo.Create(); | |
return true; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Microsoft.AspNetCore.Builder; | |
using Microsoft.AspNetCore.Hosting; | |
using Microsoft.AspNetCore.Mvc; | |
using Microsoft.EntityFrameworkCore; | |
using Microsoft.Extensions.Configuration; | |
using Microsoft.Extensions.DependencyInjection; | |
namespace GlobalValueConverterSample | |
{ | |
public class Startup | |
{ | |
public Startup(IConfiguration configuration) | |
{ | |
Configuration = configuration; | |
} | |
public IConfiguration Configuration { get; } | |
// This method gets called by the runtime. Use this method to add services to the container. | |
public void ConfigureServices(IServiceCollection services) | |
{ | |
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); | |
var connectionString = $"Data Source=app.db"; | |
services.AddEntityFrameworkSqlite() | |
.AddDbContext<AppDbContext>(x => | |
{ | |
x.UseSqlite(connectionString); | |
x.UseGlobalValueConverterSelector(); // this line matters | |
}); | |
} | |
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. | |
public void Configure(IApplicationBuilder app, IHostingEnvironment env) | |
{ | |
if (env.IsDevelopment()) | |
{ | |
app.UseDeveloperExceptionPage(); | |
} | |
else | |
{ | |
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. | |
app.UseHsts(); | |
} | |
app.UseHttpsRedirection(); | |
app.UseMvc(); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Linq.Expressions; | |
using Microsoft.EntityFrameworkCore; | |
using Microsoft.EntityFrameworkCore.Metadata.Builders; | |
using Microsoft.EntityFrameworkCore.Storage.ValueConversion; | |
namespace GlobalValueConverterSample | |
{ | |
public static class ValueConverterExtensions | |
{ | |
public static ModelBuilder HasConversion<TProperty, TProvider>( | |
this ModelBuilder modelBuilder, | |
Expression<Func<TProperty, TProvider>> convertToProviderExpression, | |
Expression<Func<TProvider, TProperty>> convertFromProviderExpression) | |
{ | |
var valueConverter = new ValueConverter<TProperty, TProvider>(convertToProviderExpression, convertFromProviderExpression); | |
GlobalValueConverterSelector.RegisterValueConverter(valueConverter); | |
return modelBuilder; | |
} | |
public static OwnedEntityTypeBuilder<TProperty> HasConversion<TProperty, TProvider>( | |
this OwnedEntityTypeBuilder<TProperty> ownedEntityTypeBuilder, | |
Expression<Func<TProperty, TProvider>> convertToProviderExpression, | |
Expression<Func<TProvider, TProperty>> convertFromProviderExpression) | |
{ | |
var valueConverter = new ValueConverter<TProperty, TProvider>(convertToProviderExpression, convertFromProviderExpression); | |
GlobalValueConverterSelector.RegisterValueConverter(valueConverter); | |
return ownedEntityTypeBuilder; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment