我的表单上有两个日期时间选择器。我想要一个函数,该函数将在这两个日期之间返回来自特定表(即特定列的值)的所有日期时间。
我的方法如下:
public DateTime[] GetAllArchiveDates(string username = null)
{
var result = new DateTime[0];
if (username != null)
{
result = this._context.archive.OrderBy(s => s.IssuingDate).Where(s => s.insertedBy == username).Select(s => s.issuing_date).Distinct().ToArray();
}
else
{
result = this._context.archive.OrderBy(s => s.IssuingDate).Select(s => s.issuing_date).Distinct().ToArray();
}
return result;
}
但是我收到此错误:
System.NotSupportedException:'LINQ to Entities不支持指定的类型成员'IssuingDate'。仅支持初始化程序,实体成员和实体导航属性。'
该怎么做?
答案 0 :(得分:1)
您应该注意IEnumerable
和IQueryable
之间的区别。
实现IEnumerable
的类的对象包含要枚举其表示的项目序列的所有内容。您可以要求序列中的第一个项目,一旦获得一个项目,就可以要求下一个项目,直到没有更多项目为止。
另一方面,实现IQueryable
的类的对象保留所有内容,以要求另一个进程提供数据以创建IEnumerable序列。为此,它包含一个Expression
和一个Provider
。
Expression
是开始枚举IQueryable
时必须创建哪种IEnumerable的通用表示形式。
Provider
知道谁必须执行查询,并且知道如何将Expression
转换为执行者可以理解的格式,例如SQL。
有两种LINQ语句。那些使用延迟执行的,那些不使用延迟执行的。延迟的函数可以被识别,因为它们返回IQueryable<TResult>
(或IEnumerable)。例如Where
,Select
,GroupBy
等。
非延迟函数返回TResult
:ToList
,ToDictionary
,FirstOrDefault
,Max
。
只要串联延迟的LINQ函数,就不会执行查询,仅更改Expression
。一旦开始枚举,无论是显式使用GetEnumerator
和MoveNext
,还是隐式使用foreach,ToList,Max等,Expression
都会发送给Provider
,后者将对其进行翻译SQL并执行查询。结果表示为IEnumerable
,在其上执行GetEnumerator
。
这与我的问题有什么关系?
由于必须将Expression
转换为SQL,因此它无法保存您发明的任何内容。毕竟,SQL不知道您的功能。实际上,IQueryable
中不能使用很多标准功能。参见Supported and unsupported LINQ functions
A,您忘记给我们提供archive
类的定义,但是我认为它不是POCO:它包含的功能和属性不仅仅具有get / set的作用。我认为IssuingDate
不仅仅是获取/设置。
对于IQueryable,您应该使类保持简单:在查询期间仅使用{get; set;}
,仅此而已。将IQueryable
实例化为IEnumerable
并将在本地进程中执行后,可以调用其他功能
因此,您有一个数据库,该数据库的表Archive
至少包含列IssuingDate
和InsertedBy
。看来InsertedBy
只是一个字符串。它可能是用户表的外键。这不会对答案产生太大影响。
在entity framework code first conventions之后,出现了以下课程
class Archive
{
public int Id {get; set;}
public DateTime IssuingDate {get; set;}
public string InsertedBy {get; set;}
...
}
public class MyDbContext : DbContext
{
public DbSet<Archive> Archives {get; set;}
}
顺便说一句,您是否有适当的理由经常偏离Microsoft的有关命名标识符的标准,特别是复数和驼峰式命名?
无论如何,您的要求
我的表单上有两个日期时间选择器。我想要一个函数,该函数将在两个日期之间返回来自特定表(即特定列的值)的所有日期时间。
您的代码似乎还有很多工作,但让我们首先编写一个满足您要求的扩展功能。我将其作为存档类的扩展方法编写。这将使您的存档类保持简单(仅{get; set;}
),但为类增加了功能。将其编写为扩展功能也使您可以像使用其他任何LINQ功能一样使用这些功能。参见Extension methods demystified
public static IQueryable<Archive> BetweenDates(this IQueryable<Archive> archives,
DateTime startDate,
DateTime endDate)
{
return archives.Where(archive => startDate <= archive.IssuingDate
&& archive.IssuingDate <= endDate);
}
如果我查看您的代码,那么您在日期之间不会选择存档。您使用userName进行某种操作,然后进行排序,选择不同的...有点奇怪的是,您首先订购了所有百万个档案,然后决定仅保留属于userName的十个档案,并且如果您有多个相同的发布日期您决定删除重复项。在开始订购日期之前先限制发行日期的数量会更有效吗?
public static IQueryable<archive> ToIssuingDatesOfUser(this IQueryable<archive> archives,
string userName)
{
// first limit the number of archives, depdning on userName,
// then select the IssuingDate, remove duplicates, and finally Order
var archivesOfUser = (userName == null) ? archives :
archives.Where(archive => archive.InsertedBy == userName);
return archivesOfUser.Select(archive => archive.IssuingDate)
.Distinct()
.OrderBy(issuingDate => issuingDate);
}
注意:到目前为止,我只创建了IQueryables。因此,仅更改Expression
,这非常有效。数据库尚未通信。
用法示例:
要求:给定一个用户名,一个startDate和一个endDate,请给我该用户发布的所有档案的唯一的issuingDate(以升序排列)
public ICollection<string> GetIssuingDatesOfUserBetweenDates(string userName,
DateTime startDate,
DateTime endDate)
{
using (var dbContext = new MyDbContext(...))
{
return dbContext.Archives
.BetweenDates(startDate, endDate)
.ToIssuingDatesOfUser(userName)
.ToList();
}
}