使用LINQ的"选择"部分中的表达式来实体查询

Bum*_*per 9 c# linq linq-to-entities

我希望能够重用我的LINQ to Entities查询的"选择"部分.例如,我可以采取以下措施......

projectQuery.Select(p => new ProjectModel
    ProjectName = p.ProjectName,
    ProjectNumber = p.ProjectNumber);
Run Code Online (Sandbox Code Playgroud)

并用表达式替换它......

projectQuery.Select(ProjectModel.FullSelector);
Run Code Online (Sandbox Code Playgroud)

其中FullSelector看起来像这样:

public static System.Linq.Expressions.Expression<Func<Project, ProjectModel>> FullSelector = project => new ProjectModel
{
    ProjectName = p.ProjectName,
    ProjectNumber = p.ProjectNumber
};
Run Code Online (Sandbox Code Playgroud)

这很好用,发送到数据库的查询只选择FullSelector使用的字段.另外,每次我需要查询Project实体时,我都可以重用FullSelector.

现在是棘手的部分.在执行包含导航属性的查询时,嵌套的选择器表达式不起作用.

public static System.Linq.Expressions.Expression<Func<Project, ProjectModel>> FullSelector = project => new ProjectModel
{
    ProjectName = p.ProjectName,
    ProjectNumber = p.ProjectNumber
    Addresses = p.Addresses.Select(AddressModel.FullSelector);
};
Run Code Online (Sandbox Code Playgroud)

这不起作用.内部Select给出了编译时错误"无法根据用法推断出类型参数.请尝试明确指定类型参数."

以下示例编译但在执行查询时说"内部.NET Framework数据提供程序错误1025"时崩溃.

public static System.Linq.Expressions.Expression<Func<Project, ProjectModel>> FullSelector = project => new ProjectModel
{
    ProjectName = p.ProjectName,
    ProjectNumber = p.ProjectNumber
    Addresses = p.Addresses.Select(AddressModel.FullSelector.Compile());
};
Run Code Online (Sandbox Code Playgroud)

下一个示例编译但抛出运行时错误"LINQ to Entities无法识别方法'EPIC.WebAPI.Models.AddressModel Invoke(EPIC.Domain.Entities.Address)'方法,并且此方法无法转换为商店表达式. "

public static System.Linq.Expressions.Expression<Func<Project, ProjectModel>> FullSelector = project => new ProjectModel
{
    ProjectName = p.ProjectName,
    ProjectNumber = p.ProjectNumber
    Addresses = p.Addresses.Select(a => AddressModel.PartialSelector.Compile().Invoke(a));
};
Run Code Online (Sandbox Code Playgroud)

有谁知道如何让内部选择工作?我理解为什么最后一个例子不起作用,但前两个接近工作?

谢谢!

Ser*_*rvy 5

所以,首先,为什么你的代码不起作用.第一个片段:

p.Addresses.Select(AddressModel.FullSelector);
Run Code Online (Sandbox Code Playgroud)

这不起作用,因为导航属性没有实现IQueryable,它们实现ICollection. ICollection当然没有Select接受Expression参数的方法.

第二个片段:

p.Addresses.Select(AddressModel.FullSelector.Compile());
Run Code Online (Sandbox Code Playgroud)

这不起作用,因为FullSelector正在编译.由于它正在编译,查询提供程序无法查看方法的主体并将代码转换为SQL代码.

第三个片段与第二个片段具有完全相同的问题.将其包裹在lambda中并不会改变这一事实.


那么,既然我们知道为什么你的代码不起作用,现在该怎么办?

这有点令人难以置信,我不是这种方法设计的忠实粉丝,但我们走了.我们将编写一个方法来接受表示具有一个参数的函数的表达式,然后它将接受另一个接受一些不相关类型的函数,然后接受与第一个参数中的委托相同类型的函数,然后返回一个不相关的类型.

这个方法的实现可以简单地替换我们拥有的表达式使用的委托参数的所有实例,然后将它们全部包装在一个新的lambda中:

public static Expression<Func<T1, T2>> Use<T1, T2, T3, T4>(
    this Expression<Func<T3, T4>> expression,
    Expression<Func<T1, Func<T3, T4>, T2>> other)
{
    return Expression.Lambda<Func<T1, T2>>(
        other.Body.Replace(other.Parameters[1], expression),
        other.Parameters[0]);
}
//another overload if there are two selectors
public static Expression<Func<T1, T2>> Use<T1, T2, T3, T4, T5, T6>(
    this Expression<Func<T3, T4>> firstExpression,
    Expression<Func<T5, T6>> secondExpression,
    Expression<Func<T1, Func<T3, T4>, Func<T5, T6>, T2>> other)
{
    return Expression.Lambda<Func<T1, T2>>(
        other.Body.Replace(other.Parameters[1], firstExpression)
            .Replace(other.Parameters[2], secondExpression),
        other.Parameters[0]);
}
Run Code Online (Sandbox Code Playgroud)

这个想法有点令人难以置信,但代码实际上很短.它依赖于此方法将一个表达式的所有实例替换为另一个:

public static Expression Replace(this Expression expression,
    Expression searchEx, Expression replaceEx)
{
    return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}
internal class ReplaceVisitor : ExpressionVisitor
{
    private readonly Expression from, to;
    public ReplaceVisitor(Expression from, Expression to)
    {
        this.from = from;
        this.to = to;
    }
    public override Expression Visit(Expression node)
    {
        return node == from ? to : base.Visit(node);
    }
}
Run Code Online (Sandbox Code Playgroud)

现在调用它我们可以调用Use我们的地址选择器,然后编写一个接受我们的普通参数以及地址选择器的委托的方法:

public static Expression<Func<Project, ProjectModel>> FullSelector =
    AddressModel.FullSelector.Use((Project project,
        Func<Address, AddressModel> selector) => new ProjectModel
        {
            ProjectName = project.ProjectName,
            ProjectNumber = project.ProjectNumber,
            Addresses = project.Addresses.Select(selector),
        });
Run Code Online (Sandbox Code Playgroud)

现在,这将完全按照预期工作.