Linq LINQ to Entities 不识别方法“System.String ToString(System.Object)”
今天在写一段Linq查询,准备将数据绑定到jQuery EasyUI框架的 dataGrid控件上,问题出在时间类型的字段(ModifyDate)上。在EasyUI框架下,服务端对象是需要将类对象序列化为JSON格式以传递给客户端,继而绑定到 dataGrid控件进行呈现,可这时间类型字段ModifyDate在客户端显示时出现异常,姑且就叫乱码吧。
一开始,试图通过对该时间字段作客户端处理,比如为它套一个js函数,如:
//时间格式化 function DateTimeFormatter(val, rec) { if (val) { var date = new Date(val.time); alert(val); var d = date.getFullYear() + '-' + (date.getMonth() + 1) + '-' + date.getDate(); var t = date.getHours() + ':' + date.getMinutes() + ':' + date.getSeconds(); return d + " " + t; } else { return ""; } }
出现的结果更是莫名其妙,尽是NaN,白忙活半天。后来经一人提醒,说可在服务端将ModifyDate字段置为字符串形式,再将其序列化为JSON格式。该思路是正确的,只是我走了很多弯路。
首先,在针对Sql Server 2005数据库进行操作时,我使用了如下代码(基于Linq to Entity):
var query = dbContext.B_SystemPage.OrderBy(p => p.PageID) .Where(p => p.IsDeleted == false && string.IsNullOrEmpty(pageName) ? true : p.PageName.Contains(pageName) || p.Remark.Contains(pageName)) .Skip((pageNumber - 1) * pageSize).Take(pageSize) .Select(l => new SystemPageGrid { PageID = l.PageID, PageName = l.PageName, PageUrl = l.PageUrl, IsMenu = l.IsMenu, Remark = l.Remark, ModifyDate = l.ModifyDate.ToString() });
看倒数第二行代码:
l.ModifyDate.ToString()
我天真的以为,只需置一个 ToString()便可万事大吉,却不想被报错:
LINQ to Entities 不识别方法“System.String ToString(System.Object)”
后来听说是因为 EF是为了兼顾多数据库而不允许这么写的。最终我想明白了,EF在向数据库提取数据的过程中,是不允许随便添加任何(包括自定义)函数,那如果我确实想修正一些数据怎么办呢? 答案是:采用本地查询Linq to Objects!
上述的Linq查询,是以 dbContext.B_SystemPage起步的,显然这是基于 Linq to Entity的面向数据库的操作,欲过渡到Linq to Objects操作将需要使用到C# 3.0就引入的匿名类型。
首先,采用Linq to Entity将期望的数据从数据库读取到匿名集合(类型):
string pageName = queryParameter["pageName"]; //Linq to Entity var query = dbContext.B_SystemPage.OrderBy(p => p.PageID) .Where(p => p.IsDeleted == false && string.IsNullOrEmpty(pageName) ? true : p.PageName.Contains(pageName) || p.Remark.Contains(pageName)) .Skip((pageNumber - 1) * pageSize).Take(pageSize) .Select(p => new { p.PageID, p.PageName, p.PageUrl, p.IsMenu, p.ModifyDate, p.Remark }); var list = query.ToList();
在Select从句中,以p字母引领的lambda表达式创建了一个匿名集合(仅有new关键字),且集合元素的属性皆遵循着数据库的原始类型,即p.ModifyDate类型为 DateTime。显然,我没有改变字段任何类型,这时所有期望的分页数据将顺利加载到内存中的匿名集合 list中。
其次,定义好序列化目标类,它是用以转化为JSON的原材料(类):
public class SystemPageGrid { public int PageID { set; get; } public string PageName { set; get; } public string PageUrl { set; get; } public bool? IsMenu { set; get; } public string Remark { set; get; } public string ModifyDate { set; get; } }
可以看到ModifyDate字段是 string型,刚好与客户端脚本框架EasyUI所期望的类型相同,所以接下来的任务便转化为如何将一个内存中的匿名泛型集合投影成为目标类型(SystemPageGrid)的泛型集合,此时该Linq to Objects上场了。
最后一步,针对匿名泛型集合使用Select从句,将集合中的每一个元素的值都投影到目标类型的泛型集合,其问题的关键在于 ToString()方法在Linq to Objects中不被阻拦,当然,自定义方法也可以往上套。
//Linq to Objects pageGridList = list.Select(l => new SystemPageGrid { PageID = l.PageID, PageName = l.PageName, PageUrl = l.PageUrl, IsMenu = l.IsMenu, Remark = l.Remark, ModifyDate = l.ModifyDate.ToString() }).ToList(); return pageGridList;
最后的最后,在编程中,往往我们都希望一步到位,但遗憾的是一些工具因为兼容性问题遭阻拦,那么采用间接的方式倒也没什么大碍,如果这种方式依旧保持着优雅。