【问题标题】:Handling Dates with OData v4, EF6 and Web API v2.2使用 OData v4、EF6 和 Web API v2.2 处理日期
【发布时间】:2014-09-09 20:27:40
【问题描述】:

我正在从 v1-3 升级到 v4,但遇到了一些问题。

我的理解是不支持 DateTime,我必须始终使用 DateTimeOffset。很好。

但在我将 Sql date 数据类型存储在 DateTime 之前,现在似乎我收到了这个错误:

Member Mapping specified is not valid. The type 'Edm.DateTimeOffset[Nullable=False,DefaultValue=,Precision=]' of member 'CreatedDate' in type 'MyEntity' is not compatible with 'SqlServer.date[Nullable=False,DefaultValue=,Precision=0]'

解决这个问题的方法是什么?我需要能够在数据库中专门存储日期(时间和地点并不重要)。如果我能得到 Edm.Date 以及返回的数据类型,那就太好了,但我以前没有。

谢谢。

编辑:示例类

之前:

public class Ticket
{
    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }

    [Required, MaxLength(50)]
    public string Reference { get; set; }

    [Column(TypeName = "date")]
    public DateTime LoggedDate { get; set; }
}

之后:

public class Ticket
{
    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }

    [Required, MaxLength(50)]
    public string Reference { get; set; }

    [Column(TypeName = "date")]
    public DateTimeOffset LoggedDate { get; set; }
}

这在 EF 中无效。

【问题讨论】:

    标签: entity-framework asp.net-web-api odata asp.net-web-api2


    【解决方案1】:

    您可以参考下面的链接来定义您的DateTimeAndDateTimeOffsetWrapper 来进行两种类型之间的转换。

    http://www.odata.org/blog/how-to-use-sql-spatial-data-with-wcf-odata-spatial/

    在你的模型上定义两个属性,一个是DateTime,它只存在于Edm模型中,另一个是DateTimeOffset,它只存在于DB中。


    如果上述解决方案不符合您的要求,您必须将数据更改为DateTime在将其保存到数据库之前并在从数据库中检索数据后将其更改回DateTimeOffset控制器动作。

    您可以定义两个几乎相同的类来实现这一点。唯一的区别是一个有DateTime 属性,另一个有DateTimeOffset 属性。

    前者用于EF和映射到DB。

    后者用于定义OData Edm模型并呈现给用户。

    正如我上面所说,你必须在保存数据之前和检索数据之后在这两个类之间进行转换。

    【讨论】:

    • 听起来第二个选项可能是我需要的,但我不会失去所有 $expand 功能,因为它不再是一个数据库实体吗?
    • 我还添加了类来演示问题。
    • 您可以将数据库中的数据作为 OldTicket 检索,然后将数据转换为 NewTicket。完成此操作后,您可以应用 $expand 和其他查询选项。但是,这部分将使用 linq-to-objects 在内存中完成,而不是旧方式的 linq-to-entities。无论如何,这是一种解决方法。
    • 对它的 hacky 不太满意。希望 MS 将产生一个功能库,而不是现在的混乱。在那之前我会坚持使用 OData v3。如果没有其他人提供更容易的解决方法,我会奖励你。
    • 关注关于 DateTime 的 codeplex 问题的状态:) aspnetwebstack.codeplex.com/workitem/2072 现在空间类型也有同样的问题,因为 EF 无法映射这些 Edm 空间类型。空间类型由aspnetwebstack.codeplex.com/workitem/1010 跟踪
    【解决方案2】:

    一种选择是在实体中定义一个新属性。说 Title 映射到 EF:

    public partial class Title
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public Nullable<System.DateTime> CreatedOn { get; set; }
    }
    

    然后添加一个新的 DateTimeOffset 属性:

    public partial class Title
    {
        [NotMapped]
        public DateTimeOffset? EdmCreatedOn
        {
            // Assume the CreateOn property stores UTC time.
            get
            {
                return CreatedOn.HasValue ? new DateTimeOffset(CreatedOn.Value, TimeSpan.FromHours(0)) : (DateTimeOffset?)null;
            }
            set
            {
                CreatedOn = value.HasValue ? value.Value.UtcDateTime : (DateTime?)null;
            }
        }
    }
    

    生成 OData 模型的代码如下:

        public static IEdmModel GetModel()
        {
            ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
            EntityTypeConfiguration<Title> titleType= builder.EntityType<Title>();
            titleType.Ignore(t => t.CreatedOn);
            titleType.Property(t => t.EdmCreatedOn).Name = "CreatedOn";
    
            builder.EntitySet<Title>("Titles");
    
            builder.Namespace = typeof(Title).Namespace;
    
            return builder.GetEdmModel();
        }
    }
    

    控制器看起来像:

    public class TitlesController : ODataController
    {
        CustomerManagementSystemEntities entities = new CustomerManagementSystemEntities();
    
        [EnableQuery(PageSize = 10, MaxExpansionDepth = 5)]
        public IHttpActionResult Get()
        {
            IQueryable<Title> titles = entities.Titles;
            return Ok(titles);
        }
    
        public IHttpActionResult Post(Title title)
        {
            entities.Titles.Add(title);
            return Created(title);
        }
    }
    

    【讨论】:

    • 此解决方案是正确的,因为它不会更改生成的元数据,请将此解决方案标记为正确的解决方案
    • 实际上,这段代码应该会出现运行时错误。对于 Nullable 或 DateTime? 的任何实体属性,运行时都会出错。如果您在 ODataConventionModelBuilder 配置级别忽略该属性,则无关紧要。至少这是我得到的。
    • 列出的代码确实有效,直到您尝试执行 $select。
    • 有人破解过这个密码吗?如果您无法在没有错误的情况下对其进行 $select ,则不确定为什么将其标记为答案。
    【解决方案3】:

    您可以添加 AppendDatetimeOffset 方法以自动添加方法 使用 microsoft T4 引擎(即更新模板文件 *.tt)。这样在重新生成代码时,您不必再次附加类。希望这会有所帮助:)

    public string Property(EdmProperty edmProperty)
        {
            return string.Format(
                CultureInfo.InvariantCulture,
                (_ef.IsKey(edmProperty) ? "[Key]" : "") +
                "{0} {1} {2} {{ {3}get; {4}set; }}  {5}",           
                Accessibility.ForProperty(edmProperty),
                _typeMapper.GetTypeName(edmProperty.TypeUsage),
                _code.Escape(edmProperty),
                _code.SpaceAfter(Accessibility.ForGetter(edmProperty)),
                _code.SpaceAfter(Accessibility.ForSetter(edmProperty)),
                AppendDateTimeOffset(edmProperty));
        }
    
        public string AppendDateTimeOffset(EdmProperty edmProperty){
    
         if(!_typeMapper.GetTypeName(edmProperty.TypeUsage).Contains("DateTime")) return " ";
         //proceed only if date time
         String paramNull = @"public Nullable<System.DateTimeOffset> edm{0} 
                            {{
                                get
                                {{
                                    return {0}.HasValue ? new DateTimeOffset({0}.Value, TimeSpan.FromHours(0)) : (DateTimeOffset?)null;
                                }}
                            }}"; 
    
        String paramNotNull = @"public System.DateTimeOffset edm{0} 
                            {{
                                get
                                {{
                                    return new DateTimeOffset({0}, TimeSpan.FromHours(0));
                                }}
                            }}"; 
    
            String s= String.Empty;
            if(edmProperty.Nullable){
            s = string.Format(paramNull, edmProperty.Name);     
            }else
            {
            s = string.Format(paramNotNull, edmProperty.Name);      
            }
            return s;
    
        }
    

    【讨论】:

      【解决方案4】:

      对于将来遇到此问题的任何人,OData v4 团队已解决此问题。

      [Column(TypeName = "date")]
      public DateTime Birthday { get; set; }
      

      这现在将自动解析为 Edm.Date。

      如果您像我一样按照惯例使用date 类型,则必须手动将属性声明为日期,以免它们被自动解析为 DateTimeOffset。 OData 目前不允许您添加自己的约定。

      customer.Property(c => c.Birthday).AsDate();
      

      http://odata.github.io/WebApi/#12-01-DateAndTimeOfDayWithEF

      【讨论】:

      • 您可以使用约定将其应用于整个模型,只要每个 DateTime 属性都应解释为日期。您可以编写约定,以便在未定义具有 Type 属性的 DateType 或 Column 时使用 Date,这将允许约定和属性表示法共存。
      猜你喜欢
      • 2014-12-28
      • 1970-01-01
      • 2015-12-23
      • 2014-10-01
      • 1970-01-01
      • 1970-01-01
      • 2015-02-11
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多