匿名型を選択するためのLINQ Expression Treeの作成方法
-
03-07-2019 - |
質問
式ツリーを使用して、次のselectステートメントを動的に生成したい:
var v = from c in Countries
where c.City == "London"
select new {c.Name, c.Population};
生成方法を考えました
var v = from c in Countries
where c.City == "London"
select new {c.Name};
しかし、select lambdaで複数のプロパティを指定できるコンストラクタ/オーバーロードを見つけることができないようです。
解決
これは、前述のように、Reflection Emitと以下に含まれるヘルパークラスの助けを借りて行うことができます。以下のコードは進行中の作業ですので、それが価値があるもののためにそれを取ります...「それは私のボックスで動作します」。 SelectDynamicメソッドクラスは、静的拡張メソッドクラスに追加する必要があります。
予想どおり、タイプは実行時まで作成されないため、IntelliSenseは取得されません。遅延バインドされたデータコントロールに適しています。
public static IQueryable SelectDynamic(this IQueryable source, IEnumerable<string> fieldNames)
{
Dictionary<string, PropertyInfo> sourceProperties = fieldNames.ToDictionary(name => name, name => source.ElementType.GetProperty(name));
Type dynamicType = LinqRuntimeTypeBuilder.GetDynamicType(sourceProperties.Values);
ParameterExpression sourceItem = Expression.Parameter(source.ElementType, "t");
IEnumerable<MemberBinding> bindings = dynamicType.GetFields().Select(p => Expression.Bind(p, Expression.Property(sourceItem, sourceProperties[p.Name]))).OfType<MemberBinding>();
Expression selector = Expression.Lambda(Expression.MemberInit(
Expression.New(dynamicType.GetConstructor(Type.EmptyTypes)), bindings), sourceItem);
return source.Provider.CreateQuery(Expression.Call(typeof(Queryable), "Select", new Type[] { source.ElementType, dynamicType },
Expression.Constant(source), selector));
}
public static class LinqRuntimeTypeBuilder
{
private static readonly ILog log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
private static AssemblyName assemblyName = new AssemblyName() { Name = "DynamicLinqTypes" };
private static ModuleBuilder moduleBuilder = null;
private static Dictionary<string, Type> builtTypes = new Dictionary<string, Type>();
static LinqRuntimeTypeBuilder()
{
moduleBuilder = Thread.GetDomain().DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run).DefineDynamicModule(assemblyName.Name);
}
private static string GetTypeKey(Dictionary<string, Type> fields)
{
//TODO: optimize the type caching -- if fields are simply reordered, that doesn't mean that they're actually different types, so this needs to be smarter
string key = string.Empty;
foreach (var field in fields)
key += field.Key + ";" + field.Value.Name + ";";
return key;
}
public static Type GetDynamicType(Dictionary<string, Type> fields)
{
if (null == fields)
throw new ArgumentNullException("fields");
if (0 == fields.Count)
throw new ArgumentOutOfRangeException("fields", "fields must have at least 1 field definition");
try
{
Monitor.Enter(builtTypes);
string className = GetTypeKey(fields);
if (builtTypes.ContainsKey(className))
return builtTypes[className];
TypeBuilder typeBuilder = moduleBuilder.DefineType(className, TypeAttributes.Public | TypeAttributes.Class | TypeAttributes.Serializable);
foreach (var field in fields)
typeBuilder.DefineField(field.Key, field.Value, FieldAttributes.Public);
builtTypes[className] = typeBuilder.CreateType();
return builtTypes[className];
}
catch (Exception ex)
{
log.Error(ex);
}
finally
{
Monitor.Exit(builtTypes);
}
return null;
}
private static string GetTypeKey(IEnumerable<PropertyInfo> fields)
{
return GetTypeKey(fields.ToDictionary(f => f.Name, f => f.PropertyType));
}
public static Type GetDynamicType(IEnumerable<PropertyInfo> fields)
{
return GetDynamicType(fields.ToDictionary(f => f.Name, f => f.PropertyType));
}
}
他のヒント
受け入れられた答えは非常に便利ですが、実際の匿名型に少し近いものが必要でした。
実際の匿名型には、読み取り専用プロパティ、すべての値を入力するためのコンストラクター、各プロパティの値を比較するためのEquals / GetHashCodeの実装、および各プロパティの名前/値を含む実装ToStringがあります。 ( https://msdn.microsoft.com/en-us/library/bb397696を参照してください。 .aspx で匿名型の完全な説明をご覧ください。)
匿名クラスの定義に基づいて、 https://github.com/dotlattice/LatticeUtils/blob/master/LatticeUtils/AnonymousTypeUtils.cs 。このプロジェクトには、偽の匿名型が実際の型のように動作することを確認するためのユニットテストも含まれています。
使用方法の非常に基本的な例を次に示します。
AnonymousTypeUtils.CreateObject(new Dictionary<string, object>
{
{ "a", 1 },
{ "b", 2 }
});
また、別のメモ:Entity Frameworkで動的な匿名型を使用する場合、コンストラクターは<!> quot; members <!> quot;で呼び出す必要があることがわかりました。パラメータセット。例:
Expression.New(
constructor: anonymousType.GetConstructors().Single(),
arguments: propertyExpressions,
members: anonymousType.GetProperties().Cast<MemberInfo>().ToArray()
);
<!> quot; members <!> quot;を含まないExpression.Newのいずれかのバージョンを使用した場合パラメーター、Entity Frameworkは匿名型のコンストラクターとして認識しません。したがって、実際の匿名型のコンストラクタ式には、<!> quot; members <!> quot;情報。
ここでIQueryable-Extensionsを使用できます。これは、<!> quot; Ethan J. Brown <!> quot;:
で説明されているソリューションの実装です。https://github.com/thiscode/DynamicSelectExtensions
拡張機能は、匿名型を動的に構築します。
これを行うことができます:
var YourDynamicListOfFields = new List<string>(
"field1",
"field2",
[...]
)
var query = query.SelectPartially(YourDynamicListOfFields);
少し遅れているかもしれませんが、誰かに役立つかもしれません。
エンティティからの選択でDynamicSelectGenerator
を呼び出すことにより、動的選択を生成できます。
public static Func<T, T> DynamicSelectGenerator<T>()
{
// get Properties of the T
var fields = typeof(T).GetProperties().Select(propertyInfo => propertyInfo.Name).ToArray();
// input parameter "o"
var xParameter = Expression.Parameter(typeof(T), "o");
// new statement "new Data()"
var xNew = Expression.New(typeof(T));
// create initializers
var bindings = fields.Select(o => o.Trim())
.Select(o =>
{
// property "Field1"
var mi = typeof(T).GetProperty(o);
// original value "o.Field1"
var xOriginal = Expression.Property(xParameter, mi);
// set value "Field1 = o.Field1"
return Expression.Bind(mi, xOriginal);
}
);
// initialization "new Data { Field1 = o.Field1, Field2 = o.Field2 }"
var xInit = Expression.MemberInit(xNew, bindings);
// expression "o => new Data { Field1 = o.Field1, Field2 = o.Field2 }"
var lambda = Expression.Lambda<Func<T, T>>(xInit, xParameter);
// compile to Func<Data, Data>
return lambda.Compile();
}
このコードで使用:
var result = dbContextInstancs.EntityClass.Select(DynamicSelectGenerator<EntityClass>());
これを達成できるとは思わない。 select new { c.Name, c.Population }
を実行すると、実際にクラスを作成していないように見えます。 Reflectorでコンパイルされた出力または生のILを見ると、これを見ることができます。
次のようなクラスがあります:
[CompilerGenerated]
private class <>c__Class {
public string Name { get; set; }
public int Population { get; set; }
}
(わかりました、プロパティは実際には単なるget_Name()
およびset_Name(name)
メソッドであるため、少し調整しました)
あなたがやろうとしているのは、適切な動的クラスの作成です。これは、.NET 4.0が登場するまで利用できません(それでも、あなたが望むものを達成できるかどうかはわかりません)。
最善の解決策は、さまざまな anonymous クラスを定義してから、作成するクラスを決定する何らかの論理チェックを行い、オブジェクトを使用して作成することですSystem.Linq.Expressions.NewExpression
。
しかし、基礎となるLINQプロバイダーについて本当にハードコアになっている場合は、(少なくとも理論的には)それを行うことができます。独自のLINQプロバイダーを 作成している場合、現在解析されている式がSelectであるかどうかを検出し、CompilerGenerated
クラスを決定し、そのコンストラクターに反映して作成します。
間違いなく単純なタスクではありませんが、LINQ to SQL、LINQ to XMLなどがすべてそれを行う方法です。
匿名型を使用する代わりに、パラメータクラスを使用できます。この例では、次のようなパラメータークラスを作成できます。
public struct ParamClass {
public string Name { get; set; };
public int Population { get; set; };
}
<!>#8230;それを次のように選択してください:
var v = from c in Countries
where c.City == "London"
select new ParamClass {c.Name, c.Population};
取得するのは、タイプIQueryable<ParamClass>
です。
これはコンパイルされますが、動作するかどうかわからない...
myEnumerable.Select((p) => { return new { Name = p.Name, Description = p.Description }; });
pが変換対象であり、selectステートメントがラムダの関数宣言を使用してanon型を返していると仮定します。
編集:これを動的に生成する方法もわかりません。ただし、少なくともselect lambdaを使用して複数の値を持つanon型を返す方法は示されています
Edit2:
また、c#コンパイラが実際にanon型の静的クラスを生成することを忘れないでください。そのため、コンパイル後、anon型には実際に型があります。したがって、実行時にこれらのクエリを生成する場合(私はあなたであると仮定します)、さまざまなリフレクションメソッドを使用して型を構築する必要があります(オンザフライで型を作成するために使用できると信じています)作成された型を実行コンテキストにロードし、生成された出力でそれらを使用します。
ほとんどのことはすでに答えられていると思います-Slaceが言ったように、Select
メソッドから返されるクラスが必要です。クラスを取得したら、System.Linq.Expressions.NewExpression
メソッドを使用して式を作成できます。
本当にこれを行いたい場合は、実行時にクラスを生成することもできます。 LINQ Expressionツリーを使用して実行できないため、もう少し作業が必要ですが、可能です。 System.Reflection.Emit
名前空間を使用してそれを行うことができます-私は簡単な検索を行いましたが、これを説明する記事があります:
動的式APIを使用すると、次のように選択ステートメントを動的に構築できます。
Select("new(<property1>,<property2>,...)");
これが機能するためには、LINQのDynamics.csファイルとVisual Studioの言語サンプルが必要です。両方ともこのページ。同じURLで実際に動作している例を示す実例も見ることができます。