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.
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
[ps: what I did above can be done with OOP (https://en.wikipedia.org/wiki/Factory_method_pattern)]
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();
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