C# 有没有一种简单的方法可以使用数据注释或自定义类型将SQL中存储为字符串的值用作EF中的日期时间?

C# 有没有一种简单的方法可以使用数据注释或自定义类型将SQL中存储为字符串的值用作EF中的日期时间?,c#,entity-framework-6,C#,Entity Framework 6,我有一个数据库,其中所有日期和时间分别存储为字符串[yyyyMMdd]和[hhmmss] 我是否可以在POCO类中添加数据注释,以便将其识别为应该的类型 例如: 如果没有,是否有方法定义自定义类型并指定EF应如何在属性值和SQL之间转换数据 注: 我不希望使用私有和公共属性对转换此客户端。我希望基本查询转到数据库,这将阻止 这里有一个类似的问题,有一个我不想寻找的答案:好的,我通过下载和研究EF源代码自己找到了答案。有几个类需要重写。这在我正在使用的EF Core 2.1.2中起作用,因此不保证

我有一个数据库,其中所有日期和时间分别存储为字符串[yyyyMMdd]和[hhmmss]

我是否可以在POCO类中添加数据注释,以便将其识别为应该的类型

例如:

如果没有,是否有方法定义自定义类型并指定EF应如何在属性值和SQL之间转换数据

注: 我不希望使用私有和公共属性对转换此客户端。我希望基本查询转到数据库,这将阻止


这里有一个类似的问题,有一个我不想寻找的答案:

好的,我通过下载和研究EF源代码自己找到了答案。有几个类需要重写。这在我正在使用的EF Core 2.1.2中起作用,因此不保证较旧或较新的版本,因为API声明这些类可能会更改,但如果有问题,希望只会进行小的更改

因此,下面将启用仅表示POCO中日期的DateTime属性,以便在实际SQL数据为字符串的情况下使用。为Times和boolean派生额外的类将很简单

需要在字符串和日期之间转换的类:

public class StringDateConverter : ValueConverter<DateTime?, string>
{
    // these can be overridden
    public static string StringDateStorageType = "char(8)";
    public static string StringDateStorageFormat = "yyyyMMdd";
    public static string StringDateEmptyValue = "00000000";

    protected static readonly ConverterMappingHints _defaultHints
        = new ConverterMappingHints(size: 48);

    public StringDateConverter()
        : base(ToString(), ToDateTime(), _defaultHints)
    {
    }

    protected new static Expression<Func<DateTime?, string>> ToString()
        => v => DateToString(v);

    protected static Expression<Func<string, DateTime?>> ToDateTime()
        => v => StringToDate(v);

    private static string DateToString(DateTime? date)
    {
        if (date.HasValue)
            return date.Value.ToString(StringDateStorageFormat);

        return StringDateEmptyValue;
    }

    private static DateTime? StringToDate(string date)
    {
        if (!string.IsNullOrWhiteSpace(date) 
            && !(date == StringDateEmptyValue)
            && DateTime.TryParseExact(date, StringDateStorageFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime result))
            return result;

        return null;
    }
}
然后需要一个类来重写类型映射服务:

public class SqlServerTypeMappingSource : Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerTypeMappingSource
{
    public SqlServerTypeMappingSource(TypeMappingSourceDependencies dependencies, RelationalTypeMappingSourceDependencies relationalDependencies) : base(dependencies, relationalDependencies)
    {
    }

    protected override RelationalTypeMapping FindMapping(in RelationalTypeMappingInfo mappingInfo)
    {
        if (mappingInfo.ClrType == typeof(DateTime) && mappingInfo.StoreTypeName == StringDateConverter.StringDateStorageType)
            return new SqlServerDateTypeMapping();

        return base.FindMapping(mappingInfo);
    }
}
可以在DbContext的OnConfigurang方法中替换EF默认映射服务:

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        base.OnConfiguring(optionsBuilder);
        optionsBuilder.ReplaceService<IRelationalTypeMappingSource, CommonComponents.Data.SqlServerTypeMappingSource>();
        optionsBuilder.UseSqlServer(Data.Configuration.ConnectionString);
    }

添加映射到真实数据库列的字符串类型的标准属性。然后添加一个额外的属性类型DateTime,它只从string属性读取和写入数据。然后,您的C代码可以处理正确键入的DateTime,但会自动使字符串保持最新。请注意,如果是我,我将非常努力地解决根本问题,即数据库中的数据类型不正确。粗略估计大约有1000个表。否则我会推它为什么不写一些存储过程来访问数据?在那里,您可以进行必要的castinghanks,但是EF将该类视为一个实体,并抛出一个没有主键的异常。即使没有,我也看不到指定列名的方法,因为这个类需要用于所有日期。这只是一段代码,您应该添加到有日期属性而不是独立类的类中。这甚至不是回答我问题的尝试!
public class SqlServerDateTypeMapping : Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerDateTimeTypeMapping
{
    public SqlServerDateTypeMapping() 
        : this(StringDateConverter.StringDateStorageType, System.Data.DbType.String)
    {
    }

    public SqlServerDateTypeMapping(string storeType, DbType? dbType = null) 
        : base(storeType, dbType)
    {
    }

    protected SqlServerDateTypeMapping(RelationalTypeMappingParameters parameters)
        : base(parameters)
    {
    }

    public override DbType? DbType => System.Data.DbType.String;

    protected override string SqlLiteralFormatString
        => StoreType == StringDateConverter.StringDateStorageType
            ? "'" + StringDateConverter.StringDateStorageFormat + "'"
            : base.SqlLiteralFormatString;

    public override ValueConverter Converter => new StringDateConverter();

    // ensure cloning returns an instance of this class

    public override RelationalTypeMapping Clone(in RelationalTypeMappingInfo mappingInfo)
    {
        return new SqlServerDateTypeMapping();
    }

    public override RelationalTypeMapping Clone(string storeType, int? size)
    {
        return new SqlServerDateTypeMapping();
    }

    public override CoreTypeMapping Clone(ValueConverter converter)
    {
        return new SqlServerDateTypeMapping();
    }
}
public class SqlServerTypeMappingSource : Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerTypeMappingSource
{
    public SqlServerTypeMappingSource(TypeMappingSourceDependencies dependencies, RelationalTypeMappingSourceDependencies relationalDependencies) : base(dependencies, relationalDependencies)
    {
    }

    protected override RelationalTypeMapping FindMapping(in RelationalTypeMappingInfo mappingInfo)
    {
        if (mappingInfo.ClrType == typeof(DateTime) && mappingInfo.StoreTypeName == StringDateConverter.StringDateStorageType)
            return new SqlServerDateTypeMapping();

        return base.FindMapping(mappingInfo);
    }
}
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        base.OnConfiguring(optionsBuilder);
        optionsBuilder.ReplaceService<IRelationalTypeMappingSource, CommonComponents.Data.SqlServerTypeMappingSource>();
        optionsBuilder.UseSqlServer(Data.Configuration.ConnectionString);
    }
    [Column(Order = 10, TypeName = "char(8)")]
    public DateTime? SomeDate { get; set; }