2015-07-21 4 views
8

ich eine einfache ausgelagertem Linq-Abfrage gegen eine Einheit haben:Entity Framework erzeugt ineffiziente SQL für ausgelagerte Abfrage

var data = (from t in ctx.ObjectContext.Widgets 
      where t.CampaignId == campaignId && 
       t.CalendarEventId == calendarEventId 
       (t.RecurringEventId IS NULL OR t.RecurringEventId = recurringEventId) 
      select t); 

data = data.OrderBy(t => t.Id); 

if (page > 0) 
{ 
    data = data.Skip(rows * (page - 1)).Take(rows); 
} 

var l = data.ToList(); 

ich es erwartet SQL ähnlich wie generieren:

select top 50 * from Widgets w where CampaignId = xxx AND CalendarEventId = yyy AND (RecurringEventId IS NULL OR RecurringEventId = zzz) order by w.Id 

Wenn ich die oben laufen Abfrage in SSMS, es schnell zurück (musste meine Indizes zuerst neu erstellen).

Das generierte SQL ist jedoch unterschiedlich. Es enthält eine verschachtelte Abfrage, wie unten dargestellt:

SELECT TOP (50) 
[Project1].[Id] AS [Id], 
[Project1].[CampaignId] AS [CampaignId] 
<redacted> 
FROM (SELECT [Project1].[Id] AS [Id], 
[Project1].[CampaignId] AS [CampaignId], 
<redacted>, 
row_number() OVER (ORDER BY [Project1].[Id] ASC) AS [row_number] 
    FROM (SELECT 
     [Extent1].[Id] AS [Id], 
     [Extent1].[CampaignId] AS [CampaignId], 
     <redacted> 
     FROM [dbo].[Widgets] AS [Extent1] 
     WHERE ([Extent1].[CampaignId] = @p__linq__0) AND ([Extent1].[CalendarEventId] = @p__linq__1) AND ([Extent1].[RecurringEventId] = @p__linq__2 OR [Extent1].[RecurringEventId] IS NULL) 
    ) AS [Project1] 
) AS [Project1] 
WHERE [Project1].[row_number] > 0 
ORDER BY [Project1].[Id] ASC 

Die Widgets Tabelle ist enorm und die innere Abfrage gibt 100000s von Datensätzen, ein Timeout verursacht.

Kann ich irgendetwas tun, um die Generation zu ändern? Was mache ich falsch? die Ergebnisse zurück relativ schnell

UPDATE

ich es endlich geschafft, meinen Code zu Refactoring:

var data = (from t in ctx.ObjectContext.Widgets 
      where t.CampaignId == campaignId && 
       t.CalendarEventId == calendarEventId 
       (t.RecurringEventId IS NULL OR t.RecurringEventId = recurringEventId) 
      select t)).AsEnumerable().Select((item, index) => new { Index = index, Item = item }); 

      data = data.OrderBy(t => t.Index); 

      if (page > 0) 
      { 
       data = data.Where(t => t.Index >= (rows * (page - 1))); 
      } 

      data = data.Take(rows); 

Hinweis, die page > 0 Logik ist einfach verwendet einen ungültigen Parameter zu verhindern, verwendet werden; es macht keine Optimierung. In der Tat, page > 1, während gültig, bietet keine spürbare Optimierung für die erste Seite; seit dem Where ist kein langsamer Vorgang.

+2

können Sie die Abfrage zeigen planen? Ich verstehe nicht, warum die innere Abfrage hier vollständig abgerufen werden würde. Es ist etwas falsch mit der Art, wie SQL ausgeführt wird. –

+1

Wie schnell ist es, wenn Sie Ihrer Abfrage den Befehl 'order by' hinzufügen? zB 'wähle Top 50 * aus Widgets, wobei CampaignId = xxx AND CalendarEventId = yyy order by id' – Aducci

+1

Dein schnelles SQL hat keine ORDER BY. Was passiert, wenn Sie das hinzufügen? – hvd

Antwort

1

Vor SQL Server 2012 ist der generierte SQL-Code die beste Methode zum Durchführen des Seitenumbaus. Ja, es ist schrecklich und sehr ineffizient, aber es ist das Beste, was Sie tun können, wenn Sie Ihren eigenen SQL-Skript manuell schreiben. Es gibt Tonnen von digitaler Tinte darüber im Netz. Google es einfach.

Auf der ersten Seite kann dies optimiert werden, indem Sie nicht Skip und nur Take tun, aber in jeder anderen Seite Sie sind f *****.

Ein Workaround könnte sein, Ihre eigene rows_number in persistence zu generieren (eine automatische Identität könnte funktionieren) und tun Sie einfach where(widget.number > (page*rows)).Take(rows) in Code. Wenn es einen guten Index in Ihrem widget.number gibt, sollte die Abfrage sehr schnell sein. Aber bricht dies die dynamische orderBy.

Allerdings kann ich in Ihrem Code sehen, dass Sie immer durch widget.id bestellen; Wenn also die dynamische orderBy nicht unbedingt erforderlich ist, könnte dies eine gültige Problemumgehung sein.

Werden Sie Ihre eigene Medizin einnehmen?

könnten Sie mich fragen.

Nein, werde ich nicht. Der beste Weg, um damit fertig zu werden, ist ein Persistence Read-Modell, in dem Sie sogar eine Tabelle pro Widget orderBy Feld mit einem eigenen widget.number haben können. Das Problem ist, dass das Modellieren eines Systems mit einem Persistenz-Lesemodell nur für dieses Problem zu verrückt ist. Ein Lesemodell ist Teil des Gesamtdesigns Ihres Systems und erfordert, dass es von Anfang an beim Entwurf und bei der Entwicklung eines Systems berücksichtigt wird.

+0

Ich denke, die automatische Identität in LINQ ist die Antwort. Wird das Update veröffentlichen, wenn es funktioniert. – Kev

1

Die generierte Abfrage ist so komplex und verschachtelt, weil Sie die Skip-Methode verwendet haben. In T-SQL Take ist leicht erreichbar mit nur Top, aber das ist nicht der Fall mit Skip - um es anzuwenden Sie brauchen row_number und deshalb gibt es eine verschachtelte Abfrage - innere gibt Zeilen mit row_number und äußere Filter sie richtig zu bekommen Anzahl der Zeilen. Ihre Frage:

select top 50 * from Widgets w where CampaignId = xxx AND CalendarEventId = yyy AND (RecurringEventId IS NULL OR RecurringEventId = zzz) order by w.Id 

fehlt Überspringen von ersten Zeilen. Um die Abfrage zu halten sehr effizient wäre es am besten zu, anstelle von Take verwenden und Skip-Paging zu halten, indem Zustand auf Id, weil Sie Ihre Zeilen für Paging-Stationierung auf diesem Feld bestellen:

var data = (from t in ctx.ObjectContext.Widgets 
     where t.CampaignId == campaignId && 
      t.CalendarEventId == calendarEventId 
      (t.RecurringEventId IS NULL OR t.RecurringEventId = recurringEventId) 
     select t); 

data = data 
    .OrderBy(t => t.Id); 
    .Where(t => t.Id >= rows * (page - 1) && t.Id < rows * page) 
    .ToList(); 
+0

Ich glaube nicht, dass das helfen wird. Die IDs der übereinstimmenden Widgets werden nicht bei 1 beginnen, und es können Lücken in den IDs auftreten. – Kev

Verwandte Themen