2010-10-14 10 views
10

Aşağıdaki LINQ sorgusunu, kullanıcı girdisine bağlı olarak birkaç gruplama sütununu kabul eden Dinamik LINQ'a çevirmem gerekiyor. Temel olarak gruplamaları uygulayan bir grup dropdownlistim var ve gruplandırmanın her bir birleşimini sıralamak istemiyorum. Dinamik LINQ başarısız olursa, el ile bir SQL sorgusu oluşturmanız gerekebilir ve kimse bunu istemez.Dinamik LINQ GroupBy Çoklu Sütunlar

var grouping = (from entry in ObjectContext.OmniturePageModules 
    where entry.StartOfWeek >= startDate && entry.StartOfWeek <= endDate && 
     (section == "Total" || section == "All" || entry.Section == section) && 
     (page == "Total" || page == "All" || entry.Page == page) && 
     (module == "Total" || module == "All" || entry.Module == module) 
    group entry by new 
    { 
     entry.Page, // I want to be able to tell this anonymous type 
     entry.Module, // which columns to group by 
     entry.StartOfWeek // at runtime 
    } 
    into entryGroup 
    select new 
    { 
     SeriesName = section + ":" + entryGroup.Key.Page + ":" + entryGroup.Key.Module, 
     Week = entryGroup.Key.StartOfWeek, 
     Clicks = entryGroup.Sum(p => p.Clicks) 
    }); 

Dynamic LINQ tamamen dışında belgelenmemiş olarak bunun nasıl hiçbir ipucu var "Merhaba dünya!" vakaları/siparişi/siparişi seçin. Ben sadece sözdizimini anlayamıyorum. :(gibi

şey?)

var grouping = ObjectContext.OmniturePageModules.Where(entry => entry.StartOfWeek >= startDate && entry.StartOfWeek <= endDate && 
              (section == "Total" || section == "All" || entry.Section == section) && 
              (page == "Total" || page == "All" || entry.Page == page) && 
              (module == "Total" || module == "All" || entry.Module == module)) 
              .GroupBy("new (StartOfWeek,Page,Module)", "it") 
              .Select("new (Sum(Clicks) as Clicks, SeriesName = section + key.Page + Key.Module, Week = it.Key.StartOfWeek)"); 

Ben System.Linq.Dynamic içinde DynamicQueryable sınıfını kullanıyorum. Bkz: http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx

Takibi: Enigmativity çözümü çoğunluklaçalıştı. - Nedense o datetime göre gruplandırmak istemiyor "startofweek" sütunu geçici çözüm sadece ikincil bir gruplandırma yapmaktır:

var entries = (from entry in ObjectContext.OmniturePageModules 
          where entry.StartOfWeek >= startDate 
           && entry.StartOfWeek <= endDate 
           && (section == "Total" || section == "All" || entry.Section == section) 
           && (page == "Total" || page == "All" || entry.Page == page) 
           && (module == "Total" || module == "All" || entry.Module == module) 
          select entry).ToArray(); // Force query execution 

      var grouping = from entry in entries 
          let grouper = new EntryGrouper(entry, section, page, module) 
          group entry by grouper into entryGroup 
          select new 
          { 
           entryGroup.Key.SeriesName, 
           entryGroup.Key.Date, 
           Clicks = entryGroup.Sum(p => p.Clicks), 
          }; 

      var grouping2 = (from groups in grouping 
          group groups by new {groups.SeriesName, groups.Date } into entryGroup 
          select new 
          { 
           entryGroup.Key.SeriesName, 
           entryGroup.Key.Date, 
           Clicks = entryGroup.Sum(p => p.Clicks), 
          }); 

ama bu ciddi aşağılamak gibi görünüyor performans ... =/

cevap

3

LINQ Dinamik Sorgu Kitaplığını açıkça kullanmak istiyorsanız, cevabım sizin istediğiniz gibi olmayacaktır, ancak istediğiniz davranışı istiyorsanız ve düzenli LINQ kullanmaktan memnunsanız, yardımcı olabileceğimi düşünüyorum.

Esasen ben açılır listeler seçilen değerlere göre gruplama mantığını işleyen bir EntryGrouper sınıf oluşturduk ve ben değişkenler section, page & module bu değerleri tutmak farz ettik. Ayrıca, ObjectContext.OmniturePageModules'un Entry türünde bir numara olduğunu varsayalım.

Yani LINQ sorgusu şimdi bu iki dönüşür: İlk sorgu veritabanı üzerinde basit bir seçme sorgusu zorlamak ve gruplandırmak istediğiniz yalnızca kayıtları döndürmek için kullanılır

var entries = (from entry in ObjectContext.OmniturePageModules 
       where entry.StartOfWeek >= startDate 
        && entry.StartOfWeek <= endDate 
        && (section == "Total" || section == "All" || entry.Section == section) 
        && (page == "Total" || page == "All" || entry.Page == page) 
        && (module == "Total" || module == "All" || entry.Module == module) 
       select entry).ToArray(); // Force query execution 

var grouping = from entry in entries 
       let grouper = new EntryGrouper(entry, section, page, module) 
       group entry by grouper into entryGroup 
       select new 
       { 
        SeriesName = entryGroup.Key.SeriesName, 
        Week = entryGroup.Key.StartOfWeek, 
        Clicks = entryGroup.Sum(p => p.Clicks), 
       }; 

. Genellikle group by sorguları veritabanını birden çok kez çağırır, bu şekilde sorgulama genellikle çok daha hızlıdır.

İkinci sorgu, gruplama anahtarı olarak EntryGrouper sınıfının örneklerini oluşturarak ilk sorgudaki sonuçları gruplandırır.

EntryGrouper sınıfında bir SeriesName özelliği ekledim, böylece tüm grup mantığı düzgün bir şekilde tek bir yerde tanımlanmış olur.

Şimdi EntryGrouper sınıf, işe gruplama izin verecek oldukça büyük, bu StartOfWeek, Section için özelliklere sahip, Page & Module ve Equals & GetHashCode yöntemlerin aşırı yüklenmeleri içerir ve IEquatable<Entry> arabirimini uygulaması gerekmektedir.İşte

öyle:

if (_page == "Total" || _page == "All") 
    return _page; 
return _entry.Page; 

O zaman kapalı, açılır değerleri üzerinde gruplama dönüş nasıl yanlış ve varsa yapmalısınız:

public class EntryGrouper : IEquatable<Entry> 
{ 
    private Entry _entry; 
    private string _section; 
    private string _page; 
    private string _module; 

    public EntryGrouper(Entry entry, string section, string page, string module) 
    { 
     _entry = entry; 
     _section = section; 
     _page = page; 
     _module = module; 
    } 

    public string SeriesName 
    { 
     get 
     { 
      return String.Format("{0}:{1}:{2}", this.Section, this.Page, this.Module); 
     } 
    } 

    public DateTime StartOfWeek 
    { 
     get 
     { 
      return _entry.StartOfWeek; 
     } 
    } 

    public string Section 
    { 
     get 
     { 
      if (_section == "Total" || _section == "All") 
       return _section; 
      return _entry.Section; 
     } 
    } 

    public string Page 
    { 
     get 
     { 
      if (_page == "Total" || _page == "All") 
       return _page; 
      return _entry.Page; 
     } 
    } 

    public string Module 
    { 
     get 
     { 
      if (_module == "Total" || _module == "All") 
       return _module; 
      return _entry.Module; 
     } 
    } 

    public override bool Equals(object other) 
    { 
     if (other is Entry) 
      return this.Equals((Entry)other); 
     return false; 
    } 

    public bool Equals(Entry other) 
    { 
     if (other == null) 
      return false; 
     if (!EqualityComparer<DateTime>.Default.Equals(this.StartOfWeek, other.StartOfWeek)) 
      return false; 
     if (!EqualityComparer<string>.Default.Equals(this.Section, other.Section)) 
      return false; 
     if (!EqualityComparer<string>.Default.Equals(this.Page, other.Page)) 
      return false; 
     if (!EqualityComparer<string>.Default.Equals(this.Module, other.Module)) 
      return false; 
     return true; 
    } 

    public override int GetHashCode() 
    { 
     var hash = 0; 
     hash ^= EqualityComparer<DateTime>.Default.GetHashCode(this.StartOfWeek); 
     hash ^= EqualityComparer<string>.Default.GetHashCode(this.Section); 
     hash ^= EqualityComparer<string>.Default.GetHashCode(this.Page); 
     hash ^= EqualityComparer<string>.Default.GetHashCode(this.Module); 
     return hash; 
    } 

    public override string ToString() 
    { 
     var template = "{{ StartOfWeek = {0}, Section = {1}, Page = {2}, Module = {3} }}"; 
     return String.Format(template, this.StartOfWeek, this.Section, this.Page, this.Module); 
    } 
} 

bu sınıfın gruplama mantığı basitçe şöyle sadece bu yöntemleri değiştirmemiz gerekir, ancak bu kodun özü, gruplama açık olduğunda girişteki değere göre bir grup değeri döndürmesi ve aksi takdirde tüm girdiler için ortak bir değer döndürmesidir. Değer tüm girdiler için ortak ise, o zaman mantıksal olarak, yalnızca gruplama ile aynı olmayan tek bir grup oluşturur.

Gruplama yaptığınız daha fazla açılırsanız, EntryGrouper sınıfına daha fazla özellik eklemeniz gerekir. Bu yeni özellikleri Equals & GetHashCode yöntemlerine de eklemeyi unutmayın.

Bu mantık, bu nedenle, istediğiniz dinamik gruplamayı temsil eder. Yardım ettiğimde veya daha fazla ayrıntıya ihtiyacınız varsa lütfen bana bildirin.

Burada

var double_grouping = (ObjectContext.OmniturePageModules.Where(entry => entry.StartOfWeek >= startDate 
        && entry.StartOfWeek <= endDate 
        && (section == "Total" || section == "All" || entry.Section == section) 
        && (page == "Total" || page == "All" || entry.Page == page) 
        && (module == "Total" || module == "All" || entry.Module == module)) 
        .GroupBy("new (it.Section, it.Page, it.StartOfWeek)", "it")) 
        .Select("new (Sum(Clicks) as Clicks, Key.Section as SeriesSection, Key.Page as SeriesPage, Key.StartOfWeek as Week)"); 

Ve iş arkadaşı o çekti kadar beni kaçan, normal LINQ yoludur - bu: tabii ki GroupBy ve zamanında Seç dizeleri inşa - İşte Dinamik LINQ olduğunu

+0

Kapsamlı cevabınız için çok teşekkür ederim. Bunu yarın deneyeceğim ve benim için işe yarayıp yaramadığını size söyleyeyim - bu konuda hızlı bir bakış cesaret vericidir. –

+0

Bu, StartOfWeek tarafından herhangi bir nedenden dolayı grup görünmüyor.Her sütun için gruplama kodunu değiştirmek zorunda kaldım (_section == "Tümü") return _entry.Section; döndürme _section; –

+0

@ Daniel Coffman - Neden StartOfWeek tarafından gruplandırılmadığını bilmiyorum, olması gerekiyordu. Kodu yeniden kontrol ettim ve 'Equals' & GetHashCode 'yöntemleri' StartOfWeek' değerini kullanır. Bana daha fazla bakmamı istersen bana bağırma. Her sütun için gruplandırma kodunun muhtemelen ihtiyaçlarınız için "tweaked" olması gerekeceğini ummuştum. Çözümünüzü takip etmek için – Enigmativity

8

böylece

var grouping = (from entry in ObjectContext.OmniturePageModules 
    where entry.StartOfWeek >= startDate && entry.StartOfWeek <= endDate && 
     (section == "Total" || section == "All" || entry.Section == section) && 
     (page == "Total" || page == "All" || entry.Page == page) && 
     (module == "Total" || module == "All" || entry.Module == module) 
    group entry by new 
    { 
     Section = section == "All" ? entry.Section : section, 
     Page = page == "All" ? entry.Page : page, 
     Module = module == "All" ? entry.Module : module, 
     entry.StartOfWeek 
    } 
     into entryGroup 
     select new 
     { 
      SeriesName = 
      entryGroup.Key.Section + ":" + entryGroup.Key.Page + ":" + entryGroup.Key.Module, 
      Week = entryGroup.Key.StartOfWeek, 
      Clicks = entryGroup.Sum(p => p.Clicks) 
     }); 
+0

+1, teşekkürler! –

0

bu soru yayınlanmıştır beri bir süre oldu biliyorum ama son zamanlarda benzer bir sorunu olan (çalışma zamanı kullanıcı tarafından seçilen birden çok sütuna göre dinamik gruplandırma) uğraşmak zorunda kaldı: orfoz sınıfının olmadan Enigmativity çözümü temelde İşte benim almam. bellek gruplama yapmak için gruplama lambdas

static Expression<Func<T, Object>> GetGroupBy<T>(string property) 
{ 
    var data = Expression.Parameter(typeof(T), "data"); 
    var dataProperty = Expression.PropertyOrField(data, property); 
    var conversion = Expression.Convert(dataProperty, typeof(object)); 
    return Expression.Lambda<Func<T, Object>>(conversion, data); 
} 
  • işlev oluşturmak için

    1. yardımcı işlevi. Grupları döndürür.

      o kullanılacak Nasıl
      static IEnumerable<IEnumerable<T>> Group<T>(IEnumerable<T> ds, params Func<T, object>[] groupSelectors) 
      { 
          Func<IEnumerable<T>, Func<T, object>[], IEnumerable<IEnumerable<T>>> inner = null; 
          inner = (d, ss) => { 
          if (null == ss || ss.Length == 0) { 
           return new[] { d }; 
          } else { 
           var s = ss.First(); 
           return d.GroupBy(s).Select(g => inner(g.Select(x => x), ss.Skip(1).ToArray())) .SelectMany(x => x); 
          } 
          }; 
          return inner(ds, groupSelectors); 
      } 
      
    2. : küçültücü performansları ile ilgili olarak

      String[] columnsSelectedByUser = ... // contains names of grouping columns selected by user 
      var entries = ... // Force query execution i.e. fetch all data 
      var groupBys = columnsSelectedByUser.Select(x => GetGroupBy(x).Compile()).ToArray(); 
      var grouping = Group(entries, groupBys); // enumerable containing groups of entries 
      

    , bunu aslında (büyük) sorun olduğunu sanmıyorum. Dinamik bir gruplandırma SQL'i oluşturmuş olsanız bile, sorgu, gruplama olmadan bir sorgu olarak aynı sayıda satır döndürmelidir. Bu nedenle, bu yaklaşımda gruplandırma veritabanı tarafından yapılmasa da, zorunlu sorgu yürütme tarafından döndürülen satır sayısı, gruplama ölçütleriyle varsayılan SQL sorgusuyla aynıdır. Tabii, veritabanı büyük olasılıkla C# kodu ile yapılan bellek içi gruplandırmadan daha iyi performans gösterirdi ancak trafik miktarı yalnızca kaç satırın (entries) gruplandırılması gerektiğine bağlıdır.