30

I have a fairly large data model that I want to expose using Web API OData using the OData V4 protocol.

The underlying data is stored in a SQL Server 2012 database. That database has many DateTime columns in it.

As I was wiring it up I got an error that System.DateTime is not supported.

So here is my question, what can I do to get my DateTime columns to be seen in the OData feed?

NOTE: I am not able to go back and change all my columns to DateTimeOffset columns.

I tried changing the type of the column in the Entity Framework edmx, but it gave me this error:

Member Mapping specified is not valid. The type 'Edm.DateTimeOffset[Nullable=False,DefaultValue=,Precision=]' of member 'MyPropertyHere' in type 'MyProject.MyEntity' is not compatible with 'SqlServer.datetime[Nullable=False,DefaultValue=,Precision=3]' of member 'MyColumnName' in type 'MyDataModel.Store.MyEntity'.

(Bascially syas that DateTime is not compatable with DateTimeOffset.)

Did the Web API OData team really just leave out everyone who needs to use the SQL Server type of DateTime?

Update: I have found workarounds for this, but they require updating the EF Model for them to work. I would rather not have to update several hundred properties individually if I can avoid it.

Update: This issue has made me realize that there are deep flaws in how Microsoft is managing its OData products. There are many issues, but this one is the most glaring. There are huge missing features in the Web API OData. Transactions and ordering of inserts being two of them. These two items (that are in the OData spec and were in WCF Data Services before Microsoft killed it) are critical to any real system.

But rather than put time into those critical spots where they are missing functionality that is in the OData Specification, they decide to spend their time on removing functionality that was very helpful to many developers. It epitomizes bad management to prioritize the removal of working features over adding in badly needed features.

I tried discussing these with the Web API OData representative, and in the end, I got a issue/ticket opened that was then closed a few days later. That was the end of what they were willing to do.

As I said, there are many more issues (not related to DateTime, so I will not list them here) with the management of Web API OData. I have been a very strong supporter of OData, but the glaring issues with Web API OData's management have forced me and my team/company to abandon it.

Fortunately, normal Web API can be setup to use OData syntax. It is more work to setup your controllers, but it works just fine in the end. And it supports DateTime. (And seems to have management that can at least stay away from making insanely bad decisions.)

Vaccano
  • 78,325
  • 149
  • 468
  • 850
  • Currently web api odata doesn't support DateTime, maybe there will be a fix. There is a similar question with a work-around, hope this will help http://stackoverflow.com/questions/24829422/handling-dates-with-odata-v4-ef6-and-web-api-v2-2/25076353#25076353> – QianLi Aug 08 '14 at 02:01
  • Actually people have **asked** to ban `DateTime` [(request here)](http://aspnetwebstack.codeplex.com/workitem/1753). Are you so sure that your *client's* timezone is the same as your server's? Using any time data type without specifying the timezone is just asking for trouble. This is another category of localization problems, similar to assuming a certain code page, date format or decimal character. It's the model that needs fixing, not the OData protocol – Panagiotis Kanavos Sep 02 '14 at 12:03
  • @PanagiotisKanavos - I am quite sure. – Vaccano Sep 02 '14 at 15:34
  • Then expect a painful realization in the future. EST, PST or GMT? Summer or winter time? What happens when the switch from summer to winter time occurs? Are you going to record dates out of order? Just because it hasn't happened yet doesn't mean it won't. Not to mention times that *require* timezone info eg. flight departures and arrivals, local event times etc. It's better to be explicit about it rather than use assumptions that are bound to fail – Panagiotis Kanavos Sep 02 '14 at 15:42
  • 2
    @PanagiotisKanavos - We are constrained by other (larger applications) to use just our timezone. Daylight savings is easily dealt with via an hour down time. Real world scenarios are rarely as cut and dried as spec designers would like to think. If I had the luxury of making a new application right now, would I use DateTimeOffset? Of course. But we manage the datetime issues now very well. Don't forget, timezones have existed for Decades, but DateTimeOffset has only been supported in OData for only a few years. It is naive to think there is no other way to deal with it. – Vaccano Sep 02 '14 at 16:10
  • 1
    @Vaccano quite !! (I totally agree) my client apps have all been carefully designed and built to assume NOTHING about what date / time it is - they take this info from the server, and until half an hour ago (when I updated from odata 3 to 4) my day was going well. WHAT A TOTAL PAIN. – MemeDeveloper Oct 19 '14 at 01:25
  • Just voted https://aspnetwebstack.codeplex.com/workitem/2072 – MemeDeveloper Oct 19 '14 at 01:28
  • @PanagiotisKanavos How about always sending the UTC datetime and letting the client take care of conversions for any UI? – Darek Jan 04 '15 at 02:38
  • @Darek That won't work - what offset *are* we talking about? Javascript's format (and Json's nowadays) is ISO 8601 which *does* include TZ offset. There's no problem with ODATA or the format specification. The problem is that ASP.NET Web API's implementation doesn't automagically assume/infer a timezone when presented with a DateTime value. Such values can be either UTC, local or unspecified as shown by the [DateTime.Kind](http://msdn.microsoft.com/en-us/library/system.datetime.kind(v=vs.110).aspx) property. I think it's the `Unspecified` value that's causing problems – Panagiotis Kanavos Jan 07 '15 at 08:40

12 Answers12

19

So far, DateTime is not the part of the OASIS OData V4 standard and Web API doesn't support the DateTime type while it do support the DateTimeOffset type.

However, OData Team are working on supporting the DataTime type now. I'd expect you can use the DateTime type in the next Web API release. If you can't wait for the next release, I wrote an example based on the blog . Hope it can help you. Thanks.

Model

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

DB Context

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

Controller

Add the OData Controller as normal.

Test

Customer Data in the DB

Payload

The customers payload

Sam Xu
  • 3,264
  • 1
  • 12
  • 17
  • 5
    Are you sure the OData Team is working on supporting the DataTime type now? Currently this issue (ODATA-220) is listed as "Unresolved" for OData v5: https://issues.oasis-open.org/browse/ODATA/fixforversion/10277/?selectedTab=com.atlassian.jira.jira-projects-plugin:version-summary-panel – David McClelland Nov 03 '14 at 16:45
  • Sorry, I mean OData Team is working on supporting the DateTime type in Web API for OData. – Sam Xu Nov 14 '14 at 01:17
  • 3
    Their "support" amounted to converting DateTimes into DateTimeOffsets. And it does not work if you need to filter on the date. – Vaccano May 28 '15 at 23:01
19

Finally Web API OData v4 now supports DateTime type in release 5.5 . Get latest nuget package and don't forget setting this:

config.SetTimeZoneInfo(TimeZoneInfo.Utc);

otherwise the timezone of the dateTime property would be considered as local timezone.

More info: ASP.NET Web API for OData V4 Docs DateTime support

Iman Mahmoudinasab
  • 6,861
  • 4
  • 44
  • 68
  • 1
    This "support" just converts the DateTime into a DateTimeOffset. (So you still have to update any existing clients.) Also, last I read you still cannot filter on the date time. (A very common thing to do with dates.) – Vaccano May 28 '15 at 23:04
  • @Vaccano 1.I have no idea what do you mean by "you still have to update any existing clients." ? 2. _Web API OData v4 now supports `DateTime` type_ means you can filter on the DateTimes as I done in my project. As you mentioned it convert the DateTime to DateTimeOffset so it allows us to filter a DateTime. – Iman Mahmoudinasab May 29 '15 at 08:58
  • If you had a client application that was using DateTime (or several as my case is), you now have to update each of them to all use DateTimeOffset where they had DateTime. And there is a bug filtering on a DateTime that was converted to a DateTimeOffset in the current version (They said it would be fixed in 5.5, but then they pushed it to the still pending 5.6) – Vaccano May 29 '15 at 20:25
  • 1
    As of 5.9 v4 now supports DateTime as an actual Date ("5/9/2016") instead of a DateTimeOffset. – Maximilian Wilson Apr 06 '16 at 14:46
  • 6
    @MaximilianWilson It is not mentioned in release note neither documents. Would you please provide any link? – Iman Mahmoudinasab Apr 07 '16 at 13:45
  • 2
    Why would anyone say it supports "DateTime"? OData V4 supports Date and DateTimeOffset but not edm:DateTime. If you look at the metadata from the OData service, you will not find DateTime. – goroth Mar 27 '18 at 02:08
  • It will throw an exception if you send a DateTime value without timezone to any OData method, `PATCH odata/Game {"GameDateTime": "2022-01-01T12:14:16"}` this will throw `couldn't convert string literal to Edm.DateTimeOffset`. Saying it supports DateTime is straight up misdirecting people. – Glass Cannon Jun 04 '22 at 18:57
3

An alternate solution is to patch the project so that DateTime is allowed again - that's what I did, you can get the code here if you want it:

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

I also pushed a NuGet package to make it easy to use, you can obtain the package using the info here:

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

... I don't know if it's ok for me to post a NuGet package containing a patched Microsoft library, I hope it's easier to ask forgiveness than permission.

Note that the work item to officially restore the ability to use DateTime in OData 4 is tracked here: https://aspnetwebstack.codeplex.com/workitem/2072 Please vote for the issue if you'd like to see it; though it looks like it's scheduled for 5.4-beta, so it should be coming one of these days.

crimbo
  • 10,308
  • 8
  • 51
  • 55
  • When I try to use the 5.3.0 I get an error: `ValueFactory` at `GlobalConfiguration.Configure(WebApiConfig.Register);` – iuristona Sep 08 '14 at 14:28
  • Hmmm - all the unit tests passed. I'll look into this later today. – crimbo Sep 08 '14 at 15:00
  • In fact the full error message is: `ValueFactory attempted to access the Value property of this instance` soon as I try to use the 5.3.0 pre for `Microsoft.AspNet.Odata` wich is pre-requesite for the patch. – iuristona Sep 08 '14 at 16:43
  • So, I figured out the issue: `config.MapHttpAttributeRoutes();` this is not working with the `5.3.0` version. But, now I am running the OData 4.0 with `DateTime`, but when I try to post a json like this: `{ name: 'iPhone 6', releaseDate: '2014-08-08' }` is not working anymore. I replaced the DateTimeOffSet to DateTime in that ReleaseDate field (it works with DateTimeOffSet). So the I get a null for the Delta in my post/put/merge/patch methods. – iuristona Sep 08 '14 at 18:05
  • Would allowing the usage of the tag [Column(TypeName = "date")] on DateTimeOffset properties be a simple solution to this? – Kittoes0124 Sep 15 '14 at 12:39
1

This workarounds and the one from http://damienbod.wordpress.com/2014/06/16/web-api-and-odata-v4-crud-and-actions-part-3/, neither work. They work one way only, meaning that quering odata datetimeoffset with the filter command fails because it is not part of the model.

You can no longer filter or sort by these fields or get this error

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

Gives this error:

"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":"   

But this works as it is addressed indirectly:

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

Reminder and Iscomplete are date and datetiem from activity through AppUserActivities.

This is wierd that that works. Does anyone have a solution?

I connect to MySQL. There is not a datetimeoffset.

And everyone wonders why no one wants to develop for Microsoft technologies.

  • It's not clear what you're saying. Is this even an answer, or a question? And what do you mean by "As to what I wrote earlier"? Wrote earlier *where*? I deleted the link that immediately followed that because it was a link to *this* page. – Adi Inbar Nov 17 '14 at 23:49
  • What I am trying to say is this is not answered. Odata will run with this solution, those fields are useless for sorting or filtering because odata on filtering and sorting hands those fields off to EF and EF says wow this field is not part of the model. What good is a datetime work around that you cannot filter on? – Jon Alberghini Nov 19 '14 at 18:17
1

Unfortunatly, https://aspnetwebstack.codeplex.com/SourceControl/network/forks/johncrim/datetimefixes fork, given by crimbo doesn`t realy support DateTime.

There is the new fork https://aspnetwebstack.codeplex.com/SourceControl/network/forks/kj/odata53datetime?branch=odata-v5.3-rtm based on OData v5.3 RTM, where DateTime properties in entities and complex types are supported in server answers, client POST/PUT/PATCH requests, $orderby and $filters clauses, function parameters and returns. We start to use it in production code and will support this fork, until DateTime support will return in future official releases.

Community
  • 1
  • 1
  • Same here. With the NuGet, even though the "unsupported" exception went away, the serialized date is an empty dictionary. The new fork works well. Just compile the solution in OData and use the System.Web.OData.dll file or the reference the project. – Jerther Jan 08 '15 at 17:10
1

Since i use a library for odata with angular, i investigated it there:

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

There you can see ( 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";
        }
    };

And the test expects ( cfr. here )

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

So this should be your "formatting"

2015-07-28T10:23:00Z
NicoJuicy
  • 3,435
  • 4
  • 40
  • 66
  • Yes! I spent a couple hours trying to understand why ODataActionParameters was throwing a serialization error. It wouldn't recognize my DateTime – Vector Jan 19 '17 at 21:21
1

Both the following work with ODATA 4

1 : This is the Latest update I see with .Net 4.7 and Microsoft.Aspnet.ODATA 5.3.1

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

2 : This also works , it needs to be in this yyyy-mm-ddThh:mm:ss.ssZ

$filter=DateofTravel lt 2018-02-02T00:00:00.00Z
AlexB
  • 7,302
  • 12
  • 56
  • 74
Sundara Prabu
  • 2,361
  • 1
  • 21
  • 20
  • 1
    now try grouping on that or replace the fixed value with a field on an entity in your OData model of Type DateTime then group on it – War Oct 04 '18 at 13:17
0
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";
Felipe Pereira
  • 11,410
  • 4
  • 41
  • 45
0

Looks like a mapping DateTimeOffset <-> DateTime will be included in Microsoft ASP.NET Web API 2.2 for OData v4.0 5.4.0:

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

Craig Boland
  • 984
  • 1
  • 10
  • 18
  • 1
    Yep. But it does not support DateTime. Just supports converting from DateTime to DatetimeOffset. Better than nothing. But still does not allow migrating from WCF DataServices to Web API OData. – Vaccano Feb 05 '15 at 20:12
0

Install System.Web.OData 5.3.0-datetimefixes from https://www.nuget.org/packages/Patches.System.Web.OData/

JeeShen Lee
  • 3,476
  • 5
  • 39
  • 59
0

Having spent a frustrating day trying to do this very thing, I have just stumbled across the only way I could get it to work. We wanted to be able to filter by date ranges using OData and Web API 2.2, which isn't an uncommon use case. Our data is in SQL Server and is stored as DateTime and we're using EF in the API.

Versions:

  • 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

Entity Snippet:

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

Controller Snippet:

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

Here's the magic bit - see the bottom of this MSDN page under the heading 'Referencing Different Data Types in Filter Expressions' and you will find a note saying:

DateTime values must be delimited by single quotation marks and preceded by the word datetime, such as datetime'2010-01-25T02:13:40.1374695Z'.

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

So far we have this working in Postman and we're now building the client which should hopefully work with this requirement to stick 'datetime' in front of the actual value. I'm looking at using Simple.OData.Client in an MVC controller but we may even decide to call straight to the API from client side JavaScript depending on how much refactoring we have to do. I'd also like to get Swagger UI working using Swashbuckle.OData but this is also proving to be tricky. Once I've done as much as I have time to, I'll post an update with useful info for those who follow as I found it very frustrating that it was so hard to find out how to do something that is ostensibly a simple requirement.

Steve Pettifer
  • 1,975
  • 1
  • 19
  • 34
0

For those who are using older versions of OData without Datetime support, you can simply use the .AddQueryOption extension to include the datetime filters manually...

Example - If you had a query defined as follows:

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

This would not give you the result you expect b/c the translated query would effectively be something like https://test.com/Entity?$filter=DateField eq 2020-02-24T00:00:00Z. When it gets to the server this wont execute the expected query b/c of the datetime offset.

to get around this do the following:

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

This will allow you to query against odata using datetime. Now, all you need to do is handle the POST, PUT, DELETE commands.

To do this, simply ensure that you have the timezone information or the client offset serialized in the header of the request. Use this in the web api to adjust the dates accordingly. I generally will add an extension method that is used in a custom serializer to adjust the dates when the payload is deserialized.

Alos, be sure to write such that the newer version of web api that handle timezone information properly will continue to function as expected...