c# - 在 LINQ to entities 查询的 "Select"部分使用表达式

标签 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 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 给出编译时错误“无法从用法中推断出类型参数。尝试明确指定类型参数。”

以下示例编译但在执行查询时崩溃,提示“Internal .NET Framework Data Provider error 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));
};

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

谢谢!

最佳答案

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

p.Addresses.Select(AddressModel.FullSelector);

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

第二个片段:

p.Addresses.Select(AddressModel.FullSelector.Compile());

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

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


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

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

这个方法的实现可以简单地用我们拥有的表达式替换使用的委托(delegate)参数的所有实例,然后将其全部包装在一个新的 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,然后编写一个方法来接受我们的普通参数以及地址选择器的委托(delegate):

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

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

关于c# - 在 LINQ to entities 查询的 "Select"部分使用表达式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23658031/

相关文章:

c# - EF 不同对象的相同位置

c# - 使其他类可以访问方法和控件

c# - 如何使用 CSOM 从/向 SharePoint 2013 下载/上传文件?

c# - Linq 性能 : Any vs. 包含

c# - 有条件地获取 Parent 和 Child 对象

c# - IQueryable<>.ToString() 太慢

c# - 计数错误,但我没有在我的 LINQ 表达式中使用计数,

c# - WPF bing map 控制多段线/多边形不首先绘制添加到集合

c# - 具有静态成员的枚举与常量/类?

C# dbml 文件从哪里来?