如何使用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。

  1. 创建您要使用的视图模型

     public class MyViewModelVM { public int Id { get; set; } public string MyFlattenedProperty { get; set; } } 
  2. 使用与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 
  3. 配置AutoMapper以将ORM视图类映射到viewmodel

     Mapper.CreateMap(); 
  4. 在您的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结果车。