编译以存储方法的 Linq 扩展方法



我有一个代码优先的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)
};

最新更新