【问题标题】:How to filter SQL Server DateTime using .NET Core WebAPI with OData v4如何使用带有 OData v4 的 .NET Core WebAPI 过滤 SQL Server DateTime
【发布时间】:2019-09-27 22:19:19
【问题描述】:

我正在使用 OData Query v4 和 SQL Server 2012 运行一个简单的 .NET Core WebApi 应用程序。

这可行,但速度极慢:

GET /api-endpoint?$filter=date(MyDateTimeField) ge 2018-01-01&$top=100

以上 URL 生成的 SQL 查询:

SELECT TOP 100 * FROM MyTable WHERE ((((DATEPART(year, [MyDateTimeField]) * 10000) + (DATEPART(month, [MyDateTimeField]) * 100)) + DATEPART(day, [MyDateTimeField])) >= (((2018 * 10000) + (1 * 100)) + 1))

当我尝试这样做时:

GET /api-endpoint?$filter=MyDateTimeField ge 2018-01-01T00:00:00.00Z&$top=100

它会生成以下 SQL 查询:

SELECT TOP 100 * FROM MyTable WHERE [MyDateTimeField] > '2018-01-01T00:00:00.0000000'

返回此错误:

从字符转换日期和/或时间时转换失败 字符串。

生成与此类似的 SQL 查询的 OData 查询语法是什么?

SELECT TOP 100 * FROM MyTable WHERE [MyDateTimeField] > '2018-01-01'

【问题讨论】:

  • 不确定我是否在问一个愚蠢的问题:为什么不使用 datetime2 ?如果您使用datetime2,您的查询应该可以正常工作。
  • 嗨@itminus,这是一个旧数据库,我无权更改它。

标签: asp.net-core odata asp.net-core-webapi odata-v4


【解决方案1】:

假设MyDateTimeField的字段是datetime而不是datatime2,用列注释[Column(TypeName="datetime")]装饰MyDateTimeField 首先:

public class MyTable
{
    // ...  other props

    [Column(TypeName="datetime")]
    public DateTime MyDateTimeField {get;set;}
}

datetime查询,cast变成DateTimeOffset

?$filter=MyDateTimeField ge cast(2018-01-01T00:00:00.00Z,Edm.DateTimeOffset)

生成的sql 类似于:

  SELECT ..., [$it].[MyDateTimeField], 
  FROM [MyTable] AS [$it]
  WHERE [$it].[MyDateTimeField] >= '2018-01-01T08:00:00.000'

注意上面的datetime2018-01-01T08:00:00.000而不是2018-01-01T00:00:00.0000000

Demo截图:

【讨论】:

  • 谢谢 - 我不敢相信 ODatav4 只是像“啊,没有人使用它”那样放弃了 DateTime - 确保我们的数据库使用 DateTimeOffset 会更好,但在 ODataV4 之前做出了一些决定并迫使开发人员只是“将所有 datetime 列转换为 datetimeoffsets”只是粗鲁 - 它至少应该像在 V3 中一样工作 - 无论如何你可能已经保存了我的培根 :)
【解决方案2】:

在对自己的挫折感到难以置信之后,我终于找到了一个解决方案,它不会强迫我的 api 使用者将 DateTime 字符串转换为丑陋、冗长、令人不安的表达方式。 我还希望我的模型看起来像使用 DateTimeOffset 而不是 DateTime,这将允许我在未来重构数据库并最终使用 DateTimeOffset,即使到目前为止我没有不需要处理时区。 我的限制是我无法更新我的旧数据库,所以这里是解决方案。

这个解决方案适合您吗?

此解决方案仅在以下情况下有用:

  • 您不能(或想要)更新您的 db 列类型(如果可以的话,您应该解决问题)
  • 您使用或可以更改您的实体以使用 DateTimeOffset 而不是 DateTime(如果它是一个新的 api,请考虑这样做以符合 odata 标准并允许您重构未来的底层数据库)
  • 您不需要处理不同的时区(这可以做到,但您需要研究解决方案才能做到这一点)
  • 您使用 EF Core >= 2.1

解决方案

  1. 使用 DateTimeOffset 更新您的实体
public class MyEntity {
    [...]
    public DateTimeOffset CreatedDateTime { get; set; }
    public DateTimeOffset ModifiedDateTime { get; set; }
    [...]
}
  1. 创建ValueConverter
public static class ValueConvertes
{
    public static ValueConverter<DateTimeOffset, DateTime> DateTimeToDateTimeOffset =
        new ValueConverter<DateTimeOffset, DateTime>(
            model => model.DateTime, 
            store => DateTime.SpecifyKind(store, DateTimeKind.UTC));
}
  1. 更新您的模型映射
public void Configure(EntityTypeBuilder<QuestionQML> builder)            
{
    builder.ToTable("MyEntityTable");
    builder.Property(e => e.CreatedDateTime)
       .HasColumnName("CreatedDateTime") // On DB datetime Type
       .HasConversion(ValueConvertes.DateTimeToDateTimeOffset);
    builder.Property(e => e.ModifiedDateTime)
       .HasColumnName("ModifiedDateTime") // On DB datetime Type
       .HasConversion(ValueConvertes.DateTimeToDateTimeOffset);
    [...]          
}

这允许您通过以下方式进行过滤:

?$filter=CreatedDateTime > 2010-01-25T02:13:40Z ?$filter=CreatedDateTime > 2010-01-25T02:13:40.01234Z ?$filter=CreatedDateTime > 2010-01-25

特别感谢chris-clark

编辑: 当数据库中存储的日期时间为 UTC 时,可以使用更正的代码 DateTimeKind.UTC,如果您存储在不同的时区,则需要将 Kind 设置为您使用的时区,但这会改变您的日期时间在结果,显示英国时区,例如 Z(GMT 时间)或 +01:00(BST 时间。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-10-18
    • 1970-01-01
    • 1970-01-01
    • 2014-10-01
    • 2023-03-12
    • 1970-01-01
    • 1970-01-01
    • 2023-03-17
    相关资源
    最近更新 更多