Costruisci selezione dinamica usando gli alberi di espressione

c# entity-framework expression-trees linq-to-entities webforms

Domanda

Come potrei generare il seguente usando gli alberi di espressione ...

var people = context.Set<Person>();
var transactions = context.Set<FinancialTransaction>();

var dataview = people.Where( p => p.LastName == "Smith" );

var selection = dataview
        .Select( p => new
        {
            FirstName = p.FirstName,
            LastName = p.LastName,
            LastTransaction =
                transactions
                    .Where( t => t.AuthorizedPersonId == p.Id )
                    .Max( t => t.TransactionDateTime )
        } );

gReport.AutoGenerateColumns = true;
gReport.DataSource = selection.ToList();
gReport.DataBind();

Sto cercando di utilizzare la soluzione LinqRuntimeTypeBuilder fornita da Ethan Brown qui, ma alle prese con il modo di creare l'espressione per la sottotabella LastTransaction e come associare la query a GridView.

Questo è quello che ho finora ...

var people = context.Set<Person>();
var transactions = context.Set<FinancialTransaction>();

var dataview = people.Where( p => p.LastName == "Smith" );

var dynamicFields = new Dictionary<string, Type>();
dynamicFields.Add( "FirstName", typeof( string ) );
dynamicFields.Add( "LastName", typeof( string ) );
dynamicFields.Add( "LastTransaction", typeof( DateTime? ) );

Type dynamicType = Rock.Data.LinqRuntimeTypeBuilder.GetDynamicType( dynamicFields );

ParameterExpression sourceItem = Expression.Parameter( dataview.ElementType, "x" );

// Is this right? if if so how do I bind it to the dynamic field????
Expression<Func<Person, DateTime>> lastTransactionSelect = a => transactions.Where( t => t.AuthorizedPersonId == a.Id && t.TransactionDateTime.HasValue ).Max( t => t.TransactionDateTime.Value );

var bindings = new List<MemberBinding>();
bindings.Add( Expression.Bind( dynamicType.GetField( "FirstName" ), Expression.Property( sourceItem, dataview.ElementType.GetProperty( "FirstName" ) ) ) );
bindings.Add( Expression.Bind( dynamicType.GetField( "LastName" ), Expression.Property( sourceItem, dataview.ElementType.GetProperty( "LastName" ) ) ) );
bindings.Add( Expression.Bind( dynamicType.GetField( "LastTransaction" ), ??? ) );

Expression selector = Expression.Lambda( Expression.MemberInit( Expression.New( dynamicType.GetConstructor( Type.EmptyTypes ) ), bindings ), sourceItem );

var query = dataview.Provider.CreateQuery(
    Expression.Call(
        typeof( Queryable ),
        "Select",
        new Type[] { dataview.ElementType, dynamicType },
    Expression.Constant( dataview ), selector ) ).AsNoTracking();

// Can't bind directly to the query since it's a DBQuery object
gReport.DataSource = ???;

gReport.DataBind();

Come posso creare l'espressione per la sotto-query, e poi anche qual è il modo migliore per associare la query a GridView?

Risposta accettata

Dopo aver usato Reflector per valutare come il compilatore ha generato l'istruzione linq, ecco come ho finito per creare l'espressione per la sotto-selezione ...

ParameterExpression transactionParameter = Expression.Parameter(typeof(FinancialTransaction), "t");
MemberExpression authorizedPersonIdProperty = Expression.Property(transactionParameter, "AuthorizedPersonId");
MemberExpression transactionDateTime = Expression.Property(transactionParameter,"TransactionDateTime");

MethodInfo whereMethod = GetWhereMethod();
MethodInfo maxMethod = GetMaxMethod();

var personIdCompare = new Expression[] { 
    Expression.Constant(transactions), 
    Expression.Lambda<Func<FinancialTransaction, bool>>( Expression.Equal(authorizedPersonIdProperty, Expression.Convert(idProperty, typeof(int?))), new ParameterExpression[] { transactionParameter } ) 
};
var transactionDate = Expression.Lambda<Func<FinancialTransaction, DateTime?>>( transactionDateTime, new ParameterExpression[] { transactionParameter } );
var lastTransactionDate = Expression.Call( null, maxMethod, new Expression[] { Expression.Call( null, whereMethod, personIdCompare ), transactionDate } );

...

bindings.Add( Expression.Bind( dynamicType.GetField( "LastTransaction" ), lastTransactionDate ) );


...


private MethodInfo GetWhereMethod()
{
    Func<FinancialTransaction, bool> fake = element => default( bool );
    Expression<Func<IEnumerable<FinancialTransaction>, IEnumerable<FinancialTransaction>>> lamda = list => list.Where( fake );
    return ( lamda.Body as MethodCallExpression ).Method;
}

private MethodInfo GetMaxMethod()
{
    Func<FinancialTransaction, DateTime?> fake = element => default( DateTime? );
    Expression<Func<IEnumerable<FinancialTransaction>, DateTime?>> lamda = list => list.Max( fake );
    return ( lamda.Body as MethodCallExpression ).Method;
}

Risposta popolare

So che la tua domanda principale riguarda la costruzione di un albero di espressioni linq dinamico, ma potrei essere in grado di aiutarti con la tua domanda secondaria sull'associazione di Queryable alla griglia.

EDIT: Scusa, ho effettivamente provato questo e facendo OfType <object> ha generato un'eccezione Casting, quindi ecco qualcosa che funzionerà davvero

var query = dataview.Provider.CreateQuery(
    Expression.Call(
        typeof( Queryable ),
        "Select",
        new Type[] { dataview.ElementType, dynamicType },
    Expression.Constant( dataview ), selector ) ).AsNoTracking();

// enumerate thru the query results and put into a list
var listResult = new List<object>();
var enumerator = query.GetEnumerator();
while ( enumerator.MoveNext() )
{
    reportResult.Add( enumerator.Current );
}

gReport.DataSource = listResult;

gReport.DataBind();


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é