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

时间:2015-08-11 16:31:00

标签: c# .net linq linq-to-sql

我有一个看起来像这样的表:

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

我希望FruitType获得一个名为FruitIDs TheFruitIDs的列表。这就是我所拥有的:

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

此代码有效,但问题是有时我会收到超时错误,因为查询运行时间过长。如何更改此代码以避免超时问题?

6 个答案:

答案 0 :(得分:6)

查询的最简单方法是按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);

这将有效地创建以下字典(假设where部分没有排除任何数据):

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

如果您真的想将其折叠成具有特定水果类型计数的单个对象,则必须创建此对象:

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

您的查询中还有另一件事可能导致性能问题,可能导致超时:where部分中的水果ID列表包含在查询中,如果该列表非常大它可能会减慢您的查询速度。除非您从先前的查询到数据库创建此列表,否则您无能为力。在这种情况下,您应该尽量避免将水果ID列表拉到客户端。相反,您应该将选择ID的查询与此查询类型的查询结合起来。这将确保整个查询在服务器端执行。

您似乎关心代码的结构变化。只要您创建匿名对象,就很难编写可重用的代码。您可以考虑使用带有计数或类似内容的字典。另一种选择是使用计数创建动态对象。就个人而言,我不喜欢这种解决方案,但你可能会发现它很有用。

为了简化代码,需要一个存储计数的类:

class TypeCount {

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

  public Int32 Type { get; private set; }

  public Int32 Count { get; private set; }

}

基于元组序列具有属性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;
  }

}

创建动态对象的扩展方法:

static class CountExtensions {

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

}

全部放在一起:

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

然后,您可以检索属性counts.CountType1counts.CountType2counts.CountType3。其他count.CountType#属性将返回0.但是,由于counts是动态的,因此您无法获得任何智能感知。

答案 1 :(得分:1)

你的LINQ为每个计数生成saperate sql,所以你需要使用TheFruits计算你的项目

试试这个

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

答案 2 :(得分:1)

您可以在内存中执行group by。将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();

答案 3 :(得分:0)

您需要记住,每次迭代都会执行select !!

所以尝试类似:

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

这将为您提供一个字典:Key-FruitId,Value-Count

答案 4 :(得分:0)

以下是我总是如何实现这一点(我构建了一个简单的控制台程序来演示):

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

您需要关注的是query。使用GroupBy后,您将获得一个组列表。对于每个组,Key是要分组的条件(此处为FruitType)。然后,我们调用Count()来获取该组中元素的数量。

答案 5 :(得分:0)

这是一种动态的方式,你不受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;