简体   繁体   English

Linq to SQL计算生成超时的分组元素

[英]Linq to SQL count grouped elements generating a timeout

I have a table that looks like this: 我有一个看起来像这样的表:

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

I want to get the count by FruitType given a list of FruitIDs called TheFruitIDs . 我想通过获得计数FruitType给出的列表FruitIDs称为TheFruitIDs This is what I have: 这就是我所拥有的:

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();

This code works but the problem is that sometimes I get a timeout error because the query runs for too long. 此代码有效,但问题是有时我会收到超时错误,因为查询运行时间过长。 How can I change this code to avoid the timeout problem? 如何更改此代码以避免超时问题?

The simplest way to do you query is to group by FruitType and then count the rows: 查询的最简单方法是按FruitType ,然后计算行数:

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);

This will efficiently create the following dictionary (assuming no data was excluded by the where part): 这将有效地创建以下字典(假设where部分没有排除数据):

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

If you really want to collapse this into a single object having counts for specific fruit types you then have to create this object: 如果您真的想将其折叠为具有特定水果类型计数的单个对象,则必须创建此对象:

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

There is another thing in your query that might be causing performance problems potentially resulting in timeouts: The list of fruit ID's in the where part is included in the query and if that list is very big it may slow down your query. 您的查询中还有另一件事可能导致性能问题,可能导致超时:查询中包含where部分中的水果ID列表,如果该列表非常大,则可能会降低查询速度。 There is nothing you can do about it unless you create this list from a previous query to the database. 除非您从先前的查询到数据库创建此列表,否则您无能为力。 In that case you should try to avoid pulling the list of fruit ID's to the client side. 在这种情况下,您应该尽量避免将水果ID列表拉到客户端。 Instead you should combine the query that selects the ID's with this query that counts the types. 相反,您应该将选择ID的查询与计算类型的查询相结合。 This will ensure that the entire query is executed server side. 这将确保整个查询在服务器端执行。

You seem to be concerned about the structural change of the code. 您似乎关心代码的结构变化。 As long as you are creating anonymous objects it is hard to write reusable code. 只要您创建匿名对象,就很难编写可重用的代码。 You could consider to just use the dictionary with the counts or something similar. 您可以考虑使用带有计数或类似内容的字典。 Another option is to create a dynamic object with the counts. 另一种选择是使用计数创建动态对象。 Personally, I do not like this solution but you may find it useful. 就个人而言,我不喜欢这个解决方案,但你可能会发现它很有用。

To simplify the code a class to store counts is needed: 要简化代码,需要一个存储计数的类:

class TypeCount {

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

  public Int32 Type { get; private set; }

  public Int32 Count { get; private set; }

}

A dynamic object that has properties CountType0 , CountType1 , CountType2 etc. based on a sequence of tuples: 一个动态对象,具有基于元组序列的属性CountType0CountType1CountType2等:

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;
  }

}

An extension method to create the dynamic object: 一种创建动态对象的扩展方法:

static class CountExtensions {

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

}

Putting it all together: 把它们放在一起:

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

You can then retrieve properties counts.CountType1 , counts.CountType2 and counts.CountType3 . 然后,您可以检索属性counts.CountType1counts.CountType2counts.CountType3 Other count.CountType# properties will return 0. However, as counts is dynamic you will not get any intellisense. 其他count.CountType#属性将返回0.但是,由于counts是动态的,因此您将无法获得任何智能感知。

This is what your query translates to: 这是您的查询转换为:

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]

Note that for each grouping the IN is evaluated again, generating a very large workload for large lists of IDs. 请注意,对于每个分组,将再次评估IN,从而为大型ID列表生成非常大的工作负载。

You have to split the job in two steps. 您必须分两步拆分作业。

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();

This translate to a much faster SQL. 这转化为更快的SQL。 Note the ToList() at the end that force the execution of a single query. 请注意最后强制执行单个查询的ToList()。

SELECT
[GroupBy1].[K1] AS [FruitType],
[GroupBy1].[A1] AS [C1]
FROM ( SELECT
    [Extent1].[FruitType] AS [K1],
    COUNT(1) AS [A1]
    FROM [dbo].[Fruits] AS [Extent1]
    WHERE [Extent1].[FruitID] IN (23, 215, 256, 643)
    GROUP BY [Extent1].[FruitType]
)  AS [GroupBy1]

Now you can take the generated list and pivot it in memory to obtain your MyCounterMode. 现在,您可以获取生成的列表并将其转移到内存中以获取MyCounterMode。

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(),
                };

your LINQ generate saperate sql for each count, so u need to use TheFruits to count your items 你的LINQ为每个计数生成saperate sql,所以你需要使用TheFruits计算你的项目

try this 尝试这个

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();

You can do the group by in-memory. 您可以group by内存来执行该group by Combining a group by with multuple counts will generate lots of subqueries, which can perform quite badly. 将group by与multuple计数组合将生成大量子查询,这些子查询的执行情况非常糟糕。

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();

You need to remember that select is being executed for each iteration!! 你需要记住每次迭代都在执行select !!

So try something like: 所以尝试类似的东西:

'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);'

This will give you a dictionary with: Key- FruitId, Value- Count 这将为您提供一个字典:Key-FruitId,Value-Count

Here is how I always implement this (I build a simple console program to demonstrate): 以下是我总是如何实现这一点(我构建了一个简单的控制台程序来演示):

Fruit.cs 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 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();
    }
}

The one you need to focus in is query . 您需要关注的是query After using GroupBy , you will have a list of groups. 使用GroupBy ,您将拥有一个组列表。 For each group, the Key is the criteria to group (here is the FruitType ). 对于每个组, Key是要分组的条件(这里是FruitType )。 Then, we call Count() to get the number of element in that group. 然后,我们调用Count()来获取该组中元素的数量。

Here's a dynamic way to do it where you're not limited by CountType #'s: 这是一个动态的方法,你不受CountType#的限制:

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;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM