将 DateTime 作为字符串进行比较到 LINQ to Entities 无法识别 DateTime.parse(s
本文关键字:DateTime Entities 识别 parse to 字符串 比较 LINQ | 更新日期: 2023-09-27 18:33:57
我正在开发一个带有C#,.NET Framework 4.0和Entity Framework Code First的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
属性。这是完全有道理的。
对于序列化,您需要一个 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"
。
如果我见过一个XY问题,这肯定是一个XY问题。 您询问的是将日期时间作为实体框架中的字符串进行比较,而真正的问题是您不喜欢 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
设置,如此处、此处和此处所述,但它很快就会变得复杂。 如果这样做要小心,一定要彻底测试!
另一个想法是编写一个使用 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问题:
- 首先,您选择了 WCF 来构建 rest 终结点,这非常成问题,以至于行业开发了其他解决方案。
- 其次,无法让
DateTime
发出正确的格式,因此决定将其视为字符串,这样就无法将其与 EF 查询中的DateTime
进行比较。
是的,我意识到我没有回答您关于如何将DateTime
输入与实体框架中的字符串属性进行比较的问题,但我认为现在您可以明白原因了。 如果你真的想走这条路,你可能会在SqlFunctions
或EntityFunctions
中找到一些有用的东西 - 但即便如此,你将如何在数据库中的这个列上建立一个有效的索引? 如果您获得大量数据,查询将非常慢。 我建议不要这样做。
你是如何实例化你的DataContractJsonSerializer
的.如果您直接实例化它...您可以传递一个DataContractJsonSerializerSettings
,其中包含用于设置DateTime
序列化方式的DateTimeFormat
。
如果您使用行为来实例化序列化程序,事情会稍微复杂一些。
如果你真的想在将数据传输到客户端时为类使用字符串,你应该有一个单独的 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
这是一个真正的痛苦。