C# 在“中使用表达式”;选择";LINQ到实体查询的一部分

C# 在“中使用表达式”;选择";LINQ到实体查询的一部分,c#,linq,linq-to-entities,C#,Linq,Linq To Entities,我希望能够重用LINQ to Entities查询的“选择”部分。例如,我可以采取以下措施 projectQuery.Select(p => new ProjectModel ProjectName = p.ProjectName, ProjectNumber = p.ProjectNumber); 并用表达式替换它 projectQuery.Select(ProjectModel.FullSelector); 其中FullSelector如下所示: public st

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

projectQuery.Select(p => new ProjectModel
    ProjectName = p.ProjectName,
    ProjectNumber = p.ProjectNumber);
并用表达式替换它

projectQuery.Select(ProjectModel.FullSelector);
其中FullSelector如下所示:

public static System.Linq.Expressions.Expression<Func<Project, ProjectModel>> FullSelector = project => new ProjectModel
{
    ProjectName = p.ProjectName,
    ProjectNumber = p.ProjectNumber
};
publicstaticsystem.Linq.Expressions.Expression FullSelector=project=>newprojectmodel
{
ProjectName=p.ProjectName,
ProjectNumber=p.ProjectNumber
};
这非常有效,发送到数据库的查询只选择FullSelector使用的字段。此外,每次需要查询项目实体时,我都可以重用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);
};
publicstaticsystem.Linq.Expressions.Expression FullSelector=project=>newprojectmodel
{
ProjectName=p.ProjectName,
ProjectNumber=p.ProjectNumber
Addresses=p.Addresses.Select(AddressModel.FullSelector);
};
这是行不通的。内部选择给出编译时错误“无法从用法推断类型参数。请尝试显式指定类型参数。”

下面的示例进行了编译,但在执行“Internal.NET Framework Data Provider error 1025”查询时崩溃:

publicstaticsystem.Linq.Expressions.Expression FullSelector=project=>newprojectmodel
{
ProjectName=p.ProjectName,
ProjectNumber=p.ProjectNumber
Addresses=p.Addresses.Select(AddressModel.FullSelector.Compile());
};
下一个示例编译但抛出运行时错误“LINQ to Entities无法识别方法'EPIC.WebAPI.Models.AddressModel Invoke(EPIC.Domain.Entities.Address)'方法,并且此方法无法转换为存储表达式。”

publicstaticsystem.Linq.Expressions.Expression FullSelector=project=>newprojectmodel
{
ProjectName=p.ProjectName,
ProjectNumber=p.ProjectNumber
Addresses=p.Addresses.Select(a=>AddressModel.PartialSelector.Compile().Invoke(a));
};
有人知道如何让内部选择工作吗?我理解为什么最后一个示例不起作用,但前两个是否已经接近起作用了


谢谢

那么,首先,为什么您的代码不起作用。第一段:

p.Addresses.Select(AddressModel.FullSelector);
这不起作用,因为导航属性不实现
IQueryable
,而是实现
ICollection
ICollection
当然没有接受
表达式
参数的
Select
方法

第二段:

p.Addresses.Select(AddressModel.FullSelector.Compile());
这不起作用,因为正在编译
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]);
}
现在要调用它,我们可以在地址选择器上调用
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),
        });
publicstaticexpressionfullselector=
AddressModel.FullSelector.Use((项目,
Func选择器)=>新项目模型
{
ProjectName=project.ProjectName,
ProjectNumber=project.ProjectNumber,
地址=项目。地址。选择(选择器),
});
现在,这将完全按照需要工作。

编辑: 我意识到我先前的回答实际上并没有回答这个问题,所以我删除了它;然后我意识到你在问什么,并意识到解决问题的最简单方法很可能是用你的表情说:

Addresses = p.Addresses.AsQueryable().Select(AddressModel.PartialSelector)
其中AddressModel.PartialSelector本身是一个表达式

通过使用
AsQueryable()
方法将
p.Addresses
转换为
IQueryable
,您允许
Select()
方法使用接受表达式的版本,而不必编译它


我希望这有帮助。

不,他不能。这不会创建
表达式
对象,因此查询提供程序将无法检查代码以将其转换为查询。相反,这将简单地将查询的全部内容下拉到内存中的对象中,然后在那里执行转换。避免这样做是很重要的。这实际上有助于我的特殊情况,因为没有其他解决方案,所以它也不是完全没有价值的。我尝试了这个方法,它奏效了。谢谢如果有多个导航属性,例如,如果下面的地址中有一个名为ProjectManager的属性,该如何操作?@buffer您必须创建一个额外的
Use
方法,该方法接受两个选择器,并将它们传递给最后一个选择器。我把这么多的内容编入了这个问题。如果需要为三个、四个等选择器创建一个选择器,我希望您可以看到该模式。我的方法未定义。它在哪里?@对不起,我的坏消息。我想我还需要一杯咖啡,今天喝太多了。
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);
    }
}
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),
        });
Addresses = p.Addresses.AsQueryable().Select(AddressModel.PartialSelector)