我需要将以下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 )
} );
我不知道如何做到这一点,因为动态LINQ在“hello world”之外完全没有文档记录!选择/ 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
随访: Enigmativity的解决方案主要用于 。由于某种原因,它不希望按日期时间“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 ),
} );
但这似乎严重降低了性能... = /
答案 0 :(得分:8)
这里是动态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 )
} );
答案 1 :(得分:3)
如果您明确想要使用LINQ动态查询库,那么我的答案将不是您想要的,但如果您想要您想要的行为并且您很乐意使用常规LINQ,那么我想我可以提供帮助。
基本上我已经创建了一个EntryGrouper
类来处理下拉列表中所选值的分组逻辑,我假设变量section
,page
&amp; module
持有这些价值观。我还假设ObjectContext.OmniturePageModules
是Entry
类型的可枚举。
所以你的LINQ查询现在变成了这两个:
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
个查询会多次调用数据库,因此以这种方式查询通常要快得多。
第二个查询通过创建EntryGrouper
类的实例作为分组键来对第一个查询的结果进行分组。
我在SeriesName
类中包含了一个EntryGrouper
属性,以便在一个地方整齐地定义所有分组逻辑。
现在,EntryGrouper
类非常大,为了让分组正常工作,它需要具有StartOfWeek
,Section
,Page
和&amp;的属性。 Module
,并包含Equals
&amp;的重载GetHashCode
方法,并实现IEquatable<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;
如果我误解了你的下拉值如何打开和关闭分组,那么你应该只需要改变这些方法,但是这个代码的关键是当分组打开时它应该返回一个基于值的组值。条目,否则它应返回所有条目的公共值。如果该值对于所有条目都是通用的,那么它在逻辑上只创建一个与完全不分组相同的组。
如果您有更多的下拉列表,那么您需要向EntryGrouper
类添加更多属性。不要忘记将这些新属性添加到Equals
&amp; GetHashCode
方法也是如此。
因此,此逻辑表示您想要的动态分组。如果我有帮助或者你需要更多细节,请告诉我。
享受!
答案 2 :(得分:0)
我知道自从这个问题发布以来已经有一段时间了,但我最近不得不处理类似的问题(用户在运行时选择的多个列进行动态分组)所以这就是我对它的看法
用于创建分组lambda的辅助函数
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 );
}
执行内存分组的功能。返回组。
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 );
}
如何使用:
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
)进行分组。