web-dev-qa-db-ja.com

匿名型を選択するLINQ式ツリーを作成する方法

式ツリーを使用して動的に次の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で複数のプロパティを指定できるコンストラクター/オーバーロードを見つけることができないようです。

45
Tom Deloford

これは、前述のように、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));
    }
}
70
Ethan J. Brown

受け入れられた答えは非常に便利ですが、実際の匿名型に少し近いものが必要でした。

実際の匿名型には、読み取り専用プロパティ、すべての値を入力するためのコンストラクター、各プロパティの値を比較するためのEquals/GetHashCodeの実装、および各プロパティの名前/値を含む実装ToStringがあります。 (匿名タイプの詳細については、 https://msdn.Microsoft.com/en-us/library/bb397696.aspx を参照してください。)

その匿名クラスの定義に基づいて、動的匿名型を生成するクラスをgithubの https://github.com/dotlattice/LatticeUtils/blob/master/LatticeUtils/AnonymousTypeUtils.cs に配置しました。プロジェクトには、偽の匿名型が実際のものと同じように動作することを確認するためのいくつかの単体テストも含まれています。

これは、使用方法の非常に基本的な例です。

AnonymousTypeUtils.CreateObject(new Dictionary<string, object>
{
    { "a", 1 },
    { "b", 2 }
});

また、別のメモ:Entity Frameworkで動的匿名型を使用する場合、コンストラクターを「メンバー」パラメーターセットで呼び出す必要があることがわかりました。例えば:

Expression.New(
    constructor: anonymousType.GetConstructors().Single(), 
    arguments: propertyExpressions,
    members: anonymousType.GetProperties().Cast<MemberInfo>().ToArray()
); 

「members」パラメーターを含まないExpression.Newのバージョンの1つを使用した場合、Entity Frameworkはそれを匿名型のコンストラクターとして認識しません。つまり、本当の匿名型のコンストラクタ式にその「メンバー」情報が含まれることを意味します。

9
dotlattice

多分少し遅れますが、誰かを助けるかもしれません。

エンティティからの選択で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>());
3
Ali

ここでIQueryable-Extensionsを使用できます。これは、「Ethan J. Brown」によって記述されたソリューションの実装です。

https://github.com/thiscode/DynamicSelectExtensions

拡張機能は匿名型を動的に構築します。

次に、これを行うことができます:

var YourDynamicListOfFields = new List<string>(

    "field1",
    "field2",
    [...]

)
var query = query.SelectPartially(YourDynamicListOfFields);
2
thiscode

匿名型を操作する代わりに、パラメータークラスを使用できます。あなたの例では、次のようなパラメータクラスを作成できます。

public struct ParamClass {
    public string Name { get; set; };
    public int Population { get; set; };
}

…それを次のようにselectに入れます:

var v = from c in Countries
        where c.City == "London"
        select new ParamClass {c.Name, c.Population};

あなたが得るものはタイプの何かですIQueryable<ParamClass>

1
Spoike

Slaceが言ったように、Selectメソッドから返されるクラスが必要です。クラスを取得したら、System.Linq.Expressions.NewExpressionメソッドを使用して式を作成できます。

本当にこれを実行したい場合は、実行時にクラスを生成することもできます。 LINQ式ツリーを使用して行うことはできないため、少し作業が増えますが、それは可能です。 System.Reflection.Emit名前空間を使用してそれを行うことができます-私は簡単な検索を行ったばかりで、これを説明する記事があります:

1
Tomas Petricek

これはコンパイルされますが、うまくいくかどうかはわかりません...

myEnumerable.Select((p) => { return new { Name = p.Name, Description = p.Description }; });

Pが変換対象であり、selectステートメントがラムダの関数宣言を使用してanonタイプを返すと仮定します。

編集:私はこれを動的に生成する方法もわかりません。しかし、少なくとも、select lambdaを使用して複数の値を持つanon型を返す方法を示しています

Edit2:

また、c#コンパイラーが実際にanonタイプの静的クラスを生成することにも注意する必要があります。そのため、anon型は実際にはコンパイル後に型を持っています。したがって、実行時にこれらのクエリを生成している場合(私はそうだと思います)、さまざまなリフレクションメソッドを使用してタイプを構築する必要がある場合があります(それらを使用してオンザフライでタイプを作成できると思います)作成されたタイプを実行コンテキストにロードし、生成された出力でそれらを使用します。

1
Sekhat

あなたがこれを成し遂げることができるとは思わない。 _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プロバイダーについて本当にハードコアになっている場合は、(少なくとも理論的には)それを実行できる可能性があります。 are独自のLINQプロバイダーを作成している場合は、現在解析されている式がSelectかどうかを検出でき、CompilerGeneratedクラスを決定し、そのコンストラクターを反映して作成します。

反抗的には単純なタスクではありませんが、それはLINQ to SQL、LINQ to XMLなどがすべてそれを行う方法です。

1
Aaron Powell

次のようなselectステートメントを動的に作成できるDynamic Expression APIを使用できます。

 Select("new(<property1>,<property2>,...)");

これを機能させるには、LINQのDynamics.csファイルとVisual Studioの言語サンプルが必要です。どちらも このページ の下部にリンクされています。同じURLで実際に動作する例を確認することもできます。

0
Adrian Grigore