OData / RESTier

A turn-key library for building RESTful services
http://odata.github.io/RESTier
Other
472 stars 135 forks source link

POST call is not working with datetimeoffset #87

Closed hpraheja closed 9 years ago

hpraheja commented 9 years ago

We are using datetimeoffset in our service for dates and GET is working fine. When we make a call for POST operation it throws below error:

"Object of type 'System.DateTimeOffset' cannot be converted to type 'System.Nullable`1[System.DateTime]'.","type":"System.ArgumentException","stacktrace":" at System.RuntimeType.TryChangeType(Object value, Binder binder, CultureInfo culture, Boolean needsSpecialCast)

karataliu commented 9 years ago

@hpraheja I've tried to reproduce this issue. When we only have DateTimeOffset defined in CLR types, both GET and POST should work fine.

It looks like the message is indicating a conversion to 'System.Nullable`1[System.DateTime]'. Then I tried to add a nullable DateTime type in CLR class. Nothing would happen as the model builder just ignores DateTime type, which is not supported in OData V4. Then if I manually add a DateTimeOffset type in model with the same name of the DateTime property. It would behavior as you described. The GET works fine, but POST would return following error.

{
  "error":{
    "code":"","message":"An error has occurred.","innererror":{
      "message":"Object of type 'System.DateTimeOffset' cannot be converted to type 'System.Nullable`1[System.DateTime]'.","type":"System.ArgumentException","stacktrace":"   at System.RuntimeType.TryChangeType(Object value, Binder binder, CultureInfo culture, Boolean needsSpecialCast)\r\n   at System.RuntimeType.CheckValue(Object value, Binder binder, CultureInfo culture, BindingFlags invokeAttr)\r\n   at System.Reflection.MethodBase.CheckArguments(Object[] parameters, Binder binder, BindingFlags invokeAttr, CultureInfo culture, Signature sig)\r\n   at System.Reflection.RuntimeMethodInfo.InvokeArgumentsCheck(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)\r\n   at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)\r\n   at System.Reflection.RuntimePropertyInfo.SetValue(Object obj, Object value, BindingFlags invokeAttr, Binder binder, Object[] index, CultureInfo culture)\r\n   at System.Reflection.RuntimePropertyInfo.SetValue(Object obj, Object value, Object[] index)\r\n   at Microsoft.Restier.EntityFramework.Submit.ChangeSetPreparer.SetValues(Object instance, Type type, IReadOnlyDictionary`2 values)\r\n   at Microsoft.Restier.EntityFramework.Submit.ChangeSetPreparer.<PrepareAsync>d__0.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n   at Microsoft.Restier.Core.Submit.DefaultSubmitHandler.<SubmitAsync>d__0.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n   at Microsoft.Restier.Core.Domain.<SubmitAsync>d__19.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n   at Microsoft.Restier.WebApi.ODataDomainController`1.<Post>d__e.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n   at System.Threading.Tasks.TaskHelpersExtensions.<CastToObject>d__3`1.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n   at System.Web.Http.Controllers.ApiControllerActionInvoker.<InvokeActionAsyncCore>d__0.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n   at System.Web.Http.Controllers.ActionFilterResult.<ExecuteAsync>d__2.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n   at System.Web.Http.Controllers.ExceptionFilterResult.<ExecuteAsync>d__0.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n   at System.Web.Http.Controllers.ExceptionFilterResult.<ExecuteAsync>d__0.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n   at System.Web.Http.Dispatcher.HttpControllerDispatcher.<SendAsync>d__1.MoveNext()"
    }
  }

In this case, your database seems to have a DateTime type column, then it maps to the CLR DateTime type. To use CLR type in model, you can try the following approach: add a wrapper with DateTimeOffset type marked with NotMapped, then manually add it to model. (This should work for basic CRUD operations, but may not support query options.)

public DateTime? dtn { get; set; }

[NotMapped]
public DateTimeOffset? dtnWrapper
{
    get
    {
        if (dtn.HasValue) return new DateTimeOffset(dtn.Value);
        return null;
    }
    set
    {
        if (value.HasValue)
        {
            dtn = value.Value.UtcDateTime;
        }
    }
}

You can also refer following issue for DateTime support: https://github.com/OData/RESTier/issues/14, https://github.com/OData/RESTier/issues/74

hpraheja commented 9 years ago

Thanks for your response. I could not update this thread earlier. Datetimeoffset is working for us but we are still having issues with complex data type.