Pregunta

Me gustaría generar la siguiente declaración de selección dinámicamente usando árboles de expresión:

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

He descubierto cómo generar

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

pero parece que no puedo encontrar un constructor/sobrecarga que me permita especificar múltiples propiedades en mi lambda seleccionada.

¿Fue útil?

Solución

Esto se puede hacer, como se mencionó, con la ayuda de Reflection Emit y una clase auxiliar que he incluido a continuación. El siguiente código es un trabajo en progreso, así que tómalo por lo que vale ... 'funciona en mi caja'. La clase de método SelectDynamic debe lanzarse en una clase de método de extensión estática.

Como se esperaba, no obtendrá Intellisense ya que el tipo no se crea hasta el tiempo de ejecución. Funciona bien en controles de datos enlazados tarde.

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

Otros consejos

La respuesta aceptada es muy útil, pero necesitaba algo un poco más cercano a un tipo anónimo real.

Un tipo anónimo real tiene propiedades de solo lectura, un constructor para completar todos los valores, una implementación de Equals / GetHashCode para comparar los valores de cada propiedad y una implementación ToString que incluye el nombre / valor de cada propiedad . (Consulte https://msdn.microsoft.com/en-us/library/bb397696 .aspx para obtener una descripción completa de los tipos anónimos.

Basado en esa definición de clases anónimas, pongo una clase que genera tipos anónimos dinámicos en github en https://github.com/dotlattice/LatticeUtils/blob/master/LatticeUtils/AnonymousTypeUtils.cs . El proyecto también contiene algunas pruebas unitarias para asegurarse de que los tipos anónimos falsos se comporten como los reales.

Aquí hay un ejemplo muy básico de cómo usarlo:

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

Además, otra nota: descubrí que cuando se usa un tipo anónimo dinámico con Entity Framework, se debe llamar al constructor con " members " conjunto de parámetros Por ejemplo:

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

Si usó una de las versiones de Expression.New que no incluye " members " parámetro, Entity Framework no lo reconocería como el constructor de un tipo anónimo. Así que supongo que eso significa que la expresión de constructor de un tipo anónimo real incluiría que & Quot; members & Quot; información.

Puede utilizar las extensiones IQueryable aquí, que es una implementación de la solución descrita por & "; Ethan J. Brown &";

https://github.com/thiscode/DynamicSelectExtensions

La extensión crea dinámicamente un tipo anónimo.

Entonces puedes hacer esto:

var YourDynamicListOfFields = new List<string>(

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

)
var query = query.SelectPartially(YourDynamicListOfFields);

Quizás un poco tarde pero puede ayudar a alguien.

Puede generar selección dinámica mediante la llamada DynamicSelectGenerator en seleccionar de una entidad.

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

Y uso por este código:

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

No creo que puedas lograr esto.Aunque cuando lo haces select new { c.Name, c.Population } parece que no estás creando una clase que realmente estás creando.Si echa un vistazo a la salida compilada en Reflector o al IL sin formato, podrá ver esto.

Tendrás una clase que se vería así:

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

(Ok, lo limpié un poco, ya que una propiedad en realidad es solo una get_Name() y set_Name(name) método establecido de todos modos)

Lo que estás intentando hacer es una creación de clases dinámica adecuada, algo que no estará disponible hasta que salga .NET 4.0 (e incluso entonces no estoy muy seguro de si podrá lograr lo que deseas).

Tu mejor solución sería definir los diferentes anónimo clases y luego tener algún tipo de verificación lógica para determinar cuál crear, y para crearlo puedes usar el objeto System.Linq.Expressions.NewExpression.

Pero puede ser posible (al menos en teoría) hacerlo si se está volviendo muy estricto con el proveedor LINQ subyacente.Si usted son Al escribir su propio proveedor LINQ, puede detectar si la expresión actualmente analizada es una Selección, luego determina el CompilerGenerated clase, reflexionar para su constructor y crear.

Definitivamente no es una tarea sencilla, pero así sería como lo hacen LINQ to SQL, LINQ to XML, etc.

Puede usar una clase de parámetro en lugar de trabajar con un tipo anónimo. En su ejemplo, puede crear una clase de parámetro como esta:

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

& # 8230; y póngalo en su selección así:

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

Lo que obtienes es algo del tipo IQueryable<ParamClass>.

Esto compila, no sé si funciona sin embargo ...

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

Suponiendo que p es lo que está transformando, y la instrucción select está devolviendo un tipo anon, utilizando la declaración de función de lambda.

Editar: Tampoco sé cómo generarías esto dinámicamente. Pero al menos le muestra cómo usar el lambda select para devolver un tipo anon con múltiples valores

Editar2:

También debería tener en cuenta que el compilador de C # en realidad genera clases estáticas del tipo anon. Entonces, el tipo anon realmente tiene un tipo después del tiempo de compilación. Entonces, si está generando estas consultas en tiempo de ejecución (lo que supongo que es), puede que tenga que construir un tipo usando los diversos métodos de reflexión (creo que puede usarlos para hacer tipos sobre la marcha) cargue los tipos creados en el contexto de ejecución y úselos en su salida generada.

Creo que la mayoría de las cosas ya están respondidas: como dijo Slace, necesita alguna clase que se devuelva del método Select. Una vez que tenga la clase, puede usar el método System.Linq.Expressions.NewExpression para crear la expresión.

Si realmente quieres hacer esto, también puedes generar clases en tiempo de ejecución. Es un poco más de trabajo, porque no se puede hacer usando árboles de expresión LINQ, pero es posible. Puede usar System.Reflection.Emit espacio de nombres para hacer eso. Acabo de hacer una búsqueda rápida y aquí hay un artículo que explica esto:

Puede usar la API de expresión dinámica que le permite construir dinámicamente su declaración de selección de esta manera:

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

Necesita el archivo Dynamics.cs de LINQ y ejemplos de idioma para que Visual Studio funcione, ambos están vinculados en la parte inferior de esta página . También puede ver un ejemplo de trabajo que muestra esto en acción en la misma URL.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top