10

次の LINQ クエリを、ユーザー入力に基づいて複数のグループ化列を受け入れる動的 LINQ に変換する必要があります。基本的に、グループ化を適用するドロップダウンリストがたくさんあり、グループ化のすべての組み合わせを列挙したくありません。動的 LINQ が失敗した場合、手動で SQL クエリを作成する必要があるかもしれませんが、誰もそれを望んでいません。

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 は "hello world!" 以外ではまったく文書化されていないため、これを行う方法がわかりません。select/where/orderby ケース。私はちょうど構文を理解することはできません。

何かのようなもの:(?)

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

System.Linq.Dynamic で DynamicQueryable クラスを使用しています。参照: http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx

フォローアップ: エニグマティビティの解決策は、ほとんどの場合うまくいきました。何らかの理由で、日時の「StartOfWeek」列でグループ化したくない - 回避策は、二次グループ化を行うことです:

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

しかし、これはパフォーマンスを著しく低下させるようです... =/

4

3 に答える 3

8

これは Dynamic LINQ にあります。もちろん、実行時に GroupBy および Select 文字列を作成します。

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

そして、同僚が指摘するまで私を逃れた通常のLINQの方法は次のとおりです。これは基本的に、グルーパークラスを使用しないEnigmativityのソリューションです。

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 )
        } );
于 2010-10-15T18:29:26.783 に答える
3

明示的に LINQ 動的クエリ ライブラリを使用したい場合、私の答えはあなたが望むものにはなりませんが、希望する動作が必要で、通常の LINQ を使用しても構わない場合は、私がお手伝いできると思います。

基本的に、ドロップダウン リストで選択した値でグループ化するロジックを処理するクラスを作成し、変数, &がそれらの値を保持するEntryGrouperと仮定しました。また、それが type の列挙可能であると仮定しました。sectionpagemoduleObjectContext.OmniturePageModulesEntry

したがって、LINQ クエリは次の 2 つになります。

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

最初のクエリは、データベースに対して単純な選択クエリを強制し、グループ化するレコードのみを返すために使用されます。通常group by、クエリはデータベースを複数回呼び出すため、この方法でクエリを実行すると通常ははるかに高速になります。

EntryGrouper2 番目のクエリは、クラスのインスタンスをグループ化キーとして作成することにより、最初のクエリの結果をグループ化します。

すべてのグループ化ロジックが 1 か所できちんと定義されるようSeriesNameに、クラスにプロパティを含めました。EntryGrouper

グループ化を機能させるには、 、、&のプロパティを持ち、 &メソッドのオーバーロードを含み、インターフェイスを実装する必要があるため、EntryGrouperクラスは非常に大きくなります。StartOfWeekSectionPageModuleEqualsGetHashCodeIEquatable<Entry>

ここにあります:

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

このクラスのグループ化ロジックは、次のようになります。

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

ドロップダウン値がグループ化のオンとオフを切り替える方法を誤解している場合は、これらのメソッドを変更するだけで済みますが、このコードの核心は、グループ化がオンの場合、エントリの値に基づいてグループ値を返す必要があることです。それ以外の場合は、すべてのエントリに共通の値を返す必要があります。値がすべてのエントリに共通の場合、論理的には 1 つのグループのみが作成されます。これは、まったくグループ化しないのと同じです。

グループ化するドロップダウンがさらにある場合は、EntryGrouperクラスにさらにプロパティを追加する必要があります。Equalsこれらの新しいプロパティを&GetHashCodeメソッドにも追加することを忘れないでください。

したがって、このロジックは、必要な動的グループ化を表しています。私が役に立ったかどうか、または詳細が必要な場合はお知らせください。

楽しみ!

于 2010-10-14T01:44:27.107 に答える
0

この質問が投稿されてからしばらく経ちましたが、最近同様の問題(実行時にユーザーが選択した複数の列による動的グループ化)に対処しなければならなかったので、ここに私の見解を示します。

  1. グループ化ラムダを作成するためのヘルパー関数

    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 );
    }
    
  2. インメモリグループ化を行う関数。グループを返します。

    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 );
    }
    
  3. どのように使用されますか:

    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
    

パフォーマンスの低下に関しては、実際には (大きな) 問題ではないと思います。グループ化 SQL を動的に作成した場合でも、クエリは、グループ化を行わないクエリと同じ数の行を返す必要があります。したがって、このアプローチではグループ化はデータベースによって行われませんが、強制的なクエリの実行によって返される行数は、グループ化基準を使用した架空の SQL クエリの場合と同じです。確かに、データベースはおそらく C# コードによるインメモリ グループ化よりもパフォーマンスが優れていますが、トラフィックの量は、グループ化する必要がある行 ( ) の数だけに依存しますentries

于 2016-10-24T11:18:54.040 に答える