Come convertire Expression > all'espressione >?

expression-trees functional-programming generics linq reflection

Domanda

Sto provando a creare un dizionario di espressioni con diversi tipi di parametri di input. Sto cercando di memorizzare il tipo del parametro perché in seguito lungo la strada ho intenzione di usare Reflection per scoprire un metodo sul tipo. Ecco il codice che crea il dizionario e una funzione di aggiunta generica che ho creato per aggiungere voci ad esso:

public class LoadEntityQuery : IQuery<LoadEntityQueryResult>
{
    public IDictionary<Type, Expression<Func<Type, bool>>> Entities { get; set; } 
    public LoadEntityQuery()
    {
        Entities = new Dictionary<Type, Expression<Func<Type, bool>>>();
    }

    public void Add<T>(Expression<Func<T, bool>> where = null) where T : Entity
    {
        Expression<Func<Type, bool>> _lambda = null;

        if (where != null)
        {
            ParameterExpression param = Expression.Parameter(typeof(T), where.Parameters[0].Name);

            var body = Expression.Invoke(where, param);
            _lambda = Expression.Lambda<Func<Type, bool>>(body, param);
        }

        Entities.Add(typeof(T), _lambda);
    }
}

Il corpo del nuovo metodo è stato creato correttamente. Il problema è quando provo a creare la nuova espressione Lambda con il tipo dall'espressione che viene passata, ricevo questo errore:

ParameterExpression di tipo "TestNamespace.TestClass" non può essere utilizzato per il parametro delegato di tipo "System.Type"

Qualcuno ha un'idea di cosa posso fare in questa situazione? Come ho detto prima, ad un certo punto più avanti ho intenzione di scorrere questo dizionario per fare qualche programmazione riflessiva su ogni voce. Se c'è un modo migliore per farlo, sono tutto orecchie.

Come esempio di ciò che sto cercando di fare, memorizzo le espressioni per le clausole Where per gli oggetti POCO che devono essere inizializzati:

LoadEntityQuery _query = new LoadEntityQuery();
    _query.Add<PayrollLocation>();
    _query.Add<PayrollGroupBU>();
    _query.Add<PersonnelPosition>(t => t.DataSet == MasterDataSet);
    _query.Add<EmployeeStatus>();
    _query.Add<PayrollGrade>();

Questo elenco di entità sarà diverso per ogni app. L'idea è di raccogliere tutte le entità e la clausola Where per ciascuna e scoprire un determinato metodo usando la riflessione su ciascuna di esse. (ad es. PayrollLocation ha un metodo GetPayrollLocationsQuery (), PayrollGroupBU ha un metodo GetPayrollGroupBUQuery () ...). Il metodo Add è generico per farmi usare l'espressione lambda nel codice chiamante.

Grazie, Jason

Risposta accettata

Osservando da vicino il tuo codice, l'espressione che hai generato presenta alcuni problemi. Vedi la mia spiegazione in cima a questa risposta per spiegare uno di loro, è lo stesso problema qui. Stai creando un nuovo lambda in cui l'istanza del parametro che hai creato qui non viene utilizzata nel corpo.

Il problema più grande è che le tue espressioni sono semplicemente sbagliate per ciò che sembra provi a fare. Per quanto ne so, stai solo provando a creare una mappatura da tipi di entità a funzioni che prendono un'entità di quel tipo e restituiscono un bool. Type -> Expression<Func<TEntity, bool>> . L'espressione che hai creato non funziona.

Dovresti fare in modo che il dizionario memorizzi i lambda non generici in questo modo, è possibile memorizzare facilmente queste funzioni senza eseguire conversioni o ricostruire le espressioni. Non sarai in grado di memorizzarli come lambda generici qui. Quindi lancia il lambda generico quando ti accedi. Lo metterei in una classe separata per gestire il casting e rifattorizzare il tuo codice a questo:

// add all necessary error checking where needed and methods
public class EntityPredicateDictionary
{
    private Dictionary<Type, LambdaExpression> dict = new Dictionary<Type, LambdaExpression>();

    public Expression<Func<TEntity, bool>> Predicate<TEntity>() where TEntity : Entity
    {
        return (Expression<Func<TEntity, bool>>)dict[typeof(TEntity)];
    }

    public LambdaExpression Predicate(Type entityType)
    {
        return dict[entityType];
    }

    internal void Add<TEntity>(Expression<Func<TEntity, bool>> predicate) where TEntity : Entity
    {
        dict.Add(typeof(TEntity), predicate);
    }
}

public class LoadEntityQuery : IQuery<LoadEntityQueryResult>
{
    public EntityPredicateDictionary Entities { get; private set; }
    public LoadEntityQuery()
    {
        Entities = new EntityPredicateDictionary();
    }

    public void Add<TEntity>(Expression<Func<TEntity, bool>> predicate = null) where TEntity : Entity
    {
        Entities.Add(predicate);
    }
}

// then to access the predicates
LoadEntityQuery query = ...;
var pred1 = query.Entities.Predicate<Entity1>();
var pred2 = query.Entities.Predicate(typeof(Entity2));

Risposta popolare

Non penso che questo farà ciò che ti aspetti che faccia; Func<Type, bool> definisce una funzione che accetta come parametro un tipo e restituisce un valore bool. Func<T, bool> definisce una funzione che accetta come parametro un oggetto di tipo T e restituisce un valore bool. Il lambda che è definito nel tuo dizionario non riceverà mai l'oggetto che stai cercando di filtrare, solo il suo tipo.

Per me il modo più rapido per rendere questo in qualsiasi modo appropriato sarebbe rendere la classe LoadEntityQuery generica sul tipo del parametro che si aspetta che la funzione accetti, ma che probabilmente limiterà in altri modi ...

Potresti usare un oggetto e lanciarlo ... Non è la soluzione migliore, ma almeno incapsula il casting ed è un pezzo abbastanza piccolo, pur continuando a permetterti di fare quello che mi sembra che tu debba fare.

public class LoadEntityQuery : IQuery<LoadEntityQueryResult>
{
    // Note: Hide this, we don't want anyone else to have to think about the cast.
    private IDictionary<Type, object> Entities { get; set; }

    public void Add<T>(Expression<Func<T, bool>> where = null) where T : Entity
    {
        Entities.Add(typeof(T), where);
    }

    public Expression<Func<T, bool>> Retrieve<T>() where T : Entity
    {
        if (!Entities.ContainsKey(typeof(T)))
            return null;
        return (Expression<Func<T, bool>>)Entities[typeof(T)];
    }
}


Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow
È legale questo KB? Sì, impara il perché
Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow
È legale questo KB? Sì, impara il perché