我已经被困在这个问题上了一个星期也没有找到解决办法。
我有一个POCO象下面这样:
public class Journal {
public int Id { get; set; }
public string AuthorName { get; set; }
public string Category { get; set; }
public DateTime CreatedAt { get; set; }
}
我想具体的日期范围(以月或年分组)期刊的数量由AUTHORNAME或类别数时就知道了。
之后,我的queryed对象发送到JSON序列然后生成JSON数据如下图所示(只使用JSON来证明我想要得到的数据,如何串行化器对象到JSON是不是我的问题)
data: {
'201301': {
'Alex': 10,
'James': 20
},
'201302': {
'Alex': 1,
'Jessica': 9
}
}
要么
data: {
'2012': {
'C#': 230
'VB.NET': 120,
'LINQ': 97
},
'2013': {
'C#': 115
'VB.NET': 29,
'LINQ': 36
}
}
我所知道的是写在“法的方式”就像一个LINQ查询:
IQueryable<Journal> query = db.GroupBy(x=> new
{
Year = key.CreatedAt.Year,
Month = key.CreatedAt.Month
}, prj => prj.AuthorName)
.Select(data => new {
Key = data.Key.Year * 100 + data.Key.Month, // very ugly code, I know
Details = data.GroupBy(y => y).Select(z => new { z.Key, Count = z.Count() })
});
通过几个月或几年分组的情况下,AUTHORNAME或类别将两个字符串类型的方法参数传递。 我不知道是如何在的GroupBy()方法使用“魔术字符串”参数。 一些google搜索后,我似乎无法通过传递一个神奇的字符串,如“AUTHORNAME”组数据。 我应该做的是建立一个表达式树,并把它传递给的GroupBy()方法。
任何解决方案或建议是欣赏。
哦,这看起来像一个有趣的问题:)
因此,首先,让我们建立了我们的人造光源,因为我没有你的DB方便:
// SETUP: fake up a data source
var folks = new[]{"Alex", "James", "Jessica"};
var cats = new[]{"C#", "VB.NET", "LINQ"};
var r = new Random();
var entryCount = 100;
var entries =
from i in Enumerable.Range(0, entryCount)
let id = r.Next(0, 999999)
let person = folks[r.Next(0, folks.Length)]
let category = cats[r.Next(0, cats.Length)]
let date = DateTime.Now.AddDays(r.Next(0, 100) - 50)
select new Journal() {
Id = id,
AuthorName = person,
Category = category,
CreatedAt = date };
好了,现在我们有一组数据的工作,让我们来看看我们想要的东西......我们想用一个“形”之类的东西:
public Expression<Func<Journal, ????>> GetThingToGroupByWith(
string[] someMagicStringNames,
????)
具有大致相同的功能(在伪代码):
GroupBy(x => new { x.magicStringNames })
让我们来仔细研究它一块在同一时间。 首先,如何赫克我们这样做动态?
x => new { ... }
该编译器的魔法为我们常-它的作用是定义一个新的Type
,我们可以这样做:
var sourceType = typeof(Journal);
// define a dynamic type (read: anonymous type) for our needs
var dynAsm = AppDomain
.CurrentDomain
.DefineDynamicAssembly(
new AssemblyName(Guid.NewGuid().ToString()),
AssemblyBuilderAccess.Run);
var dynMod = dynAsm
.DefineDynamicModule(Guid.NewGuid().ToString());
var typeBuilder = dynMod
.DefineType(Guid.NewGuid().ToString());
var properties = groupByNames
.Select(name => sourceType.GetProperty(name))
.Cast<MemberInfo>();
var fields = groupByNames
.Select(name => sourceType.GetField(name))
.Cast<MemberInfo>();
var propFields = properties
.Concat(fields)
.Where(pf => pf != null);
foreach (var propField in propFields)
{
typeBuilder.DefineField(
propField.Name,
propField.MemberType == MemberTypes.Field
? (propField as FieldInfo).FieldType
: (propField as PropertyInfo).PropertyType,
FieldAttributes.Public);
}
var dynamicType = typeBuilder.CreateType();
所以我们在这里所做的是定义具有我们通过在每个名字,这是相同类型的源类型(属性或字段)一个字段的自定义,一次性类型的。 太好了!
现在,我们如何给LINQ想要的东西?
首先,让我们建立一个“输入”的,我们将返回FUNC:
// Create and return an expression that maps T => dynamic type
var sourceItem = Expression.Parameter(sourceType, "item");
我们知道我们需要“新的了”我们的新的动态类型之一...
Expression.New(dynamicType.GetConstructor(Type.EmptyTypes))
而我们需要的价值观从参数中来初始化它...
Expression.MemberInit(
Expression.New(dynamicType.GetConstructor(Type.EmptyTypes)),
bindings),
但是,到底什么是我们要使用的bindings
? 嗯......嗯,我们想要的东西,结合到源类型相应的属性/字段,但他们重新映射到我们dynamicType
领域...
var bindings = dynamicType
.GetFields()
.Select(p =>
Expression.Bind(
p,
Expression.PropertyOrField(
sourceItem,
p.Name)))
.OfType<MemberBinding>()
.ToArray();
OOF ...讨厌的,但我们还没有完成的-所以我们需要声明的返回类型Func
通过表达式树我们正在创造......有疑问时,使用object
!
Expression.Convert( expr, typeof(object))
最后,我们将结合这通过我们的“输入参数” Lambda
,使得整个堆栈:
// Create and return an expression that maps T => dynamic type
var sourceItem = Expression.Parameter(sourceType, "item");
var bindings = dynamicType
.GetFields()
.Select(p => Expression.Bind(p, Expression.PropertyOrField(sourceItem, p.Name)))
.OfType<MemberBinding>()
.ToArray();
var fetcher = Expression.Lambda<Func<T, object>>(
Expression.Convert(
Expression.MemberInit(
Expression.New(dynamicType.GetConstructor(Type.EmptyTypes)),
bindings),
typeof(object)),
sourceItem);
为了方便使用,让整个包住弄乱作为一个扩展方法,所以现在我们有:
public static class Ext
{
// Science Fact: the "Grouper" (as in the Fish) is classified as:
// Perciformes Serranidae Epinephelinae
public static Expression<Func<T, object>> Epinephelinae<T>(
this IEnumerable<T> source,
string [] groupByNames)
{
var sourceType = typeof(T);
// define a dynamic type (read: anonymous type) for our needs
var dynAsm = AppDomain
.CurrentDomain
.DefineDynamicAssembly(
new AssemblyName(Guid.NewGuid().ToString()),
AssemblyBuilderAccess.Run);
var dynMod = dynAsm
.DefineDynamicModule(Guid.NewGuid().ToString());
var typeBuilder = dynMod
.DefineType(Guid.NewGuid().ToString());
var properties = groupByNames
.Select(name => sourceType.GetProperty(name))
.Cast<MemberInfo>();
var fields = groupByNames
.Select(name => sourceType.GetField(name))
.Cast<MemberInfo>();
var propFields = properties
.Concat(fields)
.Where(pf => pf != null);
foreach (var propField in propFields)
{
typeBuilder.DefineField(
propField.Name,
propField.MemberType == MemberTypes.Field
? (propField as FieldInfo).FieldType
: (propField as PropertyInfo).PropertyType,
FieldAttributes.Public);
}
var dynamicType = typeBuilder.CreateType();
// Create and return an expression that maps T => dynamic type
var sourceItem = Expression.Parameter(sourceType, "item");
var bindings = dynamicType
.GetFields()
.Select(p => Expression.Bind(
p,
Expression.PropertyOrField(sourceItem, p.Name)))
.OfType<MemberBinding>()
.ToArray();
var fetcher = Expression.Lambda<Func<T, object>>(
Expression.Convert(
Expression.MemberInit(
Expression.New(dynamicType.GetConstructor(Type.EmptyTypes)),
bindings),
typeof(object)),
sourceItem);
return fetcher;
}
}
现在,使用它:
// What you had originally (hand-tooled query)
var db = entries.AsQueryable();
var query = db.GroupBy(x => new
{
Year = x.CreatedAt.Year,
Month = x.CreatedAt.Month
}, prj => prj.AuthorName)
.Select(data => new {
Key = data.Key.Year * 100 + data.Key.Month, // very ugly code, I know
Details = data.GroupBy(y => y).Select(z => new { z.Key, Count = z.Count() })
});
var func = db.Epinephelinae(new[]{"CreatedAt", "AuthorName"});
var dquery = db.GroupBy(func, prj => prj.AuthorName);
该解决方案缺乏“嵌套语句”的灵活性,如“CreatedDate.Month”,但有一点想象力,你可能延长这一想法与任何自由的查询工作。