What is the best approach to load/filter/order a Kendo grid with the following classes:
Domain:
public class Car
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual bool IsActive { get; set; }
}
ViewModel
public class CarViewModel
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual string IsActiveText { get; set; }
}
AutoMapper
Mapper.CreateMap<Car, CarViewModel>()
.ForMember(dest => dest.IsActiveText,
src => src.MapFrom(m => m.IsActive ? "Yes" : "No"));
IQueryable
var domainList = RepositoryFactory.GetCarRepository().GetAllQueryable();
DataSourceResult
var dataSourceResult = domainList.ToDataSourceResult<Car, CarViewModel>(request,
domain => Mapper.Map<Car, ViewModel>(domain));
Grid
...Kendo()
.Grid<CarViewModel>()
.Name("gridCars")
.Columns(columns =>
{
columns.Bound(c => c.Name);
columns.Bound(c => c.IsActiveText);
})
.DataSource(dataSource => dataSource
.Ajax()
.Read(read => read.Action("ListGrid", "CarsController"))
)
.Sortable()
.Pageable(p => p.PageSizes(true))
Ok, the grid loads perfectly for the first time, but when I filter/order by IsActiveText
I get the following message:
Invalid property or field - 'IsActiveText' for type: Car
What is the best approach in this scenario?
One good way to solve it if you use Telerik Data Access or any other IQueryable enabled interface/ORM over your data, is to create views directly in your database RDBMS that map one-to-one (with automapper) to your viewmodel.
Create the viewmodel you wish to use
Create a view in your SQL Server (or whatever RDBMS you're working with) with columns exactly matching the viewmodel property names, and of course build your view to query the correct tables. Make sure you include this view in your ORM classes
Configure AutoMapper to map your ORM view class to your viewmodel
In your Kendo grid Read action, use the view to build your query, and project the ToDataSourceQueryResult using Automapper
It's a bit of overhead but it will help you in achieve performance on two levels when working with large datasets:
František's solution is very nice! But be careful with casting Filters to FilterDescriptor. Some of them can be composite.
Use this implementation of DataSourceRequestExtensions instead of František's:
I came across this same issue and after lots of research I resolved it permanently by using AutoMapper.QueryableExtensions library. It has an extension method that will project your entity query to your model and after that you can apply ToDataSourceResult extension method on your projected model.
Remember to configure Automapper using CreateMap.
Note: Here getCars will return IQueryable result car.
I don't like the way Kendo has implemented "DataSourceRequestAttribute" and "DataSourceRequestModelBinder", but thats another story.
To be able to filter/sort by VM properties which are "flattened" objects, try this:
Domain model:
View model:
Extensions:
Attributes:
Controller action for Ajax data load:
I followed the suggestion of CodingWithSpike and it works. I created an extension method for the DataSourceRequest class:
Then in your controller, add the
using
to the extension class and before the call to ToDataSourceResult(), add this:Something about that seems weird. You told Kendo UI to make a grid for
CarViewModel
and told it there is an
IsActive
column:but
CarViewModel
doesn't have a column by that name:My guess is that Kendo is passing up the field name from the CarViewModel
IsActiveText
, but on the server you are runningToDataSourceResult()
againstCar
objects (anIQueryable<Car>
), which do not have a property by that name. The mapping happens after the filtering & sorting.If you want the filtering and sorting to happen in the database, then you would need to call
.ToDataSourceResult()
on the IQueryable before it runs against the DB.If you have already fetched all your
Car
records out of the DB, then you can fix this by doing your mapping first, then calling.ToDataSourceResult()
on anIQueryable<CarViewModel>
.