Expresión lambda al árbol de Expression <T> (Slice 2 - Join)

Sep 10 2020

¿Cómo se pone un elefante en un frigorífico? Lo corta ... Entonces, ¿cómo se traduce un árbol de consulta lambda a expresión? Lo cortas ...

Ésta es una serie de 3 cortes de expresión lambda al árbol de expresión.

Rebanada 1. Aquí , Cómo hacer .join () usando el ejemplo de Microsoft.

Rebanada 2 (esta porción). Carga ansiosa con parámetros diferentes a los usados ​​en Slice 1.

Rebanada 3. Aquí , Cómo finalizar la consulta con .where () y .Select ().

En este segmento, estoy tratando de convertir la siguiente consulta a la sintaxis del árbol de expresiones:

IQueryable<A> As = db.A
                     .Join(
                           db.B,
                           _a => _a.bID,
                           _b => _b.ID,
                           (a, b) => new { a, b })
                     //The next two statements are going to next slice.
                     .Where(s=> s.b.Name == "xpto")
                     .Select(s => s.a);

En la rebanada 1 tuve:

Ejemplo anterior de transformación:

var query = people.AsQueryable().Join(pets,
                person => person,
                pet => pet.Owner,
                (person, pet) =>
                    new { OwnerName = person.Name, Pet = pet.Name });

Eso resultó en la respuesta de @NetMage, que hizo el truco con:

// Build Queryable.Join<TOuter,TInner,TKey,TResult> and use as query expression

// IQueryable<TOuter>
var arg0 = Expression.Constant(people.AsQueryable());

// IEnumerable<TInner>
var arg1 = Expression.Constant(pets);

// TOuter person
var arg2p = Expression.Parameter(people.GetType().GetGenericArguments()[0], "person");
// also TKey person
// Expression<Func<TOuter,TKey>>: person => person
var arg2 = Expression.Quote(Expression.Lambda(arg2p, arg2p));

// TInner pet
var arg3p = Expression.Parameter(pets.GetType().GetGenericArguments()[0], "pet");
// TKey pet.Owner
var arg3body = Expression.Property(arg3p, "Owner");
// Expression<Func<TInner,TKey>>: pet => pet.Owner
var arg3 = Expression.Quote(Expression.Lambda(arg3body, arg3p));

// TResult = typeof(new { string OwnerName , string Pet })
var anonymousType = (new { OwnerName = default(string), Pet = default(string) }).GetType();
// .ctor
var arg4Constructor = anonymousType.GetConstructors()[0];
// person.Name
var arg4PersonName = Expression.Property(arg2p, "Name");
// pet.Name
var arg4PetName = Expression.Property(arg3p, "Name");
var arg4Args = new[] { arg4PersonName, arg4PetName };
// new[] { .OwnerName, .Pet }
var arg4Members = anonymousType.GetProperties();
// new { OwnerName = person.Name, Pet = pet.Name }
var arg4body = Expression.New(arg4Constructor, arg4Args, arg4Members);
// Expression<Func<TOuter,TInner,TResult>>: (person,pet) => new { OwnerName = person.Name, Pet = pet.Name }
var arg4 = Expression.Quote(Expression.Lambda(arg4body, arg2p, arg3p));

var joinGenericMI = typeof(Queryable).GetMethod("Join", 5);
var joinMI = joinGenericMI.MakeGenericMethod(new[] { arg2p.Type, arg3p.Type, arg2.ReturnType, anonymousType });
var qExpr = Expression.Call(joinMI, arg0, arg1, arg2, arg3, arg4);

Para este segmento, era necesario modificar la siguiente declaración, presentada en el segmento anterior:

// TResult = typeof(new { string OwnerName , string Pet })
    var anonymousType = (new { OwnerName = default(string), Pet = default(string) }).GetType();

@NetMage lo adaptó a los genéricos A y B:

// TResult = typeof(new { A , B })
var anonymousType = (new { A = db.A.FirstOrDefault(), db.B = B.FirstOrDefault() }).GetType();

y

var arg4Constructor = anonymousType.GetConstructors()[0];
// person.Name
var arg4PersonName = Expression.Property(arg2p, "Name");
// pet.Name
var arg4PetName = Expression.Property(arg3p, "Name");

se convirtió (adaptado a los genéricos A y B):

// object A
var arg4A = arg2p;
// object B
var arg4B = arg3p;

Ahora es necesario filtrar el resultado por b.Name y Select a (next Slice).

Respuestas

NetMage Sep 12 2020 at 03:44

Para obtener el tipo de retorno del Joinmétodo, haga esto:

// TResult = typeof(new { A , B })
var anonymousType = (new { A = db.A.FirstOrDefault(), db.B = B.FirstOrDefault() }).GetType();

Debe crear un tipo anónimo donde el miembro Atenga un tipo de db.Aentidad. Si usa eg typeof(A), entonces tendrá Acon type Type, completamente ajeno al Join. (Si conociera el tipo de entidad de A, podría usarlo defaultcomo lo hice yo: por ejemplo, si db.Ase DbSet<AClass>usa entonces A = default(AClass)).

Como no está haciendo referencia a ninguna propiedad en el Joinresultado, simplemente use los parámetros:

// object A
var arg4A = arg2p;
// object B
var arg4B = arg3p;