如何使用Kendo UI Grid与ToDataSourceResult(),IQueryable ,ViewModel和AutoMapper?
使用以下类加载/过滤/订购Kendo网格的最佳方法是什么:
域:
public class Car { public virtual int Id { get; set; } public virtual string Name { get; set; } public virtual bool IsActive { get; set; } }
视图模型
public class CarViewModel { public virtual int Id { get; set; } public virtual string Name { get; set; } public virtual string IsActiveText { get; set; } }
AutoMapper
Mapper.CreateMap() .ForMember(dest => dest.IsActiveText, src => src.MapFrom(m => m.IsActive ? "Yes" : "No"));
IQueryable的
var domainList = RepositoryFactory.GetCarRepository().GetAllQueryable();
DataSourceResult
var dataSourceResult = domainList.ToDataSourceResult(request, domain => Mapper.Map(domain));
格
...Kendo() .Grid() .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))
好的,第一次网格加载完美,但是当我通过IsActiveText
过滤/订购时,我收到以下消息:
无效的属性或字段 – 类型为“IsActiveText”:Car
这种情况下最好的方法是什么?
关于这一点似乎很奇怪。 您告诉Kendo UI为CarViewModel
制作网格
.Grid()
并告诉它有一个IsActive
专栏:
columns.Bound(c => c.IsActive);
但CarViewModel
没有该名称的列:
public class CarViewModel { public virtual int Id { get; set; } public virtual string Name { get; set; } public virtual string IsActiveText { get; set; } }
我的猜测是,Kendo从CarViewModel IsActiveText
传递了字段名称,但是在服务器上运行ToDataSourceResult()
对Car
对象( IQueryable
),它没有该名称的属性。 映射在过滤和排序后发生。
如果要在数据库中进行过滤和排序,则需要在IQueryable上对DB运行之前调用.ToDataSourceResult()
。
如果您已经从数据库中提取了所有Car
记录,那么您可以通过先执行映射,然后在IQueryable
上调用.ToDataSourceResult()
来解决此问题。
我不喜欢Kendo实现“DataSourceRequestAttribute”和“DataSourceRequestModelBinder”的方式,但这是另一个故事。
为了能够按照“展平”对象的VM属性进行筛选/排序,请尝试以下操作:
领域模型:
public class Administrator { public int Id { get; set; } public int UserId { get; set; } public virtual User User { get; set; } } public class User { public int Id { get; set; } public string UserName { get; set; } public string Email { get; set; } }
查看型号:
public class AdministratorGridItemViewModel { public int Id { get; set; } [Displaye(Name = "E-mail")] public string User_Email { get; set; } [Display(Name = "Username")] public string User_UserName { get; set; } }
扩展:
public static class DataSourceRequestExtensions { /// /// Enable flattened properties in the ViewModel to be used in DataSource. /// public static void Deflatten(this DataSourceRequest dataSourceRequest) { foreach (var filterDescriptor in dataSourceRequest.Filters.Cast()) { filterDescriptor.Member = DeflattenString(filterDescriptor.Member); } foreach (var sortDescriptor in dataSourceRequest.Sorts) { sortDescriptor.Member = DeflattenString(sortDescriptor.Member); } } private static string DeflattenString(string source) { return source.Replace('_', '.'); } }
属性:
[AttributeUsage(AttributeTargets.Method)] public class KendoGridAttribute : ActionFilterAttribute { public override void OnActionExecuting(ActionExecutingContext filterContext) { base.OnActionExecuting(filterContext); foreach (var sataSourceRequest in filterContext.ActionParameters.Values.Where(x => x is DataSourceRequest).Cast()) { sataSourceRequest.Deflatten(); } } }
Ajax数据加载的控制器操作:
[KendoGrid] public virtual JsonResult AdministratorsLoad([DataSourceRequestAttribute]DataSourceRequest request) { var administrators = this._administartorRepository.Table; var result = administrators.ToDataSourceResult( request, data => new AdministratorGridItemViewModel { Id = data.Id, User_Email = data.User.Email, User_UserName = data.User.UserName, }); return this.Json(result); }
我遵循CodingWithSpike的建议,它的工作原理。 我为DataSourceRequest类创建了一个扩展方法:
public static class DataSourceRequestExtensions { /// /// Finds a Filter Member with the "memberName" name and renames it for "newMemberName". /// /// The DataSourceRequest instance. /// The Name of the Filter to be renamed. /// The New Name of the Filter. public static void RenameRequestFilterMember(this DataSourceRequest request, string memberName, string newMemberName) { foreach (var filter in request.Filters) { var descriptor = filter as Kendo.Mvc.FilterDescriptor; if (descriptor.Member.Equals(memberName)) { descriptor.Member = newMemberName; } } } }
然后在你的控制器中,将using
添加到扩展类,并在调用ToDataSourceResult()之前添加:
request.RenameRequestFilterMember("IsActiveText", "IsActive");
František的解决方案非常好! 但要小心将filter转换为FilterDescriptor。 其中一些可以是复合的。
使用DataSourceRequestExtensions的这个实现而不是František的:
public static class DataSourceRequestExtensions { /// /// Enable flattened properties in the ViewModel to be used in DataSource. /// public static void Deflatten(this DataSourceRequest dataSourceRequest) { DeflattenFilters(dataSourceRequest.Filters); foreach (var sortDescriptor in dataSourceRequest.Sorts) { sortDescriptor.Member = DeflattenString(sortDescriptor.Member); } } private static void DeflattenFilters(IList filters) { foreach (var filterDescriptor in filters) { if (filterDescriptor is CompositeFilterDescriptor) { var descriptors = (filterDescriptor as CompositeFilterDescriptor).FilterDescriptors; DeflattenFilters(descriptors); } else { var filter = filterDescriptor as FilterDescriptor; filter.Member = DeflattenString(filter.Member); } } } private static string DeflattenString(string source) { return source.Replace('_', '.'); } }
如果在数据上使用Telerik Data Access或任何其他IQueryable启用的接口/ ORM,解决此问题的一种好方法是直接在数据库RDBMS中创建视图,该视图将一对一(使用automapper)映射到viewmodel。
-
创建您要使用的视图模型
public class MyViewModelVM { public int Id { get; set; } public string MyFlattenedProperty { get; set; } }
-
使用与viewmodel属性名称完全匹配的列在SQL Server(或您正在使用的任何RDBMS)中创建视图,当然还要构建视图以查询正确的表。 确保在ORM类中包含此视图
CREATE VIEW MyDatabaseView AS SELECT t1.T1ID as Id, t2.T2SomeColumn as MyFlattenedProperty FROM MyTable1 t1 INNER JOIN MyTable2 t2 on t2.ForeignKeyToT1 = t1.PrimaryKey
-
配置AutoMapper以将ORM视图类映射到viewmodel
Mapper.CreateMap
(); -
在您的Kendo网格读取操作中,使用视图构建查询,并使用Automapper投影ToDataSourceQueryResult
public ActionResult Read([DataSourceRequest]DataSourceRequest request) { if (ModelState.IsValid) { var dbViewQuery = context.MyDatabaseView; var result = dbViewQuery.ToDataSourceResult(request, r => Mapper.Map
(r)); return Json(result); } return Json(new List ().ToDataSourceResult(request)); }
这有点开销,但它可以帮助您在处理大型数据集时实现两个级别的性能:
- 您正在使用可以自行调整的本机RDBMS视图。 将始终优于您在.NET中构建的复杂LINQ查询
- 您可以利用Telerik ToDataSourceResult的优势,包括过滤,分组,聚合,……
我遇到了同样的问题,经过大量研究后,我使用AutoMapper.QueryableExtensions库永久解决了这个问题。 它有一个扩展方法,可以将实体查询投影到模型中,然后可以在投影模型上应用ToDataSourceResult扩展方法。
public ActionResult GetData([DataSourceRequest]DataSourceRequest request) { IQueryable entity = getCars().ProjectTo (); var response = entity.ToDataSourceResult(request); return Json(response,JsonRequestBehavior.AllowGet); }
请记住使用CreateMap配置Automapper。
注意:这里getCars将返回IQueryable结果车。