我正在使用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进行比较吗?
答案 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
开始,其.Kind
为DateTimeKind.Local
。
如果您改为使用DateTime.UtcNow
,则其.Kind
为DateTimeKind.Utc
,并将序列化为/Date(1380924000000)/
。是的,格式的数字部分将是相同的。即使指定了偏移量,数字部分仍与UTC相关。
这只是这种格式的一个问题。另一个是虽然DataContractJsonSerializer
在序列化期间写入本地偏移量,但在反序列化期间它没有正确使用它。它假设如果提供了任何偏移量,那么时间应该是本地的 - ,即使执行反序列化的计算机具有完全不同的偏移量。
在JSON中使用的更好格式是ISO8601格式。例如,此UTC值看起来像2013-10-04T22:00:00.000Z
。虽然如果直接使用它,您可以轻松地将不同的日期格式传递给DataContractJsonSerializer
,但WCF不会轻易将此信息公开给您。
您可以通过自定义WCF邮件格式化程序更改DataContractJsonSerializer
设置,例如描述here,here和here,但它会变得复杂很快。如果你这样做要小心,一定要彻底测试!
另一个想法是编写一个使用JSON.Net而不是DataContractJsonSerializer
的自定义WCF消息格式化程序。 JSON.Net默认使用ISO8601格式,因此您将被设置。
但老实说,最好的解决方案是不要尝试使用WCF来构建REST端点。日期格式问题只是一个开始。在此过程中还会出现各种其他问题。相反,请使用专为此目的而设计的现代框架,例如ASP.Net WebAPI或ServiceStack。
ASP.Net WebAPI使用JSON.Net,而ServiceStack拥有自己的JSON序列化程序,名为ServiceStack.Text。 JSON.Net使用ISO8601作为其默认日期格式。如果您使用服务堆栈,则需要设置JsConfig.DateHandler = JsonDateHandler.ISO8601;
所以回顾一下,你有两个 XY问题:
DateTime
发出正确的格式,因此您决定将其视为字符串,然后您无法与EF查询中的DateTime
进行比较是的,我知道我没有回答你如何将DateTime
输入与实体框架中的字符串属性进行比较的问题,但我认为现在你可以看到原因了。如果真的想要沿着这条路走下去,你可能会在SqlFunctions
或EntityFunctions
找到有用的东西 - 但即使这样,你将如何建立一个有效的指数你的数据库中的列?如果您获得大量数据,查询将非常缓慢。我会反对它。
答案 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
这真是一种痛苦。