【问题标题】:How to get Web API OData v4 to use DateTime如何让 Web API OData v4 使用 DateTime
【发布时间】:2014-10-01 02:39:09
【问题描述】:

我有一个相当大的数据模型,我想通过 OData V4 协议使用 Web API OData 公开它。

基础数据存储在 SQL Server 2012 数据库中。该数据库中有许多 DateTime 列。

当我连接它时,我收到一个错误,即 System.DateTime 不受支持。

所以这是我的问题,我该怎么做才能让我的 DateTime 列显示在 OData 提要中?

注意:我无法返回并将所有列更改为 DateTimeOffset 列。

我尝试在实体框架 edmx 中更改列的类型,但它给了我这个错误:

指定的成员映射无效。 'MyProject.MyEntity' 类型中成员 'MyPropertyHere' 的类型 'Edm.DateTimeOffset[Nullable=False,DefaultValue=,Precision=]' 与 'SqlServer.datetime[Nullable=False,DefaultValue=,Precision=3] 不兼容'MyDataModel.Store.MyEntity' 类型中的成员 'MyColumnName'。

(基本上认为 DateTime 与 DateTimeOffset 不兼容。)

Web API OData 团队真的忽略了所有需要使用DateTime 的 SQL Server 类型的人吗?

更新:我找到了解决方法,但它们需要更新 EF 模型才能工作。如果可以避免的话,我宁愿不必单独更新数百个属性。

更新:这个问题让我意识到微软管理其 OData 产品的方式存在严重缺陷。有很多问题,但这个是最明显的。 Web API OData 中有大量缺失的功能。 Transactionsordering of inserts 是其中两个。这两个项目(在 OData 规范中并且在微软杀死它之前在 WCF 数据服务中)对于任何实际系统都至关重要。

但是,他们决定将时间花在删除对许多开发人员非常有帮助的功能上,而不是将时间花在那些缺少 OData 规范中的功能的关键点上。 它集中体现了管理不善,优先考虑删除工作功能而不是添加急需的功能。

我尝试与 Web API OData 代表讨论这些问题,最后,我打开了一个问题/票证,几天后又关闭了。这就是他们愿意做的事情的结束。

正如我所说,Web API OData 的管理还有很多问题(与 DateTime 无关,因此我不会在此列出)。 我一直是 OData 的坚定支持者,但 Web API OData 管理方面的明显问题迫使我和我的团队/公司放弃它。

幸运的是,普通的 Web API 可以设置为使用 OData 语法。设置控制器需要做更多的工作,但最终效果很好。它支持日期时间。 (而且似乎拥有至少可以避免做出疯狂错误决定的管理层。)

【问题讨论】:

  • 目前web api odata 不支持DateTime,也许会有修复。有一个类似的问题与解决方法,希望这将有助于stackoverflow.com/questions/24829422/…>
  • 实际上人们要求禁止DateTime(request here)。您确定您的 客户端 时区与服务器的时区相同吗?使用任何时间数据类型而不指定时区只是自找麻烦。这是另一类本地化问题,类似于假设某个代码页、日期格式或十进制字符。需要修复的是模型,而不是 OData 协议
  • @PanagiotisKanavos - 我很确定。
  • @PanagiotisKanavos - 我们受到其他(更大的应用程序)的限制,只能使用我们的时区。通过一个小时的停机时间可以轻松处理夏令时。现实世界的场景很少像规范设计者想的那样简单粗暴。如果我现在有幸创建一个新应用程序,我会使用 DateTimeOffset 吗?当然。但是我们现在很好地管理了日期时间问题。不要忘记,时区已经存在了几十年,但在 OData 中仅支持 DateTimeOffset 仅几年。认为没有其他方法可以处理它是天真的。
  • @Vaccano 相当!! (我完全同意)我的客户端应用程序都经过精心设计和构建,以假设它是什么日期/时间 - 他们从服务器获取这些信息,直到半小时前(当我从 odata 3 更新到 4 时)我的日子过得很顺利。多么痛苦。

标签: .net odata asp.net-web-api asp.net-web-api-odata odata-v4


【解决方案1】:

Web API OData v4 终于在 5.5 版中支持 DateTime 类型。获取最新的 nuget 包,不要忘记设置:

config.SetTimeZoneInfo(TimeZoneInfo.Utc);

否则 dateTime 属性的时区将被视为本地时区。

更多信息:ASP.NET Web API for OData V4 Docs DateTime support

【讨论】:

  • 这个“支持”只是将 DateTime 转换为 DateTimeOffset。 (所以你仍然必须更新任何现有的客户端。)另外,我上次读到你仍然无法过滤日期时间。 (与日期有关的很常见的事情。)
  • @Vaccano 1.我不知道“您仍然必须更新任何现有客户”是什么意思。 ? 2. Web API OData v4 现在支持DateTime 类型 意味着您可以像我在项目中所做的那样过滤 DateTimes。正如您所提到的,它将 DateTime 转换为 DateTimeOffset,因此它允许我们过滤 DateTime。
  • 从 5.9 v4 开始,现在支持将 DateTime 作为实际日期(“5/9/2016”)而不是 DateTimeOffset。
  • @MaximilianWilson 在发行说明和文档中都没有提到。请提供任何链接?
  • 为什么会有人说它支持“DateTime”? OData V4 支持 Date 和 DateTimeOffset 但不支持 edm:DateTime。如果您查看 OData 服务中的元数据,您将找不到 DateTime。
【解决方案2】:

到目前为止,DateTime 不是OASIS OData V4 standard 的一部分,Web API 不支持 DateTime 类型,但它支持 DateTimeOffset 类型。

但是,OData 团队现在正在努力支持 DataTime 类型。我希望您可以在下一个 Web API 版本中使用 DateTime 类型。如果你等不及下一个版本,我写了一个基于 blog 。希望它可以帮助你。谢谢。

型号

public class Customer
{
    private DateTimeWrapper dtw;

    public int Id { get; set; }

    public string Name { get; set; }

    public DateTime Birthday
    {
        get { return dtw; }
        set { dtw = value; }
    }

    [NotMapped]
    public DateTimeOffset BirthdayOffset
    {
        get { return dtw; }
        set { dtw = value; }
    }
}

public class DateTimeWrapper
{
    public static implicit operator DateTimeOffset(DateTimeWrapper p)
    {
        return DateTime.SpecifyKind(p._dt, DateTimeKind.Utc);
    }

    public static implicit operator DateTimeWrapper(DateTimeOffset dto)
    {
        return new DateTimeWrapper(dto.DateTime);
    }

    public static implicit operator DateTime(DateTimeWrapper dtr)
    {
        return dtr._dt;
    }

    public static implicit operator DateTimeWrapper(DateTime dt)
    {
        return new DateTimeWrapper(dt);
    }

    protected DateTimeWrapper(DateTime dt)
    {
        _dt = dt;
    }

    private readonly DateTime _dt;
}

数据库上下文

public DbSet<Customer> Customers { get; set; }

EdmModel

ODataConventionModelBuilder builder = new ODataConventionModelBuilder();

builder.EntitySet<Customer>("Customers");

var cu = builder.StructuralTypes.First(t => t.ClrType == typeof(Customer));
cu.AddProperty(typeof(Customer).GetProperty("BirthdayOffset"));
var customer = builder.EntityType<Customer>();

customer.Ignore(t => t.Birthday);
var model = builder.GetEdmModel();

config.MapODataServiceRoute("odata", "odata", model);

控制器

正常添加 OData 控制器。

测试

有效载荷

【讨论】:

  • 您确定 OData 团队现在正在努力支持 DataTime 类型吗?目前,此问题 (ODATA-220) 被列为 OData v5 的“未解决”:issues.oasis-open.org/browse/ODATA/fixforversion/10277/…
  • 抱歉,我的意思是 OData 团队正在努力支持 OData 的 Web API 中的 DateTime 类型。
  • 他们的“支持”相当于将 DateTimes 转换为 DateTimeOffsets。如果您需要按日期进行过滤,则它不起作用。
【解决方案3】:

另一种解决方案是修补项目,以便再次允许 DateTime - 这就是我所做的,如果需要,您可以在此处获取代码:

https://aspnetwebstack.codeplex.com/SourceControl/network/forks/johncrim/datetimefixes

我还推送了一个 NuGet 包,方便使用,你可以使用这里的信息获取包:

http://www.nuget.org/packages/Patches.System.Web.OData/5.3.0-datetimefixes

...我不知道我是否可以发布包含修补过的 Microsoft 库的 NuGet 包,我希望请求宽恕比许可更容易。

请注意,在此处跟踪正式恢复在 OData 4 中使用 DateTime 的能力的工作项: https://aspnetwebstack.codeplex.com/workitem/2072 如果您想查看该问题,请为该问题投票;虽然看起来它已安排在 5.4 测试版中,但它应该会在这些日子之一到来。

【讨论】:

  • 当我尝试使用 5.3.0 时出现错误:ValueFactoryGlobalConfiguration.Configure(WebApiConfig.Register);
  • 嗯——所有的单元测试都通过了。我将在今天晚些时候对此进行调查。
  • 事实上,完整的错误消息是:ValueFactory attempted to access the Value property of this instance,当我尝试使用 5.3.0 pre for Microsoft.AspNet.Odata 时,这是补丁的预先请求。
  • 所以,我发现了问题:config.MapHttpAttributeRoutes(); 这不适用于5.3.0 版本。但是,现在我正在使用DateTime 运行 OData 4.0,但是当我尝试发布这样的 json 时:{ name: 'iPhone 6', releaseDate: '2014-08-08' } 不再工作了。我在 ReleaseDate 字段中将 DateTimeOffSet 替换为 DateTime(它与 DateTimeOffSet 一起使用)。所以我在 post/put/merge/patch 方法中得到了 Delta 的空值。
  • 允许在 DateTimeOffset 属性上使用标签 [Column(TypeName = "date")] 是一个简单的解决方案吗?
【解决方案4】:

此解决方法和来自 http://damienbod.wordpress.com/2014/06/16/web-api-and-odata-v4-crud-and-actions-part-3/,两者都不起作用。它们仅以一种方式工作,这意味着使用 filter 命令查询 odata datetimeoffset 会失败,因为它不是模型的一部分。

您不能再按这些字段过滤或排序,否则会出现此错误

/Aas/Activities?$top=11&$orderby=CreatedAt

给出这个错误:

"code":"","message":"An error has occurred.","innererror":{
  "message":"The 'ObjectContent`1' type failed to serialize the response body for content type 'application/json; odata.metadata=minimal'.","type":"System.InvalidOperationException","stacktrace":"","internalexception":{
    "message":"The specified type member 'CreatedAt' is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported.","type":"System.NotSupportedException","stacktrace":"   

但这是间接解决的:

/Aas/AppUsers%28102%29/AppUserActivities?$expand=Case&$filter=%28Reminder%20ne%20null%20and%20IsComplete%20eq%20null%29&$top=15&$orderby=Reminder&$count=true

Reminder 和 Iscomplete 是通过 AppUserActivities 进行的活动的日期和日期时间。

这很奇怪。有人有解决办法吗?

我连接到 MySQL。没有 datetimeoffset。

每个人都想知道为什么没有人愿意为 Microsoft 技术进行开发。

【讨论】:

  • 不清楚你在说什么。这甚至是一个答案,还是一个问题? “至于我之前写的”是什么意思?之前写的哪里?我删除了紧随其后的链接,因为它是指向 this 页面的链接。
  • 我想说的是没有人回答。 Odata 将使用此解决方案运行,这些字段对于排序或过滤是无用的,因为过滤和排序的 odata 将这些字段交给 EF,而 EF 说哇这个字段不是模型的一部分。您无法过滤的日期时间工作有什么好处?
【解决方案5】:

不幸的是,crimbo 提供的 https://aspnetwebstack.codeplex.com/SourceControl/network/forks/johncrim/datetimefixes fork 并不真正支持 DateTime。

有基于 OData v5.3 RTM 的新 fork https://aspnetwebstack.codeplex.com/SourceControl/network/forks/kj/odata53datetime?branch=odata-v5.3-rtm,其中在服务器应答、客户端 POST/PUT/PATCH 请求、$orderby 和 $filters 子句、函数参数中支持实体和复杂类型中的 DateTime 属性并返回。我们开始在生产代码中使用它并将支持这个分支,直到 DateTime 支持将在未来的官方版本中恢复。

【讨论】:

  • 这里也一样。使用 NuGet,即使“不支持”异常消失,序列化日期也是一个空字典。新前叉运行良好。只需在 OData 中编译解决方案并使用 System.Web.OData.dll 文件或引用项目。
【解决方案6】:

由于我使用带角度的 odata 库,因此我在那里对其进行了调查:

https://github.com/devnixs/ODataAngularResources/blob/master/src/odatavalue.js

在那里你可以看到(javascript)

var generateDate = function(date,isOdataV4){
        if(!isOdataV4){
            return "datetime'" + date.getFullYear() + "-" + ("0" + (date.getMonth() + 1)).slice(-2) + "-" + ("0" + date.getDate()).slice(-2) + "T" + ("0" + date.getHours()).slice(-2) + ":" + ("0" + date.getMinutes()).slice(-2)+':'+("0" + date.getSeconds()).slice(-2) + "'";
        }else{
            return date.getFullYear() + "-" + ("0" + (date.getMonth() + 1)).slice(-2) + "-" + ("0" + date.getDate()).slice(-2) + "T" + ("0" + date.getHours()).slice(-2) + ":" + ("0" + date.getMinutes()).slice(-2)+':'+("0" + date.getSeconds()).slice(-2) + "Z";
        }
    };

并且测试期望(cfr.here

 $httpBackend.expectGET("/user(1)?$filter=date eq 2015-07-28T10:23:00Z")

所以这应该是你的“格式”

2015-07-28T10:23:00Z

【讨论】:

  • 是的!我花了几个小时试图了解为什么 ODataActionParameters 会引发序列化错误。它无法识别我的 DateTime
【解决方案7】:

以下两项均适用于 ODATA 4

1:这是我看到的 .Net 4.7 和 Microsoft.Aspnet.ODATA 5.3.1

的最新更新
$filter=DateofTravel lt cast(2018-05-15T00:00:00.00Z,Edm.DateTimeOffset)

2:这也可以,它需要在这个yyyy-mm-ddThh:mm:ss.ssZ

$filter=DateofTravel lt 2018-02-02T00:00:00.00Z

【讨论】:

  • 现在尝试对其进行分组或将固定值替换为日期时间类型的 OData 模型中实体上的字段,然后对其进行分组
【解决方案8】:
public class Customer
{
    private DateTimeWrapper dtw;

    public int Id { get; set; }

    public string Name { get; set; }

    public DateTime Birthday
    {
       get { return dtw; }
       set { dtw = value; }
    }

    [NotMapped]
    public DateTimeOffset BirthdayOffset
    {
        get { return dtw; }
        set { dtw = value; }
    }
}


var customer = builder.EntityType<Customer>();
customer.Ignore(t => t.Birthday);
customer.Property(t => t.BirthdayOffset).Name = "Birthday";

【讨论】:

  • 我得到“找不到类型或命名空间名称'DateTimeWrapper'”
  • 那是因为这个答案是上面答案的部分副本
【解决方案9】:

看起来像映射 DateTimeOffset DateTime 将包含在 Microsoft ASP.NET Web API 2.2 for OData v4.0 5.4.0 em>:

https://github.com/OData/WebApi/commit/2717aec772fa2f69a2011e841ffdd385823ae822

【讨论】:

  • 是的。但它不支持日期时间。只支持从 DateTime 转换为 DatetimeOffset。有总比没有好。但仍然不允许从 WCF DataServices 迁移到 Web API OData。
【解决方案10】:

https://www.nuget.org/packages/Patches.System.Web.OData/ 安装 System.Web.OData 5.3.0-datetimefixes

【讨论】:

    【解决方案11】:

    花了令人沮丧的一天来尝试做这件事,我偶然发现了让它工作的唯一方法。我们希望能够使用 OData 和 Web API 2.2 按日期范围进行过滤,这不是一个不常见的用例。我们的数据在 SQL Server 中,并存储为 DateTime,我们在 API 中使用 EF。

    版本:

    • Microsoft.AspNet.WebApi.OData 5.7.0
    • Microsoft.AspNet.Odata 5.9.0
    • Microsoft.OData.Core 6.15.0
    • Microsoft.OData.Edm 6.15.0
    • Microsoft.Data.OData 5.7.0

    实体片段:

    [Table("MyTable")]
    public class CatalogueEntry
    {
        [Key]
        public Guid ContentId { get; set; }
        [StringLength(15)]
        public string ProductName { get; set; }
        public int EditionNumber { get; set; }
        public string Purpose { get; set; }
        public DateTime EditionDate { get; set; }
    }
    

    WebApiConfig

    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            config.Routes.MapODataServiceRoute("ProductCatalogue", "odata", GetImplicitEdm());
    
            // Web API routes
            config.MapHttpAttributeRoutes();
    
            config.Filters.Add(new UnhandledExceptionFilter());
    
            var includeVersionHeaders = ConfigurationManager.AppSettings["IncludeVersionHeaders"];
            if (includeVersionHeaders != null && bool.Parse(includeVersionHeaders))
            {
                config.Filters.Add(new BuildVersionHeadersFilter());
            }
    
            config.SetTimeZoneInfo(TimeZoneInfo.Utc);
        }
    
        private static IEdmModel GetImplicitEdm()
        {
            ODataModelBuilder builder = new ODataConventionModelBuilder();
            builder.EntitySet<CatalogueEntry>("ProductCatalogue");
            return builder.GetEdmModel();
        }
    }
    

    控制器片段:

    public class ProductCatalogueController : EntitySetController<CatalogueEntry, string>
    {
        [EnableQuery]
        public override IQueryable<CatalogueEntry> Get()
        {
            return _productCatalogueManager.GetCatalogue().AsQueryable();
        }
    
        protected override CatalogueEntry GetEntityByKey(string key)
        {
            return _productCatalogueManager.GetCatalogue().FirstOrDefault(c => c.ContentId.ToString() == key);
        }
    }
    

    这是神奇之处 - 请参阅 this MSDN page 底部的标题“在过滤器表达式中引用不同的数据类型”,您会发现一条注释说:

    DateTime 值必须用单引号和 前面有 datetime 字样,例如 日期时间'2010-01-25T02:13:40.1374695Z'.

    http://localhost/Product-Catalogue/odata/ProductCatalogue?$filter=EditionDate lt datetime'2014-05-15T00:00:00.00Z'
    

    到目前为止,我们已经在 Postman 中进行了这项工作,我们现在正在构建客户端,希望它能够满足这一要求,将“日期时间”放在实际值的前面。我正在考虑在 MVC 控制器中使用 Simple.OData.Client,但我们甚至可能决定从客户端 JavaScript 直接调用 API,具体取决于我们必须进行多少重构。我还想让 Swagger UI 使用 Swashbuckle.OData 工作,但这也被证明很棘手。一旦我完成了我有时间做的事情,我会为那些关注的人发布一个包含有用信息的更新,因为我发现很难找到如何做表面上一个简单的要求的事情非常令人沮丧.

    【讨论】:

      【解决方案12】:

      对于那些使用不支持日期时间的旧版本 OData 的用户,您可以简单地使用 .AddQueryOption 扩展名来手动包含日期时间过滤器...

      示例 - 如果您的查询定义如下:

      var date1 = new DateTime();
      var query = from a in service.Entity
                  where a.DateField = date1
                  select a;
      

      这不会给您预期的结果 b/c 翻译后的查询实际上类似于 https://test.com/Entity?$filter=DateField eq 2020-02-24T00:00:00Z。当它到达服务器时,它不会执行日期时间偏移的预期查询 b/c。

      要解决这个问题,请执行以下操作:

      var date1 = new DateTime().ToString("yyyy-MM-dd");
      var filters = "date(DateField) eq " + date1;
      var query = from a in service.Entity.AddQueryOption("$filter", filters);
      

      这将允许您使用日期时间查询 odata。 现在,您需要做的就是处理 POST、PUT、DELETE 命令。

      为此,只需确保您在请求的标头中具有序列化的时区信息或客户端偏移量。在 web api 中使用它来相应地调整日期。我通常会添加一个在自定义序列化程序中使用的扩展方法,以调整有效负载反序列化的日期。

      另外,请务必编写以便正确处理时区信息的较新版本的 web api 将继续按预期运行...

      【讨论】:

        猜你喜欢
        • 2017-12-04
        • 1970-01-01
        • 1970-01-01
        • 2018-11-30
        • 1970-01-01
        • 2016-05-13
        • 2014-10-08
        • 1970-01-01
        • 2017-04-02
        相关资源
        最近更新 更多