簡體   English   中英

如何創建LINQ表達式樹以選擇匿名類型

[英]How to create LINQ Expression Tree to select an anonymous type

我想使用表達式樹動態生成以下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上放了一個在github上生成動態匿名類型的類。 該項目還包含一些單元測試,以確保假匿名類型的行為與真實類型相同。

這是一個如何使用它的一個非常基本的例子:

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

另外,另一個注意事項:我發現當使用帶有Entity Framework的動態匿名類型時,必須使用“members”參數集調用構造函數。 例如:

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

如果您使用其中一個不包含“members”參數的Expression.New版本,則Entity Framework不會將其識別為匿名類型的構造函數。 所以我認為這意味着真正的匿名類型的構造函數表達式將包含“成員”信息。

你可以在這里使用IQueryable-Extensions,這是“Ethan J. Brown”描述的解決方案的實現:

https://github.com/thiscode/DynamicSelectExtensions

Extension以動態方式構建匿名類型。

然后你可以這樣做:

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發布之前不可用的東西(即使那時我也不確定它是否能夠實現你想要的)。

您最好的解決方案是定義不同的匿名類,然后進行某種邏輯檢查以確定要創建哪個,並創建它,您可以使用對象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; };
}

...並將其放入您的選擇中:

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類型,使用lambda的函數聲明。

編輯:我也不知道你將如何動態生成它。 但至少它告訴你如何使用select lambda返回一個具有多個值的anon類型

EDIT2:

您還必須記住,c#編譯器實際上生成了anon類型的靜態類。 所以anon類型確實在編譯后有一個類型。 因此,如果您在運行時生成這些查詢(我假設您是這樣),您可能必須使用各種反射方法構建一個類型(我相信您可以使用它們來動態創建類型)將創建的類型加載到執行上下文中在生成的輸出中使用它們。

我認為大部分內容已經得到了解答 - 正如Slace所說,你需要一些可以從Select方法返回的類。 獲得類后,可以使用System.Linq.Expressions.NewExpression方法創建表達式。

如果你真的想這樣做,你也可以在運行時生成類。 這是一個更多的工作,因為它不能使用LINQ表達式樹完成,但它是可能的。 你可以使用System.Reflection.Emit命名空間來做到這一點 - 我只是做了一個快速搜索,這是一篇解釋這個的文章:

您可以使用動態表達式API,它允許您動態構建您的select語句,如下所示:

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

您需要LINQ中的Dynamics.cs文件和Visual Studio的語言示例才能使用,這兩個文件都鏈接在本頁底部。 您還可以在同一URL上看到一個顯示此操作的工作示例。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM