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);
    }
}