From 1c49f6e567c50f037dc4677565b3333333ab0b40 Mon Sep 17 00:00:00 2001 From: Pawel Gerr Date: Mon, 11 Jan 2021 19:58:06 +0100 Subject: [PATCH] Extension method for easier adding type converters to all corresponding enum/value type members --- .../Product.cs | 21 ++++-- .../ProductsDbContext.cs | 12 +-- .../Program.cs | 16 +--- .../ValueTypeValueConverterFactory.cs | 2 +- .../Extensions/ModelBuilderExtensions.cs | 75 +++++++++++++++++++ 5 files changed, 93 insertions(+), 33 deletions(-) create mode 100644 src/Thinktecture.Runtime.Extensions.EntityFrameworkCore/Extensions/ModelBuilderExtensions.cs diff --git a/samples/Thinktecture.Runtime.Extensions.EntityFrameworkCore.Samples/Product.cs b/samples/Thinktecture.Runtime.Extensions.EntityFrameworkCore.Samples/Product.cs index b199398c..7acc959e 100644 --- a/samples/Thinktecture.Runtime.Extensions.EntityFrameworkCore.Samples/Product.cs +++ b/samples/Thinktecture.Runtime.Extensions.EntityFrameworkCore.Samples/Product.cs @@ -2,14 +2,19 @@ using Thinktecture.EnumLikeClasses; using Thinktecture.ValueTypes; -#nullable disable - namespace Thinktecture { - public class Product - { - public Guid Id { get; set; } - public ProductName Name { get; set; } - public ProductCategory Category { get; set; } - } + public class Product + { + public Guid Id { get; private set; } + public ProductName Name { get; private set; } + public ProductCategory Category { get; private set; } + + public Product(Guid id, ProductName name, ProductCategory category) + { + Id = id; + Name = name; + Category = category; + } + } } diff --git a/samples/Thinktecture.Runtime.Extensions.EntityFrameworkCore.Samples/ProductsDbContext.cs b/samples/Thinktecture.Runtime.Extensions.EntityFrameworkCore.Samples/ProductsDbContext.cs index da492713..2ecfcb12 100644 --- a/samples/Thinktecture.Runtime.Extensions.EntityFrameworkCore.Samples/ProductsDbContext.cs +++ b/samples/Thinktecture.Runtime.Extensions.EntityFrameworkCore.Samples/ProductsDbContext.cs @@ -1,7 +1,4 @@ using Microsoft.EntityFrameworkCore; -using Thinktecture.EntityFrameworkCore.Storage.ValueConversion; -using Thinktecture.EnumLikeClasses; -using Thinktecture.ValueTypes; namespace Thinktecture { @@ -20,14 +17,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); - modelBuilder.Entity(builder => - { - builder.Property(p => p.Category) - .HasConversion(ValueTypeValueConverterFactory.Create(true)); - - builder.Property(p => p.Name) - .HasConversion(ValueTypeValueConverterFactory.Create()); - }); + modelBuilder.AddEnumAndValueTypeConverters(true); } } } diff --git a/samples/Thinktecture.Runtime.Extensions.EntityFrameworkCore.Samples/Program.cs b/samples/Thinktecture.Runtime.Extensions.EntityFrameworkCore.Samples/Program.cs index 0651a8c2..23623b8e 100644 --- a/samples/Thinktecture.Runtime.Extensions.EntityFrameworkCore.Samples/Program.cs +++ b/samples/Thinktecture.Runtime.Extensions.EntityFrameworkCore.Samples/Program.cs @@ -17,28 +17,18 @@ public static void Main() using var ctx = CreateContext(loggerFactory); - InsertProduct(ctx, new Product - { - Id = Guid.NewGuid(), - Name = ProductName.Create("Apple"), - Category = ProductCategory.Fruits - }); + InsertProduct(ctx, new Product(Guid.NewGuid(), ProductName.Create("Apple"), ProductCategory.Fruits)); try { - InsertProduct(ctx, new Product - { - Id = Guid.NewGuid(), - Name = ProductName.Create("Pear"), - Category = ProductCategory.Get("Invalid Category") - }); + InsertProduct(ctx, new Product(Guid.NewGuid(), ProductName.Create("Pear"), ProductCategory.Get("Invalid Category"))); } catch (DbUpdateException) { logger.LogError("Error during persistence of invalid category."); } - var products = ctx.Products.Where(p => p.Category == ProductCategory.Fruits).ToList(); + var products = ctx.Products.AsNoTracking().Where(p => p.Category == ProductCategory.Fruits).ToList(); logger.LogInformation("Loaded products: {@products}", products); } diff --git a/src/Thinktecture.Runtime.Extensions.EntityFrameworkCore/EntityFrameworkCore/Storage/ValueConversion/ValueTypeValueConverterFactory.cs b/src/Thinktecture.Runtime.Extensions.EntityFrameworkCore/EntityFrameworkCore/Storage/ValueConversion/ValueTypeValueConverterFactory.cs index c4ade27c..2c0f1120 100644 --- a/src/Thinktecture.Runtime.Extensions.EntityFrameworkCore/EntityFrameworkCore/Storage/ValueConversion/ValueTypeValueConverterFactory.cs +++ b/src/Thinktecture.Runtime.Extensions.EntityFrameworkCore/EntityFrameworkCore/Storage/ValueConversion/ValueTypeValueConverterFactory.cs @@ -40,7 +40,7 @@ public static ValueConverter Create(bool validateOnWri /// Creates a value converter for . /// /// Type of the value type/enum. - /// In case of an enum, ensures that the item is valid before writing it to database. + /// In case of an , ensures that the item is valid before writing it to database. /// An instance of > public static ValueConverter Create(Type type, bool validateOnWrite) { diff --git a/src/Thinktecture.Runtime.Extensions.EntityFrameworkCore/Extensions/ModelBuilderExtensions.cs b/src/Thinktecture.Runtime.Extensions.EntityFrameworkCore/Extensions/ModelBuilderExtensions.cs new file mode 100644 index 00000000..cb300baa --- /dev/null +++ b/src/Thinktecture.Runtime.Extensions.EntityFrameworkCore/Extensions/ModelBuilderExtensions.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata; +using Thinktecture.EntityFrameworkCore.Storage.ValueConversion; + +// ReSharper disable once CheckNamespace +namespace Thinktecture +{ + /// + /// Extensions for . + /// + public static class ModelBuilderExtensions + { + /// + /// Adds value converter to all properties implementing / + /// and having the . + /// Properties with a value provider are skipped. + /// + /// EF model builder. + /// In case of an , ensures that the item is valid before writing it to database. + /// If is null. + public static void AddEnumAndValueTypeConverters( + this ModelBuilder modelBuilder, + bool validateOnWrite) + { + if (modelBuilder is null) + throw new ArgumentNullException(nameof(modelBuilder)); + + foreach (var entity in modelBuilder.Model.GetEntityTypes()) + { + AddConverterForScalarProperties(entity, validateOnWrite); + AddConvertersForNavigations(entity, modelBuilder, validateOnWrite); + } + } + + private static void AddConvertersForNavigations(IMutableEntityType entity, ModelBuilder modelBuilder, bool validateOnWrite) + { + List? navigationsToConvert = null; + + foreach (var navigation in entity.GetNavigations()) + { + if (ValueTypeMetadataLookup.Find(navigation.ClrType) is not null) + (navigationsToConvert ??= new List()).Add(navigation); + } + + if (navigationsToConvert is not null) + { + foreach (var navigation in navigationsToConvert) + { + var valueConverter = ValueTypeValueConverterFactory.Create(navigation.ClrType, validateOnWrite); + modelBuilder.Entity(entity.ClrType).Property(navigation.Name).HasConversion(valueConverter); + } + } + } + + private static void AddConverterForScalarProperties(IMutableEntityType entity, bool validateOnWrite) + { + foreach (var property in entity.GetProperties()) + { + var valueConverter = property.GetValueConverter(); + + if (valueConverter is not null) + continue; + + if (ValueTypeMetadataLookup.Find(property.ClrType) is null) + continue; + + valueConverter = ValueTypeValueConverterFactory.Create(property.ClrType, validateOnWrite); + property.SetValueConverter(valueConverter); + } + } + } +}