Domanda

Vorrei generare la seguente dichiarazione select in modo dinamico usando gli alberi delle espressioni:

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

Ho scoperto come generare

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

ma non riesco a trovare un costruttore / sovraccarico che mi permetta di specificare più proprietà nel mio lambda selezionato.

È stato utile?

Soluzione

Questo può essere fatto, come detto, con l'aiuto di Reflection Emit e una classe di supporto che ho incluso di seguito. Il codice qui sotto è in fase di elaborazione, quindi prendilo per quello che vale ... "funziona sulla mia scatola". La classe del metodo SelectDynamic deve essere lanciata in una classe del metodo di estensione statica.

Come previsto, non otterrai Intellisense poiché il tipo non viene creato fino al runtime. Funziona bene con i controlli dei dati in ritardo.

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

Altri suggerimenti

La risposta accettata è molto utile, ma avevo bisogno di qualcosa di un po 'più vicino a un vero tipo anonimo.

Un vero tipo anonimo ha proprietà di sola lettura, un costruttore per compilare tutti i valori, un'implementazione di Equals / GetHashCode per confrontare i valori di ciascuna proprietà e un'implementazione ToString che include il nome / valore di ogni proprietà . (Vedi https://msdn.microsoft.com/en-us/library/bb397696 .aspx per una descrizione completa dei tipi anonimi.)

Sulla base di quella definizione di classi anonime, ho messo una classe che genera tipi anonimi dinamici su github in https://github.com/dotlattice/LatticeUtils/blob/master/LatticeUtils/AnonymousTypeUtils.cs . Il progetto contiene anche alcuni test unitari per assicurarsi che i falsi tipi anonimi si comportino come quelli reali.

Ecco un esempio molto semplice di come usarlo:

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

Inoltre, un'altra nota: ho scoperto che quando si utilizza un tipo anonimo dinamico con Entity Framework, il costruttore deve essere chiamato con il " members " set di parametri. Ad esempio:

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

Se hai utilizzato una delle versioni di Expression.New che non include " members " parametro, Entity Framework non lo riconoscerebbe come il costruttore di un tipo anonimo. Quindi suppongo che significhi che l'espressione del costruttore di un tipo anonimo includerebbe tale & Quot; members & Quot; informazioni.

Qui puoi usare IQueryable-Extensions, che è un'implementazione della soluzione descritta da " Ethan J. Brown " ;:

https://github.com/thiscode/DynamicSelectExtensions

L'estensione crea dinamicamente un tipo anonimo.

Quindi puoi farlo:

var YourDynamicListOfFields = new List<string>(

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

)
var query = query.SelectPartially(YourDynamicListOfFields);

Forse un po 'in ritardo ma può aiutare qualcuno.

È possibile generare la selezione dinamica chiamando DynamicSelectGenerator nella selezione da un'entità.

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

E utilizzare con questo codice:

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

Non credo che sarai in grado di raggiungere questo obiettivo. Anche se quando fai select new { c.Name, c.Population } sembra che tu non stia creando una classe in realtà. Se dai un'occhiata all'output compilato in Reflector o IL non elaborato, sarai in grado di vederlo.

Avrai una classe simile a questa:

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

(Ok, l'ho ripulito un po ', dal momento che una proprietà è in realtà solo un metodo get_Name() e set_Name(name) impostato comunque)

Quello che stai cercando di fare è la corretta creazione di una classe dinamica, qualcosa che non sarà disponibile fino alla pubblicazione di .NET 4.0 (e anche in questo caso non sono davvero sicuro che sarà in grado di ottenere ciò che desideri).

La soluzione migliore sarebbe quella di definire le diverse classi anonime e quindi avere un qualche tipo di controllo logico per determinare quale creare e per crearlo è possibile utilizzare l'oggetto System.Linq.Expressions.NewExpression .

Ma potrebbe essere (almeno in teoria) possibile farlo, se stai diventando davvero duro sul provider LINQ sottostante. Se stai scrivendo il tuo provider LINQ puoi rilevare se l'espressione attualmente analizzata è una selezione, quindi determini la classe CompilerGenerated, rifletti per il suo costruttore e crea.

Di contro non è un compito semplice, ma sarebbe come LINQ to SQL, LINQ to XML, ecc. tutti lo fanno.

È possibile utilizzare una classe di parametri invece di lavorare con un tipo anonimo. Nel tuo esempio puoi creare una classe di parametri come questa:

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

& # 8230; e inseriscilo nella tua selezione in questo modo:

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

Quello che ottieni è qualcosa del tipo IQueryable<ParamClass>.

Questo compila, non so se funziona comunque ...

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

Supponendo che p sia ciò che stai trasformando, e l'istruzione select sta restituendo un tipo anon, usando la dichiarazione di funzione di lambda.

Modifica: anche io non so come lo genereresti dinamicamente. Ma almeno ti mostra come usare il lambda select per restituire un tipo anon con più valori

Edit2:

Dovresti anche tenere a mente che il compilatore c # in realtà genera classi statiche di tipo anon. Quindi il tipo anon ha effettivamente un tipo dopo il tempo di compilazione. Quindi, se stai generando queste query in fase di esecuzione (cosa che suppongo tu sia) potresti dover costruire un tipo usando i vari metodi di riflessione (credo che puoi usarli per rendere i tipi al volo) caricare i tipi creati nel contesto di esecuzione e usali nel tuo output generato.

Penso che alla maggior parte delle cose sia già stata data una risposta - come ha detto Slace, hai bisogno di una classe che verrebbe restituita dal metodo Select. Una volta che hai la classe, puoi usare il metodo System.Linq.Expressions.NewExpression per creare l'espressione.

Se vuoi davvero farlo, puoi anche generare classe in fase di esecuzione. È un po 'più di lavoro, perché non può essere fatto utilizzando gli alberi di espressione LINQ, ma è possibile. Puoi usare System.Reflection.Emit lo spazio dei nomi per farlo - ho appena fatto una rapida ricerca ed ecco un articolo che spiega questo:

È possibile utilizzare l'API Dynamic Expression che consente di creare in modo dinamico la propria istruzione select in questo modo:

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

È necessario il file Dynamics.cs dal LINQ e gli esempi di lingua affinché Visual Studio funzioni, entrambi sono collegati nella parte inferiore di questa pagina . Puoi anche vedere un esempio funzionante che mostra questo in azione sullo stesso URL.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top