Для ясности, LINQ to Entities не генерирует SQL. Вместо этого он генерирует дерево канонических команд ADO.NET, а поставщик ADO.NET для вашей базы данных, в данном случае, предположительно, SQL Server, генерирует SQL.
Так почему же он генерирует эту производную таблицу (я думаю, что «производная таблица» является более правильным термином для используемой здесь функции SQL)? Поскольку код, который генерирует SQL, должен генерировать SQL для широкого спектра запросов LINQ, большинство из которых не так тривиальны, как тот, который вы показываете. Эти запросы часто выбирают данные для нескольких типов (многие из которых могут быть анонимными, а не именованными типами), и для того, чтобы генерирование SQL было относительно нормальным, они группируются в экстенты для каждого типа.
Еще один вопрос: почему тебя это должно волновать? Легко продемонстрировать, что использование производной таблицы в этом выражении «свободно» с точки зрения производительности.
Я случайно выбрал таблицу из заполненной базы данных и выполнил следующий запрос:
SELECT [AddressId]
,[Address1]
,[Address2]
,[City]
,[State]
,[ZIP]
,[ZIPExtension]
FROM [VertexRM].[dbo].[Address]
Давайте посмотрим на стоимость:
<StmtSimple StatementCompId="1" StatementEstRows="7900" StatementId="1" StatementOptmLevel="TRIVIAL" StatementSubTreeCost="0.123824" StatementText="/****** Script for SelectTopNRows command from SSMS ******/
SELECT [AddressId]
 ,[Address1]
 ,[Address2]
 ,[City]
 ,[State]
 ,[ZIP]
 ,[ZIPExtension]
 FROM [VertexRM].[dbo].[Address]" StatementType="SELECT">
<StatementSetOptions ANSI_NULLS="false" ANSI_PADDING="false" ANSI_WARNINGS="false" ARITHABORT="true" CONCAT_NULL_YIELDS_NULL="false" NUMERIC_ROUNDABORT="false" QUOTED_IDENTIFIER="false" />
<QueryPlan CachedPlanSize="9" CompileTime="0" CompileCPU="0" CompileMemory="64">
<RelOp AvgRowSize="246" EstimateCPU="0.008847" EstimateIO="0.114977" EstimateRebinds="0" EstimateRewinds="0" EstimateRows="7900" LogicalOp="Clustered Index Scan" NodeId="0" Parallel="false" PhysicalOp="Clustered Index Scan" EstimatedTotalSubtreeCost="0.123824">
Теперь давайте сравним это с запросом с производной таблицей:
SELECT
[Extent1].[AddressId]
,[Extent1].[Address1]
,[Extent1].[Address2]
,[Extent1].[City]
,[Extent1].[State]
,[Extent1].[ZIP]
,[Extent1].[ZIPExtension]
FROM (SELECT [AddressId]
,[Address1]
,[Address2]
,[City]
,[State]
,[ZIP]
,[ZIPExtension]
FROM[VertexRM].[dbo].[Address]) AS [Extent1]
А стоимость:
<StmtSimple StatementCompId="1" StatementEstRows="7900" StatementId="1" StatementOptmLevel="TRIVIAL" StatementSubTreeCost="0.123824" StatementText="/****** Script for SelectTopNRows command from SSMS ******/
SELECT 
 [Extent1].[AddressId]
 ,[Extent1].[Address1]
 ,[Extent1].[Address2]
 ,[Extent1].[City]
 ,[Extent1].[State]
 ,[Extent1].[ZIP]
 ,[Extent1].[ZIPExtension]
 FROM (SELECT [AddressId]
 ,[Address1]
 ,[Address2]
 ,[City]
 ,[State]
 ,[ZIP]
 ,[ZIPExtension]
 FROM[VertexRM].[dbo].[Address]) AS [Extent1]" StatementType="SELECT">
<StatementSetOptions ANSI_NULLS="false" ANSI_PADDING="false" ANSI_WARNINGS="false" ARITHABORT="true" CONCAT_NULL_YIELDS_NULL="false" NUMERIC_ROUNDABORT="false" QUOTED_IDENTIFIER="false" />
<QueryPlan CachedPlanSize="9" CompileTime="0" CompileCPU="0" CompileMemory="64">
<RelOp AvgRowSize="246" EstimateCPU="0.008847" EstimateIO="0.114977" EstimateRebinds="0" EstimateRewinds="0" EstimateRows="7900" LogicalOp="Clustered Index Scan" NodeId="0" Parallel="false" PhysicalOp="Clustered Index Scan" EstimatedTotalSubtreeCost="0.123824">
В обоих случаях SQL Server просто сканирует кластерный индекс. Не удивительно, что стоимость почти точно такая же.
Давайте посмотрим на немного более сложный запрос. Я запустил LINQPad и ввел следующий запрос к той же таблице плюс одна связанная таблица:
from a in Addresses
select new
{
Id = a.Id,
Address1 = a.Address1,
Address2 = a.Address2,
City = a.City,
State = a.State,
ZIP = a.ZIP,
ZIPExtension = a.ZIPExtension,
PersonCount = a.EntityAddresses.Count()
}
Это генерирует следующий SQL:
SELECT
1 AS [C1],
[Project1].[AddressId] AS [AddressId],
[Project1].[Address1] AS [Address1],
[Project1].[Address2] AS [Address2],
[Project1].[City] AS [City],
[Project1].[State] AS [State],
[Project1].[ZIP] AS [ZIP],
[Project1].[ZIPExtension] AS [ZIPExtension],
[Project1].[C1] AS [C2]
FROM ( SELECT
[Extent1].[AddressId] AS [AddressId],
[Extent1].[Address1] AS [Address1],
[Extent1].[Address2] AS [Address2],
[Extent1].[City] AS [City],
[Extent1].[State] AS [State],
[Extent1].[ZIP] AS [ZIP],
[Extent1].[ZIPExtension] AS [ZIPExtension],
(SELECT
COUNT(cast(1 as bit)) AS [A1]
FROM [dbo].[EntityAddress] AS [Extent2]
WHERE [Extent1].[AddressId] = [Extent2].[AddressId]) AS [C1]
FROM [dbo].[Address] AS [Extent1]
) AS [Project1]
Анализируя это, мы видим, что Project1
- это проекция на анонимный тип. Extent1
является Address
таблицей / сущностью. И Extent2
- таблица для ассоциации. Теперь нет производной таблицы для Address
, но есть одна для проекции.
Я не знаю, писали ли вы когда-нибудь систему генерации SQL, но это нелегко. Я считаю, что общая проблема доказательства того, что запрос LINQ to Entities и SQL-запрос эквивалентны, является NP-трудной, хотя некоторые конкретные случаи, очевидно, намного проще. SQL намеренно неполон по Тьюрингу, потому что его разработчики хотели, чтобы все запросы SQL выполнялись за ограниченное время. LINQ, не так.
Короче говоря, эту проблему очень трудно решить, и сочетание Entity Framework и его провайдеров иногда жертвует некоторой читабельностью в пользу согласованности по широкому кругу запросов. Но это не должно быть проблемой производительности.