2015-08-11 7 views
14

Ich habe eine Tabelle, die wie folgt aussieht:Linq to SQL Zählung gruppierten Elemente Erzeugen eines Timeout

FruitID | FruitType 
    23 | 2 
    215 | 2 
    256 | 1 
    643 | 3 

Ich möchte die Zählung von FruitType gegeben eine Liste von FruitIDsTheFruitIDs genannt bekommen. Das ist, was ich habe:

var TheCounter = (from f in MyDC.Fruits  
        where TheFruitIDs.Contains(f.FruitID) 
        group f by 0 into TheFruits 
        select new MyCounterMode() 
        { 
        CountType1 = (int?) TheFruits.Where(f => f.FruitType == 1).Count() ?? 0, 
        CountType2 = (int?) TheFruits.Where(f => f.FruitType == 2).Count() ?? 0, 
        .... all the way to CountType6  
        }).Single(); 

Dieser Code funktioniert, aber das Problem ist, dass ich manchmal einen Timeout-Fehler erhalten, da die Abfrage zu lange läuft. Wie kann ich diesen Code ändern, um das Timeout-Problem zu vermeiden?

+1

Sind Sie sicher, dass dies zu einer einzigen Abfrage übersetzt? Es scheint, dass die Projektion möglicherweise 6 neue Abfragen pro Zeile ergeben könnte. Ist das der Fall? –

+0

Das beantwortet Ihre Frage nicht, aber 'Count()' gibt ein 'int' zurück, nicht' int? ', Selbst wenn es in SQL konvertiert wird; Daher ist der Koaleszenzoperator Null unnötig (und wird wahrscheinlich nicht einmal funktionieren). Dies liegt daran, dass beim Casting auf "int"?'Es wird nie einen Nullwert haben, da der Wert von 0 nicht null ist. Ich bin überrascht, dass das kompiliert wurde, ohne den Cast in einen zusätzlichen Satz von Klammern zu verpacken, aber das könnte ein Copy-Paste-Ding sein. – JNYRanger

+1

Sie müssen sich das generierte SQL ansehen und es im Query Analyzer ausführen. – Magnus

Antwort

6

Der einfachste Weg, Sie Abfrage zu tun ist, zu einer Gruppe von FruitType und dann die Zeilen zählen:

var countsDictionary = MyDC 
    .Fruits 
    .Where(f => TheFruitIDs.Contains(f.FruitID)) 
    .GroupBy(
    f => f.FruitType, 
    (fruitType, fruits) => new { FruitType = fruitType, Count = fruits.Count() } 
) 
    .ToDictionary(c => c.FruitType, c => c.Count); 

Dies wird im folgenden Wörterbuch effizient erstellen (wurde von dem where Teil ausgeschlossen keine Daten vorausgesetzt) :

 
FruitType | Count 
----------+------ 
1   | 1 
2   | 2 
3   | 1 

Wenn Sie wirklich diese müssen Sie dann für bestimmte Obstsorten, die zählt zu einem einzigen Objekt wollen zusammenzubrechen erstellen diesen obj ect:

var TheCounter = new { 
    CountType1 = countsDictionary.ContainsKey(1) ? countsDictionary[1] : 0, 
    CountType2 = countsDictionary.ContainsKey(2) ? countsDictionary[2] : 0, 
    CountType3 = countsDictionary.ContainsKey(3) ? countsDictionary[3] : 0 
}; 

Es gibt eine andere Sache in Ihrer Abfrage ist, die Leistung könnte Probleme möglicherweise zu Timeouts verursacht: Die Liste der Frucht-IDs im where Teil in der Abfrage enthalten ist, und wenn diese Liste sehr groß ist, kann es verlangsamen Sie Ihre Abfrage. Sie können nichts dagegen tun, es sei denn, Sie erstellen diese Liste aus einer vorherigen Abfrage in der Datenbank. In diesem Fall sollten Sie versuchen, die Liste der Frucht-IDs nicht auf die Client-Seite zu ziehen. Stattdessen sollten Sie die Abfrage kombinieren, die die IDs mit dieser Abfrage auswählt, die die Typen zählt. Dadurch wird sichergestellt, dass die gesamte Abfrage serverseitig ausgeführt wird.

Sie scheinen über die strukturelle Änderung des Codes besorgt zu sein. Solange Sie anonyme Objekte erstellen, ist es schwer, wiederverwendbaren Code zu schreiben. Sie könnten in Erwägung ziehen, das Wörterbuch nur mit den Zählern oder ähnlichem zu verwenden. Eine weitere Möglichkeit besteht darin, ein dynamisches Objekt mit den Zählungen zu erstellen. Persönlich mag ich diese Lösung nicht, aber Sie können es nützlich finden.

der Code eine Klasse Zur Vereinfachung der Zählungen zu speichern, wird benötigt:

class TypeCount { 

    public TypeCount(Int32 type, Int32 count) { 
    Type = type; 
    Count = count; 
    } 

    public Int32 Type { get; private set; } 

    public Int32 Count { get; private set; } 

} 

Ein dynamisches Objekt, das CountType0 Eigenschaften, CountType1, CountType2 usw., basierend auf einer Sequenz von Tupeln:

class CountsDictionary : DynamicObject { 

    readonly IDictionary<Int32, Int32> counts; 

    public CountsDictionary(IEnumerable<TypeCount> typeCounts) { 
    if (typeCounts== null) 
     throw new ArgumentNullException("typeCounts"); 
    this.counts = typeCounts.ToDictionary(c => c.Type, c => c.Count); 
    } 

    public override Boolean TryGetMember(GetMemberBinder binder, out Object result) { 
    Int32 value; 
    if (binder.Name.StartsWith("CountType") && Int32.TryParse(binder.Name.Substring(9), NumberStyles.None, CultureInfo.InvariantCulture, out value) && value >= 0) { 
     result = this.counts.ContainsKey(value) ? this.counts[value] : 0; 
     return true; 
    } 
    result = 0; 
    return false; 
    } 

} 

Eine Erweiterungsmethode zum Erstellen des dynamischen Objekts:

static class CountExtensions { 

    public static dynamic ToCounts(this IEnumerable<TypeCount> typeCounts) { 
    return new CountsDictionary(typeCounts); 
    } 

} 

Dass sie alle zusammen:

var counts = MyDC 
    .Fruits 
    .Where(f => TheFruitIDs.Contains(f.FruitID)) 
    .GroupBy(
    f => f.FruitType, 
    (fruitType, fruits) => new TypeCount(fruitType, fruits.Count()) 
) 
    .ToCounts(); 

Sie dann counts.CountType1 Eigenschaften abrufen können, counts.CountType2 und counts.CountType3. Andere count.CountType# Eigenschaften werden 0 zurückgeben. Da counts jedoch dynamisch ist, erhalten Sie kein Intellisense. Diese

+0

Ich brauche die Gruppierung und das Zählerobjekt, die im DB und nicht im Speicher eingetragen werden sollen. – frenchie

+0

@Frenchie: Die Gruppierung und die Zählung werden alle auf der Serverseite durchgeführt. Das Ausführen von "ToList" oder in diesem Fall "ToDictionary" wird dann die Zeilen, die den Fruchttyp und die Zählung enthalten, auf die Clientseite ziehen. Ich entschied mich, 'ToDictionary' zu verwenden, um das Nachschlagen jeder Zählung effizienter zu machen, aber mit nur wenigen Zeilen könnte man auch' ToList' verwenden und dann eine lineare Suche in der Liste für jede der Zählungen durchführen. –

+0

Ja, aber das Problem ist, dass MyCounterModel nicht in der DB aufgefüllt ist. Wie kann ich es so füllen, wie ich es in meiner Problemabfrage habe? – frenchie

0

Sie müssen daran denken, dass select für jede Iteration ausgeführt wird !!

versuchen So etwas wie:

'var TheCounter = (from f in MyDC.Fruits  
        group f by f.FruitID into TheFruits 
        select new KeyValuePair<int, int>(TheFruits.Key,TheFruits.Count())).ToDictionary(r=>r.Key,r=>r.Value);' 

Dies wird Ihnen ein Wörterbuch mit: Key- FruitId, Value- Graf

1

Ihre LINQ saperate SQL für jede Zählung erzeugen, so müssen u TheFruits verwenden Ihre Artikel

versuchen diese

var TheCounter = (from f in MyDC.Fruits  
        where TheFruitIDs.Contains(f.FruitID) 
        group new {f.FruitType} by f.FruitType into TheFruits 
        select new MyCounterMode() 
        { 
        CountType1 = TheFruits.Count(f => f.FruitType == 1), 
        CountType2 = TheFruits.Count(f => f.FruitType == 2), 
        .... all the way to CountType6  
        }).Single(); 
+0

Das sieht gut aus, weil das Gruppieren und Zählen in der DB statt im Speicher erfolgt. – frenchie

+0

Diese Abfrage generiert 6 Zeilen, und der Einzeloperator löst dann eine Ausnahme aus. – deramko

1

Sie die 0 tun können, zählenIn-Memory. Wenn Sie eine Gruppe mit mehreren Zählern kombinieren, werden viele Unteranfragen generiert, die ziemlich schlecht arbeiten können.

var tempResult = (from f in MyDC.Fruits where TheFruitIDs.Contains(f.FruitID)).ToList(); 

var TheCounter = (from f in tempResult 
        group f by f.FruitType into TheFruits 
        select new MyCounterMode() 
        { 
        CountType1 = (int?) TheFruits.Count(f => f.FruitType == 1), 
        CountType2 = (int?) TheFruits.Count(f => f.FruitType == 2), 
        .... all the way to CountType6  
        }).Single(); 
+0

Obwohl dies für LINQ-to-SQL im Allgemeinen gilt, macht die 'group by 0' nur 1 Gruppe und eine Abfrage. Nichtsdestotrotz denke ich auch, dass das Gruppieren (Pivotieren) im Speicher besser ist, so dass sich die Datenbank einfach auf das Erzeugen von Daten konzentrieren kann. –

+0

Nein, ich brauche die Gruppierung und das Zählerobjekt, die im DB und nicht im Speicher abgelegt werden sollen. – frenchie

2

ist, was Ihre Abfrage übersetzt:

SELECT 
[Limit1].[C1] AS [C1], 
[Limit1].[C2] AS [C2], 
[Limit1].[C3] AS [C3], 
[Limit1].[C4] AS [C4], 
[Limit1].[C5] AS [C5], 
[Limit1].[C6] AS [C6], 
[Limit1].[C7] AS [C7] 
FROM (SELECT TOP (2) 
    [Project13].[C1] AS [C1], 
    CASE WHEN ([Project13].[C2] IS NULL) THEN 0 ELSE [Project13].[C3] END AS [C2], 
    CASE WHEN ([Project13].[C4] IS NULL) THEN 0 ELSE [Project13].[C5] END AS [C3], 
    CASE WHEN ([Project13].[C6] IS NULL) THEN 0 ELSE [Project13].[C7] END AS [C4], 
    CASE WHEN ([Project13].[C8] IS NULL) THEN 0 ELSE [Project13].[C9] END AS [C5], 
    CASE WHEN ([Project13].[C10] IS NULL) THEN 0 ELSE [Project13].[C11] END AS [C6], 
    CASE WHEN ([Project13].[C12] IS NULL) THEN 0 ELSE [Project13].[C13] END AS [C7] 
    FROM (SELECT 
     [Project12].[C1] AS [C1], 
     [Project12].[C2] AS [C2], 
     [Project12].[C3] AS [C3], 
     [Project12].[C4] AS [C4], 
     [Project12].[C5] AS [C5], 
     [Project12].[C6] AS [C6], 
     [Project12].[C7] AS [C7], 
     [Project12].[C8] AS [C8], 
     [Project12].[C9] AS [C9], 
     [Project12].[C10] AS [C10], 
     [Project12].[C11] AS [C11], 
     [Project12].[C12] AS [C12], 
     (SELECT 
      COUNT(1) AS [A1] 
      FROM [dbo].[Fruits] AS [Extent13] 
      WHERE ([Extent13].[FruitID] IN (23, 215, 256, 643)) AND ([Project12].[C1] = 0) 
      AND (6 = [Extent13].[FruitType])) AS [C13] 
     FROM (SELECT 
      [Project11].[C1] AS [C1], 
      [Project11].[C2] AS [C2], 
      [Project11].[C3] AS [C3], 
      [Project11].[C4] AS [C4], 
      [Project11].[C5] AS [C5], 
      [Project11].[C6] AS [C6], 
      [Project11].[C7] AS [C7], 
      [Project11].[C8] AS [C8], 
      [Project11].[C9] AS [C9], 
      [Project11].[C10] AS [C10], 
      [Project11].[C11] AS [C11], 
      (SELECT 
       COUNT(1) AS [A1] 
       FROM [dbo].[Fruits] AS [Extent12] 
       WHERE ([Extent12].[FruitID] IN (23, 215, 256, 643)) 
       AND ([Project11].[C1] = 0) 
       AND (6 = [Extent12].[FruitType])) AS [C12] 
      FROM (SELECT 
       [Project10].[C1] AS [C1], 
       [Project10].[C2] AS [C2], 
       [Project10].[C3] AS [C3], 
       [Project10].[C4] AS [C4], 
       [Project10].[C5] AS [C5], 
       [Project10].[C6] AS [C6], 
       [Project10].[C7] AS [C7], 
       [Project10].[C8] AS [C8], 
       [Project10].[C9] AS [C9], 
       [Project10].[C10] AS [C10], 
       (SELECT 
        COUNT(1) AS [A1] 
        FROM [dbo].[Fruits] AS [Extent11] 
        WHERE ([Extent11].[FruitID] IN (23, 215, 256, 643)) 
        AND([Project10].[C1] = 0) 
        AND (5 = [Extent11].[FruitType])) AS [C11] 
       FROM (SELECT 
        [Project9].[C1] AS [C1], 
        [Project9].[C2] AS [C2], 
        [Project9].[C3] AS [C3], 
        [Project9].[C4] AS [C4], 
        [Project9].[C5] AS [C5], 
        [Project9].[C6] AS [C6], 
        [Project9].[C7] AS [C7], 
        [Project9].[C8] AS [C8], 
        [Project9].[C9] AS [C9], 
        (SELECT 
         COUNT(1) AS [A1] 
         FROM [dbo].[Fruits] AS [Extent10] 
         WHERE ([Extent10].[FruitID] IN (23, 215, 256, 643)) 
         AND ([Project9].[C1] = 0) 
         AND (5 = [Extent10].[FruitType])) AS [C10] 
        FROM (SELECT 
         [Project8].[C1] AS [C1], 
         [Project8].[C2] AS [C2], 
         [Project8].[C3] AS [C3], 
         [Project8].[C4] AS [C4], 
         [Project8].[C5] AS [C5], 
         [Project8].[C6] AS [C6], 
         [Project8].[C7] AS [C7], 
         [Project8].[C8] AS [C8], 
         (SELECT 
          COUNT(1) AS [A1] 
          FROM [dbo].[Fruits] AS [Extent9] 
          WHERE ([Extent9].[FruitID] IN (23, 215, 256, 643)) 
          AND ([Project8].[C1] = 0) 
          AND (4 = [Extent9].[FruitType])) AS [C9] 
         FROM (SELECT 
          [Project7].[C1] AS [C1], 
          [Project7].[C2] AS [C2], 
          [Project7].[C3] AS [C3], 
          [Project7].[C4] AS [C4], 
          [Project7].[C5] AS [C5], 
          [Project7].[C6] AS [C6], 
          [Project7].[C7] AS [C7], 
          (SELECT 
           COUNT(1) AS [A1] 
           FROM [dbo].[Fruits] AS [Extent8] 
           WHERE ([Extent8].[FruitID] IN (23, 215, 256, 643)) 
           AND ([Project7].[C1] = 0) 
           AND (4 = [Extent8].[FruitType])) AS [C8] 
          FROM (SELECT 
           [Project6].[C1] AS [C1], 
           [Project6].[C2] AS [C2], 
           [Project6].[C3] AS [C3], 
           [Project6].[C4] AS [C4], 
           [Project6].[C5] AS [C5], 
           [Project6].[C6] AS [C6], 
           (SELECT 
            COUNT(1) AS [A1] 
            FROM [dbo].[Fruits] AS [Extent7] 
            WHERE ([Extent7].[FruitID] IN (23, 215, 256, 643)) 
            AND ([Project6].[C1] = 0) 
            AND (3 = [Extent7].[FruitType])) AS [C7] 
           FROM (SELECT 
            [Project5].[C1] AS [C1], 
            [Project5].[C2] AS [C2], 
            [Project5].[C3] AS [C3], 
            [Project5].[C4] AS [C4], 
            [Project5].[C5] AS [C5], 
            (SELECT 
             COUNT(1) AS [A1] 
             FROM [dbo].[Fruits] AS [Extent6] 
             WHERE ([Extent6].[FruitID] IN (23, 215, 256, 643)) 
             AND ([Project5].[C1] = 0) 
             AND (3 = [Extent6].[FruitType])) AS [C6] 
            FROM (SELECT 
             [Project4].[C1] AS [C1], 
             [Project4].[C2] AS [C2], 
             [Project4].[C3] AS [C3], 
             [Project4].[C4] AS [C4], 
             (SELECT 
              COUNT(1) AS [A1] 
              FROM [dbo].[Fruits] AS [Extent5] 
              WHERE ([Extent5].[FruitID] IN (23, 215, 256, 643)) 
              AND ([Project4].[C1] = 0) 
              AND (2 = [Extent5].[FruitType])) AS [C5] 
             FROM (SELECT 
              [Project3].[C1] AS [C1], 
              [Project3].[C2] AS [C2], 
              [Project3].[C3] AS [C3], 
              (SELECT 
               COUNT(1) AS [A1] 
               FROM [dbo].[Fruits] AS [Extent4] 
               WHERE ([Extent4].[FruitID] IN (23, 215, 256, 643)) 
               AND ([Project3].[C1] = 0) 
               AND (2 = [Extent4].[FruitType])) AS [C4] 
              FROM (SELECT 
               [Project2].[C1] AS [C1], 
               [Project2].[C2] AS [C2], 
               (SELECT 
                COUNT(1) AS [A1] 
                FROM [dbo].[Fruits] AS [Extent3] 
                WHERE ([Extent3].[FruitID] IN (23, 215, 256, 643)) 
                AND ([Project2].[C1] = 0) 
                AND (1 = [Extent3].[FruitType])) AS [C3] 
               FROM (SELECT 
                [Distinct1].[C1] AS [C1], 
                (SELECT 
                 COUNT(1) AS [A1] 
                 FROM [dbo].[Fruits]AS [Extent2] 
                 WHERE ([Extent2].[FruitID] IN (23, 215, 256, 643)) 
                 AND ([Distinct1].[C1] = 0) 
                 AND (1 = [Extent2].[FruitType])) AS [C2] 
                FROM (SELECT DISTINCT 
                 0 AS [C1] 
                 FROM [dbo].[Fruits]AS [Extent1] 
                 WHERE [Extent1].[FruitID] IN (23, 215, 256, 643) 
                ) AS [Distinct1] 
               ) AS [Project2] 
              ) AS [Project3] 
             ) AS [Project4] 
            ) AS [Project5] 
           ) AS [Project6] 
          ) AS [Project7] 
         ) AS [Project8] 
        ) AS [Project9] 
       ) AS [Project10] 
      ) AS [Project11] 
     ) AS [Project12] 
    ) AS [Project13] 
) AS [Limit1] 

Beachten Sie, dass für jede der IN-Gruppierung erneut ausgewertet wird, eine sehr große Arbeitsbelastung für große Listen von IDs zu erzeugen.

Sie müssen den Job in zwei Schritten aufteilen.

List<int> theFruitIDs = new List<int> { 23, 215, 256, 643 }; 

var theCounter = (from f in MyDC.Fruits 
        where theFruitIDs.Contains(f.FruitID) 
        group f by f.FruitType into theFruits 
        select new { fruitType = theFruits.Key, fruitCount = theFruits.Count() }) 
        .ToList(); 

Dies übersetzt zu einem viel schnelleren SQL. Beachten Sie die ToList() am Ende, die die Ausführung einer einzelnen Abfrage erzwingen.

Jetzt können Sie die generierte Liste aufnehmen und im Speicher schwenken, um Ihren MyCounterMode zu erhalten.

var thePivot = new MyCounterMode 
       { 
        CountType1 = theCounter.Where(x => x.fruitType == 1).Select(x => x.fruitCount).SingleOrDefault(), 
        CountType2 = theCounter.Where(x => x.fruitType == 2).Select(x => x.fruitCount).SingleOrDefault(), 
        CountType3 = theCounter.Where(x => x.fruitType == 3).Select(x => x.fruitCount).SingleOrDefault(), 
       }; 
+0

Nein, ich brauche die Gruppierung und das Zählerobjekt, die in der DB und nicht im Speicher abgelegt werden sollen. – frenchie

+0

Was ich vorgeschlagen und erklärt habe, ist alles auf der db.Die Liste "theCounter" ist nur eine Liste von sechs Elementen und die zweite Gruppierung "thePivot" dient lediglich dazu, diese sechs Zeilen in sechs Spalten zu transponieren. – deramko

+0

Das Problem ist, dass ich in einigen Abfragen das gleiche Muster verwende, um den Zähler als Unterabfrage einer viel größeren Abfrage zu füllen, so dass ich die Zählerabfrage als eine eigenständige Abfrage schreiben muss. – frenchie

0

Hier ist, wie ich das immer implementieren (ich ein einfaches Konsolenprogramm bauen demonstrieren):

Fruit.cs

public class Fruit 
{ 
    public Fruit(int fruitId, int fruitType) 
    { 
     FruitId = fruitId; 
     FruitType = fruitType; 
    } 

    public int FruitId { get; set; } 
    public int FruitType { get; set; } 
} 

Program.cs

class Program 
{ 
    static void Main(string[] args) 
    { 
     // Data 
     var fruits = new List<Fruit> 
     { 
      new Fruit(23, 2), 
      new Fruit(215, 2), 
      new Fruit(256, 1), 
      new Fruit(643, 3) 
     }; 

     // Query 
     var query = fruits 
      .GroupBy(x => x.FruitType) 
      .Select(x => new {Name = x.Key, Total = x.Count()}); 

     // Output 
     foreach (var item in query) 
     { 
      Console.WriteLine(item.Name + ": " + item.Total); 
     } 
     Console.ReadLine(); 
    } 
} 

Dasjenige, auf das Sie sich konzentrieren müssen, ist query. Nach der Verwendung von GroupBy haben Sie eine Liste von Gruppen. Für jede Gruppe ist Key das Gruppierungskriterium (hier ist FruitType). Dann rufen wir Count() auf, um die Nummer des Elements in dieser Gruppe zu erhalten.

0

Hier ist eine dynamische Art und Weise, es zu tun, wo man nicht von CountType # 's beschränkt:

int typesOfCounts = 6; 

IEnumerable<Fruit> theCounter = fruitList.Where(x => theFruitIDs.Contains(x.FruitID)); 

Dictionary<string, int> myCounterMode = new Dictionary<string, int>(); 

for (var i = 1; i < typesOfCounts + 1; i++) 
{ 
    string counterType = "CountTypeX"; 
    counterType = counterType.Replace("X", i.ToString()); 

    myCounterMode.Add(counterType, theCounter.Count(x => x.FruitType == i)); 
} 

return myCounterMode;