我有一个代码优先的EF数据库,其中的对象具有"状态"来跟踪历史记录。它们的实现方式如下:
public class Example
{
public Example()
{
this.Statuses = new HashSet<Status>();
}
public Guid Id { get; set; }
public virtual ICollection<Status> Statuses { get; set; }
}
public class Status
{
public Guid Id { get; set; }
public DateTimeOffset SetOn { get; set; }
public string SetBy { get; set; }
}
我们在代码中有一些实例,我们需要在其中获取最旧或最新的状态。目前,我们一直在使用如下所示的链式 linq 表达式:
var setBy = example.Statuses.OrderByDescending(s => s.SetOn).FirstOrDefault().SetBy;
我认为如果我们可以通过扩展来做到这一点,它会更具可读性,因为获得最新或最旧的状态只是按升序还是降序排序的区别。
像这样的简单扩展方法适用于 linq-to-objects,如果我们已经从数据库中获得了结果:
public static Status Newest(this IQueryable<Status> items)
{
return items.OrderByDescending(s => s.SetOn).FirstOrDefault();
}
但是,如果我在表示我们的数据库的 IQueryable 上运行它,这不起作用,因为 EF 无法将其转换为存储表达式。例如,如果下面的"存储库"是表示 SQL 后端中示例的IQueryable<Example>
,则以下内容将失败:
var date = DateTimeOffset.Parse("4/1/2018");
var query = repository.Where(ex => ex.Statuses.Newest().SetOn > date).FirstOrDefault();
有没有办法将其重构为可以转换为存储表达式的扩展方法或表达式?
这可以通过定义一个从示例返回状态的表达式,并使用 LINQKit 的 Expandable 包装 IQueryable 来完成。使用上面的类,我可以做类似的事情
private Expression<Func<Example, Status>> Newest =
e => e.Statuses.OrderByDescending(s => s.SetOn).FirstOrDefault();
并像
var results = from example in repository.AsExpandable()
select new
{
Example = example,
LatestStatus = Newest.Invoke(example)
};