Skip to content

Commit

Permalink
Extension method for easier adding type converters to all correspondi…
Browse files Browse the repository at this point in the history
…ng enum/value type members
  • Loading branch information
PawelGerr committed Jan 11, 2021
1 parent 18c1dd2 commit 1c49f6e
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
using Microsoft.EntityFrameworkCore;
using Thinktecture.EntityFrameworkCore.Storage.ValueConversion;
using Thinktecture.EnumLikeClasses;
using Thinktecture.ValueTypes;

namespace Thinktecture
{
Expand All @@ -20,14 +17,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);

modelBuilder.Entity<Product>(builder =>
{
builder.Property(p => p.Category)
.HasConversion(ValueTypeValueConverterFactory.Create<ProductCategory, string>(true));
builder.Property(p => p.Name)
.HasConversion(ValueTypeValueConverterFactory.Create<ProductName, string>());
});
modelBuilder.AddEnumAndValueTypeConverters(true);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public static ValueConverter<TEnum, TKey> Create<TEnum, TKey>(bool validateOnWri
/// Creates a value converter for <see cref="IValidatableEnum{TKey}"/>.
/// </summary>
/// <param name="type">Type of the value type/enum.</param>
/// <param name="validateOnWrite">In case of an enum, ensures that the item is valid before writing it to database.</param>
/// <param name="validateOnWrite">In case of an <see cref="IValidatableEnum{TKey}"/>, ensures that the item is valid before writing it to database.</param>
/// <returns>An instance of <see cref="ValueConverter"/>></returns>
public static ValueConverter Create(Type type, bool validateOnWrite)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Extensions for <see cref="ModelBuilder"/>.
/// </summary>
public static class ModelBuilderExtensions
{
/// <summary>
/// Adds value converter to all properties implementing <see cref="IEnum{TKey}"/>/<see cref="IValidatableEnum{TKey}"/>
/// and having the <see cref="ValueTypeAttribute"/>.
/// Properties with a value provider are skipped.
/// </summary>
/// <param name="modelBuilder">EF model builder.</param>
/// <param name="validateOnWrite">In case of an <see cref="IValidatableEnum{TKey}"/>, ensures that the item is valid before writing it to database.</param>
/// <exception cref="ArgumentNullException">If <paramref name="modelBuilder"/> is <c>null</c>.</exception>
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<IMutableNavigation>? navigationsToConvert = null;

foreach (var navigation in entity.GetNavigations())
{
if (ValueTypeMetadataLookup.Find(navigation.ClrType) is not null)
(navigationsToConvert ??= new List<IMutableNavigation>()).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);
}
}
}
}

0 comments on commit 1c49f6e

Please sign in to comment.