Как заставить Web API OData v4 использовать DateTime
У меня довольно большая модель данных, которую я хочу открыть с помощью OData Web API с использованием протокола OData V4.
Базовые данные хранятся в базе данных SQL Server 2012. В этой базе данных есть много столбцов DateTime.
Поскольку я подключал его, я получил сообщение об ошибке, что System.DateTime не поддерживается.
Итак, вот мой вопрос, что я могу сделать, чтобы мои столбцы DateTime отображались в фиде OData?
ПРИМЕЧАНИЕ. Я не могу вернуться и изменить все мои столбцы на столбцы DateTimeOffset.
Я попытался изменить тип столбца в Entity Framework edmx, но он дал мне эту ошибку:
Указание членства недействительно. Тип "Edm.DateTimeOffset [Nullable = False, DefaultValue =, Precision =]" члена MyPropertyHere в типе "MyProject.MyEntity" несовместим с "SqlServer.datetime [Nullable = False, DefaultValue =, Precision = 3] 'члена' MyColumnName 'в типе' MyDataModel.Store.MyEntity '.
(Bascially syas, что DateTime не совместим с DateTimeOffset.)
Действительно ли команда API Web OData просто не учитывала всех, кто должен использовать тип SQL Server DateTime
?
Обновление. Я нашел обходные пути для этого, но они требуют обновления модели EF для их работы. Я бы предпочел не обновлять несколько сотен свойств индивидуально, если я могу избежать этого.
Обновление:. Эта проблема заставила меня понять, что есть серьезные недостатки в том, как Microsoft управляет своими продуктами OData. Есть много вопросов, но это один из самых вопиющих. В OData Web API отсутствуют огромные недостатки. Транзакции и порядок вставки, являющийся двумя из них. Эти два элемента (которые входят в спецификацию OData и находятся в службах данных WCF до того, как Microsoft их убил) имеют решающее значение для любой реальной системы.
Вместо того, чтобы помещать время в те критические места, где им не хватает функциональности, которая находится в спецификации OData, они решают потратить свое время на удаление функциональных возможностей, которые очень помогли многим разработчикам. Он олицетворяет плохое управление, чтобы уделить приоритетное внимание устранению рабочих функций при добавлении в крайне необходимые функции.
Я пробовал обсудить их с представителем Web API OData, и в конце концов, у меня открылась проблема/билет, который был закрыт через несколько дней. Это был конец того, что они хотели сделать.
Как я уже сказал, есть еще много проблем (не связанных с DateTime, поэтому я не буду перечислять их здесь) с управлением OData Web API. Я был очень сильным сторонником OData, но вопиющие проблемы с управлением веб-интерфейсом OData заставили меня и мою команду/компанию отказаться от нее.
К счастью, обычный Web API можно настроить для использования синтаксиса OData. Это больше подходит для настройки ваших контроллеров, но в конце концов это прекрасно работает. И он поддерживает DateTime. (И, похоже, есть руководство, которое может по крайней мере избегать принятия безумно плохих решений.)
Ответы
Ответ 1
До сих пор DateTime не является частью OASIS OData V4 standard, а Web API не поддерживает тип DateTime, поддерживая тип DateTimeOffset.
Однако команда OData теперь работает над поддержкой типа DataTime. Я ожидаю, что вы можете использовать тип DateTime в следующей версии Web API. Если вы не можете дождаться следующего выпуска, я написал пример, основанный на
blog. Надеюсь, это может вам помочь. Спасибо.
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
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 как обычно.
Test
![Customer Data in the DB]()
Payload
![The customers payload]()
Ответ 2
Наконец, Web API OData v4 теперь поддерживает тип DateTime
в версии 5.5. Получите последний пакет nuget и не забудьте установить это:
config.SetTimeZoneInfo(TimeZoneInfo.Utc);
иначе часовой пояс свойства dateTime будет рассматриваться как локальный часовой пояс.
Дополнительная информация: ASP.NET Web API for OData V4 Docs DateTime support
Ответ 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
... Я не знаю, нормально ли мне отправлять пакет NuGet, содержащий исправленную библиотеку Microsoft, надеюсь, вам будет проще просить прощения, чем разрешение.
Обратите внимание, что рабочий элемент, который официально восстанавливает возможность использования DateTime в OData 4, отслеживается здесь:
https://aspnetwebstack.codeplex.com/workitem/2072
Пожалуйста, проголосуйте за вопрос, если вы хотите его увидеть; хотя похоже, что он запланирован на 5.4-бета, поэтому он должен наступить на днях.
Ответ 4
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";
Ответ 5
Обходные пути и
http://damienbod.wordpress.com/2014/06/16/web-api-and-odata-v4-crud-and-actions-part-3/, не работают. Они работают только в одном направлении, а это означает, что запрос odata datetimeoffset с командой фильтра терпит неудачу, потому что он не является частью модели.
Вы больше не можете фильтровать или сортировать по этим полям или получать эту ошибку
? /Aas/Деятельность $топ = 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":"
Но это работает, поскольку оно адресовано косвенно:
/Аас/AppUsers% 28102% 29/AppUserActivities $расширить = Case &? $Фильтр =% 28Reminder %20Ne %20null %20and %20IsComplete %20eq %20null% 29 & $верхний = 15 & $OrderBy = напоминание & $кол = истина
Напоминание и Iscomplete - это дата и данные из активности через AppUserActivities.
Это странно, что это работает. У кого-нибудь есть решение?
Я подключаюсь к MySQL. Не существует datetimeoffset.
И все удивляются, почему никто не хочет разрабатывать технологии Microsoft.
Ответ 6
К сожалению, https://aspnetwebstack.codeplex.com/SourceControl/network/forks/johncrim/datetimefixes fork, заданный crimbo doesn `t реальная поддержка DateTime.
Появится новый fork https://aspnetwebstack.codeplex.com/SourceControl/network/forks/kj/odata53datetime?branch=odata-v5. 3-rtm на основе RTM OData v5.3, где свойства DateTime в объектах и сложных типах поддерживаются в ответах сервера, запросах клиента POST/PUT/PATCH, предложениях $orderby и $filters, параметрах функции и возвратах. Мы начинаем использовать его в производственном коде и будем поддерживать эту вилку, пока поддержка DateTime не вернется в будущих официальных выпусках.
Ответ 7
Так как я использую библиотеку для odata с angular, я исследовал ее там:
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. здесь)
$httpBackend.expectGET("/user(1)?$filter=date eq 2015-07-28T10:23:00Z")
Итак, это должно быть ваше "форматирование"
2015-07-28T10:23:00Z
Ответ 8
Похоже на отображение DateTimeOffset ↔ DateTime будет включено в Microsoft ASP.NET Web API 2.2 для OData v4.0 5.4.0:
https://github.com/OData/WebApi/commit/2717aec772fa2f69a2011e841ffdd385823ae822
Ответ 9
Установите System.Web.OData 5.3.0-datetimefixes из https://www.nuget.org/packages/Patches.System.Web.OData/
Ответ 10
Проведя разочаровывающий день, пытаясь сделать это, я просто наткнулся на единственный способ заставить его работать. Мы хотели иметь возможность фильтровать диапазоны дат, используя OData и Web API 2.2, что не является необычным вариантом использования. Наши данные находятся в SQL Server и хранятся как DateTime, и мы используем EF в API.
Версии:
- 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();
}
}
Флажок контроллера:
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);
}
}
Здесь волшебный бит - см. нижнюю часть эту страницу MSDN под заголовком "Ссылка на разные типы данных в выражениях фильтра", и вы найдете примечание:
Значения DateTime должны быть разделены одинарными кавычками и которому предшествует слово 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, и теперь мы строим клиента, который, надеюсь, будет работать с этим требованием, чтобы придерживаться 'datetime' перед фактическим значением. Я смотрю на использование Simple.OData.Client в MVC-контроллере, но мы можем даже решить обратиться прямо к API с клиентской стороны JavaScript в зависимости от того, сколько рефакторинга нам нужно делать. Я также хотел бы, чтобы пользовательский интерфейс Swagger работал с использованием Swashbuckle.OData, но это тоже сложно. Как только я сделал столько, сколько у меня есть время, я опубликую обновление с полезной информацией для тех, кто будет следовать, поскольку мне было очень неприятно, что было так сложно узнать, как сделать что-то, что якобы простое требование.