EF CORE 2.1 HasConversion on all properties of type datetime
This wouldn't fit in the comment section so I added an answer. Here is the code I'm using to convert lists and dictionaries.
foreach (var entity in builder.Model.GetEntityTypes())
{
foreach (var property in entity.ClrType.GetProperties())
{
if (property.PropertyType == typeof(List<string>))
{
builder.Entity(entity.Name).Property(property.Name).HasConversion(new ValueConverter<List<string>, string>(v => v.ToJson(), v => v.FromJson<List<string>>())).HasColumnType("json");
}
else if (property.PropertyType == typeof(Dictionary<string, string>))
{
builder.Entity(entity.Name).Property(property.Name).HasConversion(new ValueConverter<Dictionary<string, string>, string>(v => v.ToJson(), v => v.FromJson<Dictionary<string, string>>())).HasColumnType("json");
}
else if (property.PropertyType == typeof(List<List<string>>))
{
builder.Entity(entity.Name).Property(property.Name).HasConversion(new ValueConverter<List<List<string>>, string>(v => v.ToJson(), v => v.FromJson<List<List<string>>>())).HasColumnType("json");
}
else if (property.PropertyType == typeof(bool))
{
builder.Entity(entity.Name).Property(property.Name).HasConversion(new BoolToZeroOneConverter<short>());
}
}
}
Just thought I could throw in my two cents
There's an issue for that opened here: https://github.com/aspnet/EntityFrameworkCore/issues/10784
Ivan's solution will work for simple types like DateTime
etc. but it will crash when using user-defined types when calling entityType.GetProperties()
this is described better in the issue in the link above. To make it work with user-defined types you will have to use entityType.ClrType.GetProperties()
.
For universal workaround you can use this extension method:
public static class ModelBuilderExtensions
{
public static ModelBuilder UseValueConverterForType<T>(this ModelBuilder modelBuilder, ValueConverter converter)
{
return modelBuilder.UseValueConverterForType(typeof(T), converter);
}
public static ModelBuilder UseValueConverterForType(this ModelBuilder modelBuilder, Type type, ValueConverter converter)
{
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
var properties = entityType.ClrType.GetProperties().Where(p => p.PropertyType == type);
foreach (var property in properties)
{
modelBuilder.Entity(entityType.Name).Property(property.Name)
.HasConversion(converter);
}
}
return modelBuilder;
}
}
As of EF core v6.0.0-preview6
, there is a more elegant solution to register a ValueConverter globally. In this example I use a custom ISO8601 converter that converts to/from ISO8601 in UTC, always appending the Z
in the end:
public class DateTimeToIso8601StringConverter : ValueConverter<DateTime, string>
{
public DateTimeToIso8601StringConverter() : base(Serialize, Deserialize, null)
{
}
static Expression<Func<string, DateTime>> Deserialize = x => DateTime.Parse(x).ToUniversalTime();
static Expression<Func<DateTime, string>> Serialize = x => x.ToString("o", System.Globalization.CultureInfo.InvariantCulture);
}
and inside your DbContext
class:
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
configurationBuilder.Properties<DateTime>().HaveConversion<DateTimeToIso8601StringConverter>();
}
This avoids having to specify the converter for every model property individually.
Excerpt from EF Core 2.1 Value Conversions documentation topic:
There is currently no way to specify in one place that every property of a given type must use the same value converter. This feature will be considered for a future release.
Until then, you can use the typical loop at the end of the OnModelCreating
override where all entity types and properties are discovered:
var dateTimeConverter = new ValueConverter<DateTime, DateTime>(
v => v, v => DateTime.SpecifyKind(v, DateTimeKind.Utc));
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
foreach (var property in entityType.GetProperties())
{
if (property.ClrType == typeof(DateTime) || property.ClrType == typeof(DateTime?))
property.SetValueConverter(dateTimeConverter);
}
}