48

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

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

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

4

9 回答 9

73

如前所述,这可以在 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));
    }
}
于 2009-04-06T20:12:27.093 回答
9

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

真正的匿名类型具有只读属性、用于填充所有值的构造函数、用于比较每个属性值的 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 }
});

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

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

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

于 2015-01-25T19:12:32.553 回答
5

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

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>());
于 2017-07-20T04:45:04.990 回答
2

我不相信你能做到这一点。虽然当你这样做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 出现才可用(即使那样我也不确定它是否能够实现您想要的)。

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

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

绝对不是一项简单的任务,但它是 LINQ to SQL、LINQ to XML 等如何做到的。

于 2009-03-03T12:25:07.943 回答
2

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

https://github.com/thiscode/DynamicSelectExtensions

扩展动态构建匿名类型。

然后你可以这样做:

var YourDynamicListOfFields = new List<string>(

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

)
var query = query.SelectPartially(YourDynamicListOfFields);
于 2013-07-04T10:34:49.823 回答
1

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

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>

于 2009-03-03T12:32:18.020 回答
1

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

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

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

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

编辑2:

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

于 2009-03-03T12:53:07.220 回答
1

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

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

于 2009-03-04T13:40:40.593 回答
0

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

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

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

于 2009-03-03T12:23:56.400 回答