I'm trying to dynamically build some sql-queries depending on a given config to only query data needed:
When writing plain linq it would look like this:
var data = dbContext
.TableOne
.Select(t1 => new TableOneSelect
{
TableOneId = t1.TableOneId,
TableOneTableTwoReference = new[] { TableOne.FirstTableTwoReference.Invoke(t1) }
.Select(t2 => new TableTwoSelect
{
TableTowId = (Guid?)t2.TableTwoId,
// ... some more properties of t2
}).FirstOrDefault(),
// ... some more properties of t1
});
whereas TableOne.FirstTableTwoReference.Invoke(t1)
is defined
public static Expression<Func<TableOne, TableTwo>> FirstTableTwoReference => (t1) => t1.TableTwoReferences.FirstOrDefault();
Currently I have the following for building the TableOne-part dynamically:
public Expression<Func<TableOne, TableOneSelect>> Init(TableOneConfig cfg)
{
var memberBindings = new List<MemberBinding>();
var selectType = typeof(TableOneSelect);
var newExpression = Expression.New(selectType);
var theEntity = Expression.Parameter(typeof(TableOne), "t1");
// decide if the property is needed and add to the object-initializer
if (cfg.Select("TableOneId"))
memberBindings.Add(Expression.Bind(selectType.GetProperty("TableOneId"), Expression.Property(theEntity, nameof("TableOneId"))));
// ... check other properties of TableOneSelect depending on given config
var memberInit = Expression.MemberInit(newExpression, memberBindings);
return Expression.Lambda<Func<tblTournament, EventResourceSelect>>(memberInit, theEntity);
}
same for TableTwo
(different properties and different db-table).
This I can dynamically invoke like this
dbContext.TableOne.Select(t => TableOneHelper.Init(cfg).Invoke(t1));
whereas Invoke
is the one from LinqKit
.
But I get stuck with the inner part for the TableOneTableTwoReference
where I need to make an enumeration to call the Init
of TableTwoHelper
but I don't get the point how this can be achieved.
I guess Expression.NewArrayInit(typeof(TableTwo), ...)
would be step one. But I still get stuck in how to pass t1.TableTwoReferences.FirstOrDefault()
to this array calling the Select
on.
I guess
Expression.NewArrayInit(typeof(TableTwo), ...)
would be step one. But I still get stuck in how to passt1.TableTwoReferences.FirstOrDefault()
to this array calling theSelect
on.
As I understand, the question is what is the expression equivalent of
new[] { TableOne.FirstTableTwoReference.Invoke(t1) }
It's really simple. As you correctly stated, you'll need Expression.NewArrayInit
expression. However, since it expects params Expression[] initializers
, instead of LINQKit Invoke
extension method you should use Expression.Invoke
method to emit call to TableOne.FirstTableTwoReference
lambda expression with the outer theEntity
("t1") parameter:
var t2Array = Expression.NewArrayInit(
typeof(TableTwo),
Expression.Invoke(TableOne.FirstTableTwoReference, theEntity));
The same way you can emit the Select
expression:
var t2Selector = TableTwoHelper.Init(cfg2);
// t2Selector is Expression<Func<TableTwo, TableTwoSelect>>
var t2Select = Expression.Call(
typeof(Enumerable), "Select", new[] { t2Selector.Parameters[0].Type, t2Selector.Body.Type },
t2Array, t2Selector);
then FirstOrDefault
call:
var t2FirstOrDefault = Expression.Call(
typeof(Enumerable), "FirstOrDefault", new[] { t2Selector.Body.Type },
t2Select);
and finally the outer member binding:
memberBindings.Add(Expression.Bind(
selectType.GetProperty("TableOneTableTwoReference"),
t2FirstOrDefault));
This will produce the equivalent of your "plain linq" approach.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With