mercredi 12 août 2020

IQueryable Join Using Reflection

I'm trying to join two IQueryables.
The first one is of type TSource (the only thing for sure in the type, it has a Guid Id property), it represents any entity on my system. The queryable is intended for multiple transactions <=> tables. It's working OK as it is, is used for filtering / ordering my grids on the view according to GridFilters/GridSorts coming from the view.
The second one is IQueryable, using a concrete class containing a property TransactionId (type Guid) and anothers called ValueName and Value (for simplicity). It represents the previous Id on TSource. The class is intended to add additional fields to the previous entities. It's working OK standalone, reading config from GridFilters/GridSorts.

So far, my join is

public IQueryable<TSource> JoinAdditionalField<TSource>(IQueryable<TSource> source, 
            IQueryable<TransactionValue> queryTransactionValue)
        {
            var typeSource = typeof(TSource);
            LambdaExpression sourceKeyExpression = GetPropertyAccesssLambdaExpr(typeSource , "Id");
            LambdaExpression additionalFieldKeyExpression = GetPropertyAccesssLambdaExpr(typeof(TransactionValue), "TransactionId");
            //var resultExpression1 = (query1, query2) => new {query1};  // Only if returning source data
            //var resultExpression2 = (query1, query2) => new {query1, query2};
            MethodInfo joinMethod = typeof(Queryable).GetMethods(BindingFlags.Static | BindingFlags.Public).
                First(m => m.Name == "Join" && m.GetParameters().Length == 5);
            var genericJoinMethod = joinMethod.MakeGenericMethod(typeSource, typeof(TransactionValue), 
                typeof(Guid), typeSource);
            var joined = genericJoinMethod.Invoke(source, new object[]
            {
                source, queryTransactionValue, sourceKeyExpression, additionalFieldKeyExpression, resultExpression 
            });

            return source;
        }


        private LambdaExpression GetPropertyAccesssLambdaExpr(Type type, string name)
        {
            PropertyInfo prop = type.GetProperty(name, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
            ParameterExpression param = Expression.Parameter(type);
            MemberExpression propAccess = Expression.Property(param, prop.Name);
            LambdaExpression expr = Expression.Lambda(propAccess, param);
            return expr;
        }

My problem now is how to define the resultExpression.
As a first step I'd like to return just the fields from TSource (resultExpression1) but effectively joining both tables. This is because my previous code is already filling my viewModel (containing properties from TransactionValue and TSource) at a later step, after the query is executed.

After that, the quest is to map a resultExpression2, so that
TransactionX
Id: A220B4CD-1C5A-4C51-9BF5-5BE43BE3B1F6
Field1: 1
Field2: "Hi"

TransactionValue
Id:
TransactionId: A220B4CD-1C5A-4C51-9BF5-5BE43BE3B1F6
ValueName: "Country"
Value: "Spain"

TransactionXViewModel, used in grid
Id: A220B4CD-1C5A-4C51-9BF5-5BE43BE3B1F6
Field1: 1
Field2: "Hi"
Country: "Spain"

In summary, if solving resultExpression1 I think I can live with it right now. For resultExpression2, I still need to think better how to assign the additional values to the VM in a generic way, ie, with reflection.

Thanks in advance for your ideas.





Aucun commentaire:

Enregistrer un commentaire