Linq projection on a ViewModel IQueryable extension
namespace: MvcControlsToolkit.Core.Linq
Business layer Linq queries usually fill a ViewModel that is then passed to the presentation Layer. Typically, a lot of time is spent manually copying data from the DB model into the ViewModel properties, i.e, in projecting the DB model into the ViewModel. Tools like automapper can't help in this task since they just perform in-memory copying of properties. Thus they may be used only after the whole model has been retrieved from the DB. On the contrary the manual Projection into the ViewModel simply creates an efficient DB query that retrieves just the DB fields needed by the ViewModel:
var finalData = await ctx.TestModels.Project().To<TestViewModel>().ToArrayAsync();
Mvc Controls Toolkit Linq Projection operator does automatically the same efficient job performed by the manual ViewModel projection. Properties are copied according to a name convenzion. The developer is required to specify just the ViewModel properties that need to be filled with a different logic:
var finalData = await ctx.TestModels.Project().To(m => new TestViewModel { FieldBC=m.FieldB+" "+m.FieldC }).ToArrayAsync();
Connected objects properties are copied automatically into ViewModel properties whose names are obtained by concatenating the name of the DB model property containing the connected object, and the of the connected object.
One may specify also a select clause containining conditional expressions without listing all properties that conform to the automatic-copy name convention, since they are inferred automatically also in this case:
m => m.Maintenance != null ? new ProductMaintenanceViewModelDetail { MaintenanceYearlyRate = (decimal)m.Maintenance.YearlyRate }: new ProductViewModelDetail { }
In the example above, a wider ViewModel is selected when the DB object contains a connected entity. All propeties are inferred automatically but one! The MaintenanceYearlyRate is specified notwithstanding it conforms with the automatic/copy name convention, because it needs a cast.
Data may be projected with the same name conventions and rules also in ViewModels contained in nested collections, as in the example below:
m => new ReferenceTypeWithChildren { AMonth = Month.FromDateTime(m.AMonth), ANMonth = Month.FromDateTime(m.ANMonth.Value), AWeek = Week.FromDateTime(m.AWeek), ANWeek = Week.FromDateTime(m.ANWeek.Value), Children = m.Children.Select(l => new NestedReferenceType {}) });
The overhead of the automatic creation of the Linq projection impacts just on the first execution of the query, thanks to a double caching technique.
var finalData = await ctx.TestModels.Project().To(m => new TestViewModel { FieldBC=m.FieldB+" "+m.FieldC }).ToArrayAsync();
public class TestViewModel { public int Id { get; set; } public string FieldA { get; set; } public string FieldB { get; set; } public string FieldBC { get; set; } public string FieldD { get; set; } }
public class TestModel { public int Id { get; set; } [MaxLength(64)] public string FieldA { get; set; } [MaxLength(64)] public string FieldB { get; set; } [MaxLength(64)] public string FieldC { get; set; } [MaxLength(64)] public string FieldD { get; set; } [MaxLength(64)] public string FieldE { get; set; } [MaxLength(64)] public string FieldF { get; set; } }