使用静态 Func<> 投影单个记录

Projecting a single record with a static Func<>

我一直在使用一种模式从 Entity Framework 投影到业务领域视图。我正在嵌套它,即从另一个投影中调用一个投影。它适用于集合,但我不知道如何使用相同的模式来投影单个实体。

对于集合,我的代码类似于:

public class PersonView
{
    public int Id {get;private set;}
    public string FullName { get; set; }
    public static Expression<Func<Person, PersonView>> Projector = p => new PersonView {
         Id = p.PersonId,
         FullName = p.FirstName + " " + p.LastName
    };
}
//...
context.People.Select(PersonView.Projector).ToList();  // returns a list of PersonViews

如果我创建一个包含 1 元素的列表,或者以其他方式使用 LINQ 发挥创意,我可以让它工作,但如果可能的话我更喜欢更简洁的解决方案。

// convert single element to list, then project it. Works, but is messy
var orderDetails = context.Orders.Where(...)
    .Select(o => new { 
       Id = o.Id, 
       Date = o.Date, 
       PersonView = new [] { o.Person }.AsQueryable().Select(PersonView.Projector).FirstOrDefault()
}).FirstOrDefault();

我想要类似的东西(以下不起作用,因为 linq to entities 无法调用 Func<>):

public class PersonView
{
    public int Id {get;private set;}
    public string FullName { get; set; }
    public static Func<Person, PersonView> ProjectorFn = p => new PersonView {
         Id = p.PersonId,
         FullName = p.FirstName + " " + p.LastName
    };        
    public static Expression<Func<Person, PersonView>> ProjectorExpr = p => ProjectorFn(p);
}
 var orderDetails = context.Orders.Where(...)
    .Select(o => new { 
       Id = o.Id, 
       Date = o.Date, 
       PersonView = PersonView.ProjectorFn(o.Person)
}).FirstOrDefault();

//...
var peopleWithOrders = context.People.Where(p => p.Orders.Any())
    .Select(PersonView.ProjectorExpr);

有什么建议吗?

您需要继续使用直接表达式树,但还要将其编译为普通委托:

public static readonly Func<Person, PersonView> ProjectorFunc = Projector.Compile();

问题的本质是你投影中的下面一行

PersonView = PersonView.ProjectorFn(o.Person)

无法转换为商店查询,因为 ProjectorFn 不再是 Expression 而是通用委托 (Func<Person, PersonView>)。

现在,您真正想要的是使用 PersonView.Projector 字段中包含的原始表达式,但显然您不能,因为它不能被调用(不编译委托)因此不能 return 你想要的 PersonView 类型。

LinqKit 旨在使用其自己的 Invoke() 扩展方法解决此问题,该方法在让您的代码编译的同时,将确保您的表达式被替换回其原始形式。

要启用拦截,您必须使用扩展实体集的 AsExpandable() 方法:

using LinqKit.Extensions;

var orderDetails = context.Orders
    .AsExpandable()
    .Where(...)
    .Select(o => new { 
       Id = o.Id, 
       Date = o.Date, 
       PersonView = PersonView.Projector.Invoke(o.Person)
    })
    .FirstOrDefault();

More on LinqKit