I have a read model as IQueryable<CustomType>
, I use this inside my Web application. A lot of time I need to extract from this read model different View Model.
I use to write extension method like:
public static ViewModelA AsViewModelA(this IQueryable<CustomType> query)
{
var vm = view
.Select(x => new ViewModelA
{
Something = x.Something
}).FirstOrDefault();
return vm;
}
public static ViewModelB AsViewModelB(this IQueryable<CustomType> query)
{
var vm = view
.Select(x => new ViewModelB
{
SomethingElse = x.SomethingElse
}).FirstOrDefault();
return vm;
}
This do the job but I don't like the mess generated with method names; a more generic way, something like this would be preferable:
query.AsViewModel<ViewModelA>()
I know that return type is not intended as method signature (so no overload applies) and I know that generic type is not sufficient to make an overload. What I would is a mechanism to just simulate overloading based on generic type. This mechanism should avoid a main method with cascading if/then/else. There is a way? Maybe with dynamics?
One option is to have a map from the type to a conversion of CustomType
to that type. So it would look something like:
private static readonly Dictionary<Type, Expression> Mappings =
new Dictionary<Type, Expression>
{
{ typeof(ViewModelA),
Helper<ViewModelA>(x => new ViewModelA { Something = x.Something }) },
{ typeof(ViewModelB),
Helper<ViewModelB>(x => new ViewModelB { SomethingElse = x.SomethingElse }) },
...
}
// This method just helps avoid casting all over the place.
// In C# 6 you could use an expression-bodied member - or add a
private static Expression<Func<CustomType, T>> Helper<T>
(Expression<Func<CustomType, T>> expression)
{
return expression;
}
public static T AsViewModel<T>(this IQueryable<CustomType> query)
{
Expression rawMapping;
if (!Mappings.TryGetValue(typeof(T), out rawMapping))
{
throw new InvalidOperationException("Or another exception...");
}
// This will always be valid if we've set up the dictionary properly
var mapping = (Expression<Func<CustomType, T>>) rawMapping;
return view.Select(mapping).FirstOrDefault();
}
You can make the dictionary construction a bit cleaner with a bit more up-front code.
See more on this question at Stackoverflow