将DateTime作为字符串比较为LINQ to Entities不识别DateTime.parse(string)

时间:2013-10-05 12:44:16

标签: c# .net linq entity-framework datetime

我正在使用C#,.NET Framework 4.0和实体框架代码优先开发WCF RESTful服务。

我有这个类(表示数据库上的表):

[DataContract]
public class PostLine
{
    public int PostLineId { get; set; }

    [DataMember]
    public int? UserId { get; set; }

    [DataMember]
    public string Description { get; set; }

    [DataMember]
    public string DateUtc { get; set; }

    public User Author { get; set; }
}

我正在尝试这样做:

DateTime fourDaysAgo = DateTime.Now.Date.AddDays(-4);

var postLines = 
    context.PostLines.Where(p => DateTime.Compare(DateTime.Parse(p.DateUtc), fourDaysAgo) > 0).Include("Author");

但是我收到以下错误:

{System.NotSupportedException: LINQ to Entities doesn't recognize the method 'System.DateTime Parse(System.String)', which can not be converted into an expression of the repository.

我需要将PostLine.DateUtc作为字符串,因为我将在我的Web服务上使用它,并将其作为JSON发送,因此我最好将其存储为字符串。

如果我使用DateTime类型,我将在JSON响应中获得类似的内容:

{
    "DateUtc": "/Date(1380924000000+0200)/",
    "Description": "post_1",
    "UserId": 1
}

您知道如何在LINQ表达式上将字符串与DateTime进行比较吗?

4 个答案:

答案 0 :(得分:3)

我认为最好的办法是将财产分成两部分。

实体框架需要DateTime属性。这很有道理。

对于序列化,您需要string属性。这也很有道理。

但是,您尝试为两者使用单个属性,这没有意义,并且没有必要。

[DataContract]
public class PostLine
{
    ...

    public DateTime DateUtcAsDateTime { get; set; }

    [DataMember, NotMapped]
    public string DateUtcAsString {
        get { return DateUtcAsDateTime.ToString(); }
        set { DateUtcAsDateTime = DateTime.Parse(value); }
    }

    ...
}

现在,实体框架将使用DateUtcAsDateTime,实体框架将忽略DateUtcAsString,因为它具有NotMapped属性。

另一方面,

DateUtcAsString是这些属性中唯一具有DataMember属性的属性,因此应该是唯一一个被序列化的属性。

如果需要,您当然可以将其中一个属性重命名为DateUtc

更新:正如Matt Johnson指出的那样,改进的方法是以一种始终产生完全相同字符串的方式指定格式。这可以确保您的字符串不会更改,只是因为您的代码被移动到恰好具有不同区域设置的另一台服务器。

[DataMember, NotMapped]
public string DateUtcAsString {
    get { return DateUtcAsDateTime.ToString("o"); }
    set { DateUtcAsDateTime = DateTime.Parse(value, "o", null, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal); }
}

请注意,我使用DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal代替他建议的DateTimeStyles.RoundTripKind,因为名称DateUtc强烈建议您始终想要UTC,绝不是当地时间。而且我没有明确指定任何文化,因为"o"格式已经独立于文化。

如果您的其他代码更容易处理,您可以使用"r"代替"o"并获得相同的好处。

答案 1 :(得分:2)

如果我看过一个,那肯定是XY problem。您要求将datetime作为实体框架中的字符串进行比较,而真正的问题似乎是您不喜欢WCF默认使用的DataContractJsonSerializer的默认日期格式。

部分问题在于您正在混合使用本地和UTC。您将获得/Date(1380924000000+0200)/,其中包含服务器的本地时区偏移量。这是因为您从DateTime.Now开始,其.KindDateTimeKind.Local

如果您改为使用DateTime.UtcNow,则其.KindDateTimeKind.Utc,并将序列化为/Date(1380924000000)/。是的,格式的数字部分将是相同的。即使指定了偏移量,数字部分仍与UTC相关。

这只是这种格式的一个问题。另一个是虽然DataContractJsonSerializer在序列化期间写入本地偏移量,但在反序列化期间它没有正确使用它。它假设如果提供了任何偏移量,那么时间应该是本地的 - ,即使执行反序列化的计算机具有完全不同的偏移量

在JSON中使用的更好格式是ISO8601格式。例如,此UTC值看起来像2013-10-04T22:00:00.000Z。虽然如果直接使用它,您可以轻松地将不同的日期格式传递给DataContractJsonSerializer,但WCF不会轻易将此信息公开给您。

您可以通过自定义WCF邮件格式化程序更改DataContractJsonSerializer设置,例如描述hereherehere,但它会变得复杂很快。如果你这样做要小心,一定要彻底测试!

另一个想法是编写一个使用JSON.Net而不是DataContractJsonSerializer的自定义WCF消息格式化程序。 JSON.Net默认使用ISO8601格式,因此您将被设置。

但老实说,最好的解决方案是不要尝试使用WCF来构建REST端点。日期格式问题只是一个开始。在此过程中还会出现各种其他问题。相反,请使用专为此目的而设计的现代框架,例如ASP.Net WebAPIServiceStack

ASP.Net WebAPI使用JSON.Net,而ServiceStack拥有自己的JSON序列化程序,名为ServiceStack.Text。 JSON.Net使用ISO8601作为其默认日期格式。如果您使用服务堆栈,则需要设置JsConfig.DateHandler = JsonDateHandler.ISO8601;

所以回顾一下,你有两个 XY问题:

  • 首先,您选择了WCF来构建您的休息端点,这是行业开发其他解决方案的问题。
  • 其次,您无法让DateTime发出正确的格式,因此您决定将其视为字符串,然后您无法与EF查询中的DateTime进行比较

是的,我知道我没有回答你如何将DateTime输入与实体框架中的字符串属性进行比较的问题,但我认为现在你可以看到原因了。如果真的想要沿着这条路走下去,你可能会在SqlFunctionsEntityFunctions找到有用的东西 - 但即使这样,你将如何建立一个有效的指数你的数据库中的列?如果您获得大量数据,查询将非常缓慢。我会反对它。

答案 2 :(得分:1)

我不知道你是如何实例化DataContractJsonSerializer的。如果您直接实例化它......您可以传递DataContractJsonSerializerSettings DateTimeFormat来设置DateTime的序列化方式。

如果您正在使用行为来实例化您的Serializer,那么事情就会复杂一些。

答案 3 :(得分:0)

如果您真的想在将数据传输到客户端时为您的班级使用字符串,那么您应该有一个单独的DTO类。

然后,您可以使用AutoMapper等库将PostLine类映射到PostLineDto,并附带表达式。

然而,您将面临的下一个问题是,Expression<Func<PostLine,PostLineDto>>将包含Expression.MethodCall(DateTime.Parse),您必须注入可以转换的ExpressionVisitor ...

p => p.DateUtc > DateTime.Parse("2013 Aug 1") - bool

p => p.DateUtc > new DateTime(1, Aug, 2013) - bool

这真是一种痛苦。