Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How merge expression in the Select method with Linq

I am trying to make a generic to be used on the Selectors components. It should provide a default pattern result which is based on a type we have made called SelectorViewModel that has an Id, a Description and a Code. Today we have a method that does it using the following query:

var result = Queryable.Where(x => .... )
                      .OrderBy(x => ... )
                      .Select(x => SelectorViewModel(x.Id, 
                                                     x.Name, 
                                                     x.Code))
                      .ToList();

It works fine, but we will have a lot of these methods. The question is, how to make the fields defined on the Select method to be possible to pass as parameter to the SelectorViewModel? For sample:

public IEnumerable<SelectorViewModel> Selector<T>(.. idExpression, .. descriptionExpression, .. codeExpression)
{
    var result = session.Query<T>
                        .Where(x => .... )
                        .OrderBy(x => ... )
                        .Select(x => SelectorViewModel(idExpression,  // id
                                                       descriptionExpression, // description
                                                       codeExpression /*code*/))
                        .ToList();

   return result;
}

I would like to have something like this on the use of this method:

var result = repository.Selector(x => x.Id, x => x.Name, x => x.Code);

And make these arguments be merged on the expression on the Select method. Is that possible?

Thank you.

like image 884
Felipe Oriani Avatar asked Mar 11 '23 06:03

Felipe Oriani


2 Answers

you need something like this:

public IEnumerable<SelectorViewModel> Selector<T>(
        Func<T, int> idExpression, 
        Func<T, string> descriptionExpression,
        Func<T, string> codeExpression)
    {
        var result = session.Query<T>
                 .Where(x => .... )
                 .OrderBy(x => ... )
                 .Select(x => new SelectorViewModel(idExpression(x), descriptionExpression(x), codeExpression(x)))
                 .ToList();

        return result;
    }

However I prefer something more like a "Factory method", so that you pass a factory that convert T to SelectorViewModel (something like this):

public class SelectorViewModel
{
    public static SelectorViewModel GetSelectorViewModel<T>(T input)
    {
        //create SelectorViewModel how do you prefer
        return new SelectorViewModel(input.Id, input.Description, input.Code);
    }

    public SelectorViewModel(int id, string description, int code)
    {
        // TODO
    }
}

public IEnumerable<SelectorViewModel> Selector<T>(Func<T, SelectorViewModel> factoryMethod)
{
     var result = session.Query<T>
                 .Where(x => .... )
                 .OrderBy(x => ... )
                 .Select(x => factoryMethod(x))
                 .ToList();
     return result;
}

this because (personally) I don't like to pass many functions, maybe complex such as:

x => x.property > 2 ? x.name.ToString() : (x.property < 0 ? x.description : x.description + "a")

and, at the end

  • the code is less readable
  • no/low support for debugging
  • long code line (length)

[ps: what I did above can be done with OOP (https://en.wikipedia.org/wiki/Factory_method_pattern)]

like image 174
Sierrodc Avatar answered Mar 12 '23 20:03

Sierrodc


From your samples it's not quite clear what query provider are you targeting (EF or NHibernate), so the following is an universal helper method which composes selector expression using prototype expression and small ExpressionVisitor based parameter replacer:

public static class QueryableExtensions
{
    public static IQueryable<SelectorViewModel> ToSelectorViewModel<T>(
        this IQueryable<T> source, 
        Expression<Func<T, long>> idSelector,
        Expression<Func<T, string>> descriptionSelector,
        Expression<Func<T, string>> codeSelector
    )
    {
        Expression<Func<long, string, string, SelectorViewModel>> prototype =
            (id, description, code) => new SelectorViewModel { Id = id, Description = description, Code = code };
        var parameter = Expression.Parameter(typeof(T), "e");
        var body = prototype.Body
            .ReplaceParameter(prototype.Parameters[0], idSelector.Body.ReplaceParameter(idSelector.Parameters[0], parameter))
            .ReplaceParameter(prototype.Parameters[1], descriptionSelector.Body.ReplaceParameter(descriptionSelector.Parameters[0], parameter))
            .ReplaceParameter(prototype.Parameters[2], codeSelector.Body.ReplaceParameter(codeSelector.Parameters[0], parameter));
        var selector = Expression.Lambda<Func<T, SelectorViewModel>>(body, parameter);
        return source.Select(selector);
    }
}

The expression helper used:

public static partial class ExpressionUtils
{
    public static Expression ReplaceParameter(this Expression expression, ParameterExpression source, Expression target)
    {
        return new ParameterReplacer { Source = source, Target = target }.Visit(expression);
    }

    class ParameterReplacer : ExpressionVisitor
    {
        public ParameterExpression Source;
        public Expression Target;
        protected override Expression VisitParameter(ParameterExpression node)
        {
            return node == Source ? Target : base.VisitParameter(node);
        }
    }
}

Now assuming your method receives the same idSelector, descriptionSelector and codeSelector arguments as above, the usage would be:

var result = Queryable.Where(x => .... )
                      .OrderBy(x => ... )
                      .ToSelectorViewModel(idSelector, descriptionSelector, codeSelector) 
                      .ToList();
like image 28
Ivan Stoev Avatar answered Mar 12 '23 18:03

Ivan Stoev