C# 如何创建 LINQ 表达式树以选择匿名类型

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/606104/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 10:05:50  来源:igfitidea点击:

How to create LINQ Expression Tree to select an anonymous type

c#linqentity-frameworklinq-to-entitiesexpression-trees

提问by Tom Deloford

I would like to generate the following select statement dynamically using expression trees:

我想使用表达式树动态生成以下 select 语句:

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

I have worked out how to generate

我已经研究出如何生成

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

but I cannot seem to find a constructor/overload that will let me specify multiple properties in my select lambda.

但我似乎找不到一个构造函数/重载,可以让我在我的选择 lambda 中指定多个属性。

采纳答案by Ethan J. Brown

This can be done, as mentioned, with the help of Reflection Emit and a helper class I've included below. The code below is a work in progress, so take it for what it's worth... 'it works on my box'. The SelectDynamic method class should be tossed in a static extension method class.

如前所述,这可以在 Reflection Emit 和我在下面包含的帮助类的帮助下完成。下面的代码正在开发中,所以请考虑它的价值......“它适用于我的盒子”。SelectDynamic 方法类应该放在静态扩展方法类中。

As expected, you won't get any Intellisense since the type isn't created until runtime. Works good on late-bound data controls.

正如预期的那样,您不会获得任何智能感知,因为直到运行时才会创建类型。适用于后期绑定数据控件。

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));
    }
}

回答by Adrian Grigore

You could use the Dynamic Expression API which allows you to dynamically build your select statement like this:

您可以使用动态表达式 API,它允许您像这样动态构建选择语句:

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

You need the Dynamics.cs file from the LINQ and language samples for Visual Studio for this to work, both are linked at the bottom of this page. You can also see a working example showing this in action on at the same URL.

您需要 LINQ 中的 Dynamics.cs 文件和 Visual Studio 的语言示例才能使其工作,两者都在本页底部链接。您还可以在同一 URL 上看到一个显示此操作的工作示例。

回答by Aaron Powell

I don't believe that you will be able to achieve this. Although when you do select new { c.Name, c.Population }it seems like you're not creating a class you actually are. If you have a look at the compiled output in Reflector or the raw IL you will be able to see this.

我不相信你能做到这一点。尽管当您这样做时,您select new { c.Name, c.Population }似乎并没有创建一个类,但实际上您是在创建类。如果您查看 Reflector 中的编译输出或原始 IL,您将能够看到这一点。

You'll have a class which would look something like this:

你会有一个看起来像这样的类:

[CompilerGenerated]
private class <>c__Class {
  public string Name { get; set; }
  public int Population { get; set; }
}

(Ok, I cleaned it up a touch, since a property is really just a get_Name()and set_Name(name)method set anyway)

(好吧,我稍微清理了一下,因为属性实际上只是一个get_Name()set_Name(name)方法集)

What you're trying to do is proper dynamic class creation, something which wont be available until .NET 4.0 comes out (and even then I'm not really sure if it'll be able to achieve what you want).

您正在尝试做的是适当的动态类创建,在 .NET 4.0 出现之前不会可用的东西(即使那样我也不确定它是否能够实现您想要的)。

You're best solution would be to define the different anonymousclasses and then have some kind of logical check to determine which one to create, and to create it you can use the object System.Linq.Expressions.NewExpression.

您最好的解决方案是定义不同的匿名类,然后进行某种逻辑检查以确定要创建哪个类,并创建它您可以使用 object System.Linq.Expressions.NewExpression

But, it may be (in theory at least) possible to do it, if you're getting really hard-core about the underlying LINQ provider. If you arewriting your own LINQ provider you can detect if the currently-parsed expression is a Select, then you determine the CompilerGeneratedclass, reflect for its constructor and create.

但是,如果您对底层 LINQ 提供程序非常了解,则可能(至少在理论上)可以这样做。如果您正在编写自己的 LINQ 提供程序,您可以检测当前解析的表达式是否为 Select,然后您确定CompilerGenerated类,反映其构造函数并创建。

Defiantly not a simple task, but it would be how LINQ to SQL, LINQ to XML, etc all do it.

这绝对不是一项简单的任务,但这将是 LINQ to SQL、LINQ to XML 等的全部工作方式。

回答by Spoike

You could use a parameter class instead of working with an anonymous type. In your example you can create a parameter class like this:

您可以使用参数类而不是使用匿名类型。在您的示例中,您可以创建这样的参数类:

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

…and put it into your select like this:

...并像这样将其放入您的选择中:

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

What you get out is something of the type IQueryable<ParamClass>.

你得到的是那种类型的东西IQueryable<ParamClass>

回答by Sekhat

This compiles, I dunno if it works however...

这可以编译,但我不知道它是否有效......

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

Assuming p is what your transforming, and the select statement is returning an anon type, using the function declaration of lambda's.

假设 p 是您的转换,并且 select 语句使用 lambda 的函数声明返回 anon 类型。

Edit: I also don't know how you would generate this dynamically. But at least it shows you how to use the select lambda to return an anon type with multiple values

编辑:我也不知道您将如何动态生成它。但至少它向您展示了如何使用 select lambda 返回具有多个值的匿名类型

Edit2:

编辑2:

You would also have to bare in mind, that the c# compiler actually generates static classes of the anon type. So the anon type does actually have a type after compile time. So if your generating these queries at run time (which I assume you are) you may have to construct a type using the various reflection methods (I believe you can use them to make types on the fly) load the created types into execution context and use them in your generated output.

您还必须牢记,c# 编译器实际上生成 anon 类型的静态类。所以 anon 类型在编译时确实有一个类型。因此,如果您在运行时生成这些查询(我假设您是),您可能必须使用各种反射方法来构造一个类型(我相信您可以使用它们来动态创建类型)将创建的类型加载到执行上下文中并在生成的输出中使用它们。

回答by Tomas Petricek

I think most of the things are already answered - as Slace said, you need some class that would be returned from the Selectmethod. Once you have the class, you can use the System.Linq.Expressions.NewExpressionmethod to create the expression.

我认为大多数事情都已经得到了回答 - 正如 Slace 所说,你需要一些从Select方法中返回的类。一旦你有了这个类,你就可以使用这个System.Linq.Expressions.NewExpression方法来创建表达式。

If you really want to do this, you can generate class at runtime too. It's a bit more work, because it cannot be done using LINQ Expression trees, but it's possible. You can use System.Reflection.Emitnamespace to do that - I just did a quick search and here is an article that explains this:

如果你真的想这样做,你也可以在运行时生成类。这需要更多的工作,因为它不能使用 LINQ 表达式树完成,但它是可能的。你可以使用System.Reflection.Emit命名空间来做到这一点——我只是做了一个快速搜索,这里有一篇文章解释了这一点:

回答by thiscode

You could use the IQueryable-Extensions here, which is an implemantation of the solution described by "Ethan J. Brown":

您可以在此处使用 IQueryable-Extensions,这是“Ethan J. Brown”描述的解决方案的实现:

https://github.com/thiscode/DynamicSelectExtensions

https://github.com/thiscode/DynamicSelectExtensions

The Extension builds dynamically an anonymous type.

扩展动态构建匿名类型。

Then you can do this:

然后你可以这样做:

var YourDynamicListOfFields = new List<string>(

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

)
var query = query.SelectPartially(YourDynamicListOfFields);

回答by dotlattice

The accepted answer is very useful, but I needed something a little closer to a real anonymous type.

接受的答案非常有用,但我需要一些更接近真正匿名类型的东西。

A real anonymous type has read-only properties, a constructor for filling in all of the values, an implementation of Equals/GetHashCode for comparing the values of each property, and an implementation ToString that includes the name/value of each property. (See https://msdn.microsoft.com/en-us/library/bb397696.aspxfor a full description of anonymous types.)

真正的匿名类型具有只读属性、用于填充所有值的构造函数、用于比较每个属性值的 Equals/GetHashCode 实现以及包含每个属性的名称/值的 ToString 实现。(有关匿名类型的完整说明,请参阅https://msdn.microsoft.com/en-us/library/bb397696.aspx。)

Based on that definition of anonymous classes, I put a class that generates dynamic anonymous types on github at https://github.com/dotlattice/LatticeUtils/blob/master/LatticeUtils/AnonymousTypeUtils.cs. The project also contains some unit tests to make sure the fake anonymous types behave like real ones.

基于匿名类的定义,我在https://github.com/dotlattice/LatticeUtils/blob/master/LatticeUtils/AnonymousTypeUtils.cs上放置了一个生成动态匿名类型的类。该项目还包含一些单元测试,以确保假匿名类型的行为与真实类型相同。

Here's a very basic example of how to use it:

这是如何使用它的一个非常基本的示例:

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

Also, another note: I found that when using a dynamic anonymous type with Entity Framework, the constructor must be called with the "members" parameter set. For example:

另外,另一个注意事项:我发现在 Entity Framework 中使用动态匿名类型时,必须使用“members”参数集调用构造函数。例如:

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

If you used one of the versions of Expression.New that does not include the "members" parameter, Entity Framework would not recognize it as the constructor of an anonymous type. So I assume that means a real anonymous type's constructor expression would include that "members" information.

如果您使用不包含“成员”参数的 Expression.New 版本之一,则实体框架不会将其识别为匿名类型的构造函数。所以我认为这意味着真正的匿名类型的构造函数表达式将包含“成员”信息。

回答by Ali

Maybe a bit late but may help to someone.

也许有点晚,但可能对某人有所帮助。

You Can generate dynamic select by call DynamicSelectGeneratorin select from an entity.

您可以通过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();
        }

And use by this code:

并通过此代码使用:

var result = dbContextInstancs.EntityClass.Select(DynamicSelectGenerator<EntityClass>());