如何将 Kendo UI Grid 与 ToDataSourceResult()、IQueryable<T>、ViewModel 和 AutoMapper 一起使用?

Posted

技术标签:

【中文标题】如何将 Kendo UI Grid 与 ToDataSourceResult()、IQueryable<T>、ViewModel 和 AutoMapper 一起使用?【英文标题】:How to use Kendo UI Grid with ToDataSourceResult(), IQueryable<T>, ViewModel and AutoMapper? 【发布时间】:2013-05-05 06:22:15 【问题描述】:

什么是负载/滤波器的最佳方法/订购剑道网格以下类:

域强>

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<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));

网格强>

...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))

确定,网格负载完美首次,但是当我滤波器/以便通过IsActiveText我得到以下信息:

无效的属性或字段 - 'IsActiveText' 的类型:车载 P>

什么是在这种情况下,最好的办法? P>

【问题讨论】:

什么是初始化网格中的代码跨度> 【参考方案1】:

我不喜欢 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

    /// <summary>
    /// Enable flattened properties in the ViewModel to be used in DataSource.
    /// </summary>
    public static void Deflatten(this DataSourceRequest dataSourceRequest)
    
        foreach (var filterDescriptor in dataSourceRequest.Filters.Cast<FilterDescriptor>())
        
            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<DataSourceRequest>())
        
            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);
    

【讨论】:

不错的解决方案。在看到您的解决方案之前,我尝试在我的依赖属性中使用 '_' 并认为 kendo 将“当然”绑定属性,但它没有。我想知道为什么他们没有实现这个逻辑.. 绝对精彩,与 AutoMapper 一起使用:***.com/a/1630696/260556 有点困惑...这只是映射所需对象的工作,但是排序/过滤呢?我可能遗漏了一些东西 - 但如果我尝试对列(映射到 VM)进行排序,则会引发错误,因为 DataSourceRequest 在模型上找不到字段名称。 这里是 CompositeFilterDescriptor 使用的扩展版本:pastebin.com/RVkuwhSS【参考方案2】:

这似乎有些奇怪。你告诉 Kendo UI 为CarViewModel 制作一个网格

.Grid<CarViewModel>()

并告诉它有一个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 的字段名称,但在服务器上,您正在针对 Car 对象(IQueryable&lt;Car&gt;)运行 ToDataSourceResult(),这些对象不具有 by 属性那个名字。映射发生在过滤和排序之后。

如果您希望在数据库中进行过滤和排序,则需要在 IQueryable 对数据库运行之前调用 .ToDataSourceResult()

如果您已经从数据库中提取了所有 Car 记录,那么您可以通过先进行映射来解决此问题,然后在 IQueryable&lt;CarViewModel&gt; 上调用 .ToDataSourceResult()

【讨论】:

我编辑了主题,因为我在编写网格代码时犯了一个错误。我实际上是在显示 IsActiveText 属性。我只是想知道是否有另一种方法可以使用 AutoMapper 针对 NHibernate 和 ToDataSourceResult() 生成 GetAllQueryable() 而无需将所有实体加载到内存中。 .ToDataSourceResult() 正在向调用它的 Linq 可查询对象添加表达式。如果该可查询对象尚未对 DB 执行,则过滤和排序将应用于 DB 语句。 实际上现在我再次查看您的类型,针对 DB 过滤 IsActive 将很困难,因为您将 Car.IsActive 定义为 bit,它不是 javascript 类型。 boolean 会更好,但即便如此它也不匹配数据库中的 string。如果您希望IsActive 使用ToDataSourceResult 自动过滤数据库,那么您应该将其设为字符串。否则,您将不得不自己编写一些代码来将bit 转换为适当的string 我编辑了主题(再次!)。 IsActive 是一个布尔类型。我的观点是,如果 ToDataSourceResult 可以使用 AutoMapper Map 将 IsActiveText 过滤器转换为 IsActive DB 列的“反向路径”? 不,Kendo 默认不会这样做,因为它不知道 AutoMapper 会做什么。您也许可以在 DB 调用之前更改 request 对象,并将 IsActiveText 更改为 IsActive,但这将是您的手动操作。【参考方案3】:

František 的解决方案非常好!但要小心将过滤器转换为过滤器描述符。其中一些可以是复合的。

使用此 DataSourceRequestExtensions 实现而不是 František 的实现:

public static class DataSourceRequestExtensions

    /// <summary>
    /// Enable flattened properties in the ViewModel to be used in DataSource.
    /// </summary>
    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<IFilterDescriptor> 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('_', '.');
    

【讨论】:

这是正确答案!我调整了您的解决方案,用数据库模型的属性替换来自 ViewModel 的 POST 的成员。谢谢!【参考方案4】:

我听从了 CodingWithSpike 的建议,它确实有效。我为 DataSourceRequest 类创建了一个扩展方法:

public static class DataSourceRequestExtensions
    
        /// <summary>
        /// Finds a Filter Member with the "memberName" name and renames it for "newMemberName".
        /// </summary>
        /// <param name="request">The DataSourceRequest instance. <see cref="Kendo.Mvc.UI.DataSourceRequest"/></param>
        /// <param name="memberName">The Name of the Filter to be renamed.</param>
        /// <param name="newMemberName">The New Name of the Filter.</param>
        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");

【讨论】:

正如其他答案中提到的,如果您有CompositeFilterDescriptor,请小心 这个答案是黄金。我花了一段时间才找到一个像样的解决方案(Telerik 网站失败了)。我不得不添加代码来解析复合过滤器和排序,但它现在就像一个魅力。【参考方案5】:

如果您在数据上使用 Telerik 数据访问或任何其他启用 IQueryable 的接口/ORM,解决此问题的一个好方法是直接在数据库 RDBMS 中创建视图,将一对一(使用自动映射器)映射到您的视图模型.

    创建您想要使用的视图模型

    public class MyViewModelVM
    
        public int Id  get; set; 
        public string MyFlattenedProperty  get; set; 
    
    

    在您的 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 视图类映射到您的视图模型

    Mapper.CreateMap<MyDatabaseView, MyViewModelVM>();
    

    在您的 Kendo 网格读取操作中,使用视图构建您的查询,并使用 Automapper 投影 ToDataSourceQueryResult

    public ActionResult Read([DataSourceRequest]DataSourceRequest request)
    
        if (ModelState.IsValid)
        
            var dbViewQuery = context.MyDatabaseView;
    
            var result = dbViewQuery.ToDataSourceResult(request, r => Mapper.Map<MyViewModelVM>(r));
    
            return Json(result);
        
    
        return Json(new List<MyViewModelVM>().ToDataSourceResult(request));
    
    

这有点开销,但它可以帮助您在处理大型数据集时在两个级别上实现性能:

您正在使用可以自行调整的本机 RDBMS 视图。将始终胜过您在 .NET 中构建的复杂 LINQ 查询 您可以利用 Telerik ToDataSourceResult 的过滤、分组、聚合等优势...

【讨论】:

【参考方案6】:

我遇到了同样的问题,经过大量研究后,我使用 AutoMapper.QueryableExtensions 库永久解决了这个问题。它有一个扩展方法,可以将您的实体查询投影到您的模型中,然后您可以在投影模型上应用 ToDataSourceResult 扩展方法。

public ActionResult GetData([DataSourceRequest]DataSourceRequest request)

     IQueryable<CarModel> entity = getCars().ProjectTo<CarModel>();
     var response = entity.ToDataSourceResult(request);
     return Json(response,JsonRequestBehavior.AllowGet);

记得使用 CreateMap 配置 Automapper。

注意:这里 getCars 将返回 IQueryable 结果汽车。

【讨论】:

以上是关于如何将 Kendo UI Grid 与 ToDataSourceResult()、IQueryable<T>、ViewModel 和 AutoMapper 一起使用?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 kendo.ui.grid 中创建自定义 kendo.ui.Window 以进行编辑

Kendo UI 将 DropDownList 添加到 Grid (MVC)

如何使用 JQuery 在 Kendo Ui Grid 上刷新“页脚”

如何使用 Kendo UI Grid 的 SetDataSource 方法

如何提高kendo ui grid在页面的渲染速度

更新 ViewModel 时如何防止 Kendo UI Grid 多次重新绑定