I was testing the new ASP.NET 4.5 model binding for Web Forms, with a simple repository exposing an IQueryable. The repository is using EF 5, Database First approach. I’m projecting the EF auto-generated entity to use my DTO.
Everything works fine, and that’s the point, I was expecting to see some kind of exception…
This is the code:
Repository
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
});
}
As you can see I’m projecting my EF entity into a custom DTO and the properties are totally different.
ASPX Code behind
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>
This works like a charm, paging and sorting out of the box when using a repository which is great (Now I do not have to use an ObjectDataSource to simplify things like paging and sorting)
My question is:
If my repository is returning IQueryable<JobDto> and the properties of my DTO do not have the same name than the properties of my EF entity (which is a different entity named: job).
How is it possible that EF can sort my GridView correctly since my GridView is configured with the property names defined in my DTO entity??? As far as I know, the dynamic sorting using LINQ is done using a string to set the order criteria. Somehow LINQ to Entities – IQueryable is auto-mapping my DTO properties to the properties exposed by my EF entity.
Can anyone help this poor soul =( to understand what’s happening behind the scenes??
I ran a SQL profile just to confirm that the query is being executed correctly in the database:
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
Pay special attention to these lines (ASPX):
<asp:BoundField DataField="Description" SortExpression="Description" />
And the resulting SQL
ORDER BY [Project1].[job_desc] DESC
The answer to this question lies with how IQueryable operators work. As LINQ queries are executed in deferred mode, the result is not obtained as soon as the method GetJobs is called.
To enable sorting on the ASP.NET GridView, we set the property SortExpression to the column. When we click on the column to sort the data, a dynamic lambda expression is created and the OredrBy operator of is invoked on the result obtained from the SelectMethod of the GridView. We may say that, a statement of following pattern is invoked when we apply sorting :
Execution of the operator is dependent on the type returned by GetJobs() method. Stepping another level down, we can say that the data returned to the GridView is result of:
Stepping anothet level down, we may assume that, the final LINQ query that will be evaluated is:
The SQL query will be formed after parsing all the expressions. Here, we have 2 expressions to parse : one passed to the Select operator and the other passed to the OrderBy operator. Data obtained after parsing both the expressions is used to form the SQL Query.
If we use a method returning IEnumerable as SelectMethod, we get an exception. Because, IEnumerable operators accept Func, which cannot be formed dynamically.