2

我正在测试 Web 窗体的新 ASP.NET 4.5 模型绑定,其中包含一个公开 IQueryable 的简单存储库。存储库使用 EF 5,数据库优先方法。我正在投影 EF 自动生成的实体以使用我的 DTO。

一切正常,这就是重点,我期待看到某种异常......

这是代码:

存储库

    public IQueryable<JobDto> GetJobs()
    {
        var ctx = this.contextResolver.GetCurrentContext<pubsEntities>();

        return ctx.jobs.Select(x => new JobDto
            {
                Description = x.job_desc,
                ID = x.job_id,
                Maximum = x.max_lvl,
                Minimum = x.min_lvl
            });
    }

如您所见,我将我的 EF 实体投影到自定义 DTO 中,并且属性完全不同。

ASPX 代码背后

    public IQueryable<JobDto> gv_GetData()
    {
        return this.jobsRepository.GetJobs();
    }

ASPX

    <asp:GridView runat="server" ID="gv" AllowPaging="true" AllowSorting="true"
        DataKeyNames="ID"
        AutoGenerateColumns="true"
        SelectMethod="gv_GetData"
        ItemType="QueryRepository.JobDto, QueryRepository">
        <Columns>
            <asp:BoundField DataField="Description" HeaderText="My custom description" SortExpression="Description" />
        </Columns>
    </asp:GridView>

当使用很棒的存储库时,这就像一个魅力,开箱即用的分页和排序(现在我不必使用 anObjectDataSource来简化分页和排序之类的事情)

我的问题是:

如果我的存储库正在返回IQueryable<JobDto>,并且我的 DTO 的属性与我的 EF 实体的属性(它是一个名为: 的不同实体job)的属性不同。

GridView既然我GridView配置了我的 DTO 实体中定义的属性名称,EF 怎么可能正确地对我进行排序???据我所知,使用 LINQ 的动态排序是使用字符串来设置顺序标准的。不知何故 LINQ to Entities -IQueryable将我的 DTO 属性自动映射到我的 EF 实体公开的属性。

谁能帮助这个可怜的灵魂=(了解幕后发生的事情??

我运行 SQL 配置文件只是为了确认查询在数据库中正确执行:

SELECT TOP (10) 
[Project1].[C1] AS [C1], 
[Project1].[job_desc] AS [job_desc], 
[Project1].[job_id] AS [job_id], 
[Project1].[max_lvl] AS [max_lvl], 
[Project1].[min_lvl] AS [min_lvl]
FROM ( SELECT [Project1].[job_id] AS [job_id], [Project1].[job_desc] AS [job_desc], [Project1].[min_lvl] AS [min_lvl], [Project1].[max_lvl] AS [max_lvl], [Project1].[C1] AS [C1], row_number() OVER (ORDER BY [Project1].[job_desc] DESC) AS [row_number]
    FROM ( SELECT 
        [Extent1].[job_id] AS [job_id], 
        [Extent1].[job_desc] AS [job_desc], 
        [Extent1].[min_lvl] AS [min_lvl], 
        [Extent1].[max_lvl] AS [max_lvl], 
        1 AS [C1]
        FROM [dbo].[jobs] AS [Extent1]
    )  AS [Project1]
)  AS [Project1]
WHERE [Project1].[row_number] > 0
ORDER BY [Project1].[job_desc] DESC

请特别注意这些行 (ASPX):

<asp:BoundField DataField="Description" SortExpression="Description" />

以及生成的 SQL

ORDER BY [Project1].[job_desc] DESC
4

3 回答 3

5

使用此代码:

return ctx.jobs.Select(x => new JobDto
    {
        Description = x.job_desc,
        ID = x.job_id,
        Maximum = x.max_lvl,
        Minimum = x.min_lvl
    });

Select您调用的重载实际上是创建一个IQueryable影响 EF 发出的实际 SQL 的新的。您实际上还没有将数据对象投影到您的 DTO,而是为 EF 提供了一个表达式,它可以用来为结果集创建查询,一旦查询运行,该结果集将投影到您的 DTO。请注意,在Select被调用的重载中,Expression<Func<Job, JobDto>>传递的是 an,而不仅仅是 a Func<Job, JobDto>。因为 EF 分析表达式,所以它能够在可能的情况下对 SQL 进行复杂的转换。

OrderBy您添加表达式时GridView,它也只是IQueryable使用可以转换为 SQL 的新表达式修改您。

编辑

当您调用上下文SelectIQueryable<Job> jobs属性时,EF 能够查看您Expression<Job, JobDto>并确定:

  1. JobDto您的投影需要哪些列
  2. 如何从表JobDto中填充投影。Job

如果你看一下ExpressionBCL 中的许多类,你就会明白它是如何做到这一点的。编译器在遇到 时x => new JobDto { Description = x.job_desc, ... },会创建一个看起来像这样的复杂表达式树(我正在严重简化它):

LambdaExpression<Func<Job, JobDto>>
    MemberInitExpression
        NewExpression
        Bindings
            MemberAssignment
                Member = Description property
                Expression = MemberExpression representing access to the Job property
            MemberAssignment...
            MemberAssignment...
            MemberAssignment...
            etc.

您可以看到此树如何包含足够的信息,以便 EF 遍历表达式并生成内部映射并生成等效的 SQL 命令。他们基本上是将 .NET 表达式投影到 SQL 表达式。并非所有内容都具有 1:1 映射,但在您的情况下,您可以看到映射是多么简单:

Job type          -> Extent1 alias   -> dbo.jobs table
JobDto projection -> Project1 alias  -> subquery

您会注意到还有其他预测;它引入了一个行号属性和一些包含该值的神秘属性1;我不确定那是用来做什么的。

然后是分析OrderByan 的附加扩充Expression

于 2012-10-11T23:27:32.357 回答
1

这个问题的答案在于 IQueryable 运算符的工作方式。由于 LINQ 查询在延迟模式下执行,因此不会在调用 GetJobs 方法后立即获得结果。

为了在 ASP.NET GridView 上启用排序,我们将属性 SortExpression 设置为列。当我们单击列对数据进行排序时,会创建一个动态 lambda 表达式,并对从 GridView 的 SelectMethod 获得的结果调用 OredrBy 运算符。我们可以说,当我们应用排序时,会调用以下模式的语句:

gv_GetData().OrderBy(j => j.Description);

运算符的执行取决于 GetJobs() 方法返回的类型。再往下走,我们可以说返回给 GridView 的数据是以下结果:

this.jobsRepository.GetJobs().OrderBy(j => j.Description);

再往下走,我们可以假设,最终要评估的 LINQ 查询是:

ctx.jobs.Select(x => new JobDto
        {
            Description = x.job_desc,
            ID = x.job_id,
            Maximum = x.max_lvl,
            Minimum = x.min_lvl
        }).OrderBy(j => j.Description);

解析完所有表达式后,将形成 SQL 查询。在这里,我们有 2 个表达式要解析:一个传递给 Select 运算符,另一个传递给 OrderBy 运算符。解析这两个表达式后获得的数据用于形成 SQL 查询。

如果我们使用返回 IEnumerable 作为 SelectMethod 的方法,则会出现异常。因为,IEnumerable 运算符接受 Func,不能动态形成。

于 2012-10-15T16:42:49.033 回答
1

我认为 EF 完全有可能知道如何生成查询,因为在应用排序之前 IQueryable 可能没有查询任何内容。我想如果你运行这个,你会得到相同的 sql 输出:

var ctx = this.contextResolver.GetCurrentContext<pubsEntities>();

var jobs = ctx.jobs.Select(x => new JobDto
    {
        Description = x.job_desc,
        ID = x.job_id,
        Maximum = x.max_lvl,
        Minimum = x.min_lvl
    }).OrderBy(x => x.Description).ToArray();

编辑:为后代保留我的答案,但雅各布的写得更好。

于 2012-10-11T23:26:35.903 回答