ああ、これは楽しい問題のように見えます:)
だから、最初、私はあなたの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 };
[OK]を、今、我々は我々が望むものを見てみましょう、と連携するデータのセットを持っている...私たちはのような「形状」で何かをしたい:
public Expression<Func<Journal, ????>> GetThingToGroupByWith(
string[] someMagicStringNames,
????)
ほぼ同じ機能を持っていること(擬似コードで):
GroupBy(x => new { x.magicStringNames })
一度に1つずつ解剖しましょう。まず、どうやって動的にこれを行うのですか?
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();
です、私たちが渡す名前ごとに1つのフィールドを持つ使い捨て型。ソース型の(プロパティまたはフィールドのいずれかと同じ)型です。ニース!
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
を使用してください。 、使用を容易にするため
// 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);
のは、拡張機能として全体の混乱を締めくくりましょう:
Expression.Convert(expr, typeof(object))
そして最後に、我々はスタック全体を作り、Lambda
を経由して私たちの「入力パラメータ」にこれを結合します方法、今私たちが持っている:
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」のような「ネストされた文」の柔軟性に欠けていますが、少し想像力があれば、任意のフリーフォームクエリで動作するようにこのアイデアを拡張できます。
ダイナミックLINQを見ましたか? – svick
@svick LINQ to Entityの別のオプションがある場合は、動的LINQではなくStackOverflowのDapperを選択します –
ダイナミックLINQはIQueryableの上で動作するため、LINQ to Entitiesなどのライブラリを置き換えることはありません。それが働くように。 – svick