EF CORE 2.1 HasConversion 在日期时间类型的所有属性上

Posted

技术标签:

【中文标题】EF CORE 2.1 HasConversion 在日期时间类型的所有属性上【英文标题】:EF CORE 2.1 HasConversion on all properties of type datetime 【发布时间】:2018-06-06 19:06:50 【问题描述】:

我之前在读取实体时使用 DateTimeKindEntityMaterializerSource (Git) 将所有 DateTime 转换为 UTC,因为未指定默认值。

在 EF 核心 2.1 中,DateTimeKindEntityMaterializerSource 不再起作用,但我们实际上可以这样做

         builder
        .Entity<ESDataQuotation>()
        .Property(e => e.CreatedDate)
        .HasConversion(v => v, v => DateTime.SpecifyKind(v, DateTimeKind.Utc));

但是,我有很多 DateTime 属性,我想知道是否有办法对 DateTime 类型的所有属性进行转换。

【问题讨论】:

【参考方案1】:

摘自 EF Core 2.1 Value Conversions 文档主题:

目前无法在一个地方指定给定类型的每个属性都必须使用相同的值转换器。未来版本将考虑此功能。

在此之前,您可以在 OnModelCreating 覆盖的末尾使用典型循环,在该循环中发现所有实体类型和属性:

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

【讨论】:

如果您在 DbContext optionsBuilder 上设置了 warningConfigurationBuilder.Throw(RelationalEventId.QueryClientEvaluationWarning),这将发出警告:“使用 ValueConverter 'ValueConverter'。检查生成的 SQL 是否正确,并考虑在内存中评估目标表达式。”但是不用担心生成的 SQL 查询是正确的,所以你应该忽略这个警告。 从 EFcore 6 开始,这个问题不再有利; “未来版本”在这里,请参阅我关于如何为所有类型的模型属性全局注册 ValueConverter 的答案。 @Dynalon 这个评论是给我的还是给读者的?我知道如何在 EFC6 中做到这一点,无需查看您的答案。此外,所有这些现在都在 EFC 文档中。【参考方案2】:

只是觉得我可以投入两分钱

这里有一个问题:https://github.com/aspnet/EntityFrameworkCore/issues/10784

Ivan 的解决方案适用于 DateTime 等简单类型,但在调用 entityType.GetProperties() 时使用用户定义类型时会崩溃,这在上面链接中的问题中有更好的描述。要使其与用户定义的类型一起使用,您必须使用 entityType.ClrType.GetProperties()

对于通用解决方法,您可以使用此扩展方法:

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;
    

【讨论】:

这是唯一在尝试使用列表和字典时也有效的方法。 @agritton 你能在这里分享你处理列表和字典的解决方案吗? @mreyeros 确定这里是代码。我正在使用它将我的列表和字典转换为 json 列。实际上它不适合评论,所以我将添加一个答案。 我认为这是正确的答案。谢谢,@康拉德!【参考方案3】:

这不适合评论部分,所以我添加了一个答案。这是我用来转换列表和字典的代码。

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

【讨论】:

【参考方案4】:

从 EF 核心 v6.0.0-preview6 开始,有一个更优雅的解决方案可以全局注册 ValueConverter。在这个例子中,我使用了一个自定义的 ISO8601 转换器,它在 UTC 中与 ISO8601 相互转换,最后总是附加 Z

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

在你的 DbContext 类中:

protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)

    configurationBuilder.Properties<DateTime>().HaveConversion<DateTimeToIso8601StringConverter>();

这避免了为每个模型属性单独指定转换器。

【讨论】:

以上是关于EF CORE 2.1 HasConversion 在日期时间类型的所有属性上的主要内容,如果未能解决你的问题,请参考以下文章

EF Core 2.1变化

EF Core 2.1 中的新增功能

如何在 Core 2.1 中引用 EF(Database First Approach)项目程序集

EF Core 2.1 中的 Eager loadingExplicit loading和LazyLoading (转自MSDN)

EF Core 2.1:具有一对多关系的自引用实体生成附加列

Dotnet EF Core 2.1 在查询小数属性时抛出 QueryClientEvaluationWarning