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

时间:2014-05-14 14:47:52

标签: c# linq linq-to-entities

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

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
};

这很好用,发送到数据库的查询只选择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);
};

这不起作用。内部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());
};

下一个示例编译但抛出运行时错误“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));
};

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

谢谢!

2 个答案:

答案 0 :(得分:5)

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

p.Addresses.Select(AddressModel.FullSelector);

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

第二个片段:

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]);
}

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

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);
    }
}

现在要调用它,我们可以在地址选择器上调用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),
        });

现在这将完全符合要求。

答案 1 :(得分:0)

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

Addresses = p.Addresses.AsQueryable().Select(AddressModel.PartialSelector)

其中AddressModel.PartialSelector是表达式本身。

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

我希望这会有所帮助。

相关问题