如何将 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<Car>
)运行 ToDataSourceResult()
,这些对象不具有 by 属性那个名字。映射发生在过滤和排序之后。
如果您希望在数据库中进行过滤和排序,则需要在 IQueryable 对数据库运行之前调用 .ToDataSourceResult()
。
如果您已经从数据库中提取了所有 Car
记录,那么您可以通过先进行映射来解决此问题,然后在 IQueryable<CarViewModel>
上调用 .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 上刷新“页脚”