C# 如何在 ToDataSourceResult()、IQueryable<T>、ViewModel 和 AutoMapper 中使用 Kendo UI 网格?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/16484417/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to use Kendo UI Grid with ToDataSourceResult(), IQueryable<T>, ViewModel and AutoMapper?
提问by rGiosa
What is the best approach to load/filter/order a Kendo grid with the following classes:
使用以下类加载/过滤/订购 Kendo 网格的最佳方法是什么:
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 IsActiveTextI get the following message:
好的,网格第一次完美加载,但是当我过滤/排序时,IsActiveText我收到以下消息:
Invalid property or field - 'IsActiveText' for type: Car
无效的属性或字段 - 类型为“IsActiveText”:汽车
What is the best approach in this scenario?
在这种情况下最好的方法是什么?
采纳答案by CodingWithSpike
Something about that seems weird. You told Kendo UI to make a grid for CarViewModel
关于那个的东西似乎很奇怪。你告诉 Kendo UI 为CarViewModel
.Grid<CarViewModel>()
and told it there is an IsActivecolumn:
并告诉它有一个IsActive专栏:
columns.Bound(c => c.IsActive);
but CarViewModeldoesn't have a column by that name:
但CarViewModel没有该名称的列:
public class CarViewModel
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual string IsActiveText { get; set; }
}
My guess is that Kendo is passing up the field name from the CarViewModel IsActiveText, but on the server you are running ToDataSourceResult()against Carobjects (an IQueryable<Car>), which do not have a property by that name. The mapping happens after the filtering & sorting.
我的猜测是 Kendo 正在传递 CarViewModel 中的字段名称IsActiveText,但在服务器上您正在ToDataSourceResult()针对Car对象(an IQueryable<Car>)运行,这些对象没有该名称的属性。映射发生在过滤和排序之后。
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.
如果您希望在数据库中进行过滤和排序,那么您需要在对数据库.ToDataSourceResult()运行之前调用IQueryable。
If you have already fetched all your Carrecords out of the DB, then you can fix this by doing your mapping first, then calling .ToDataSourceResult()on an IQueryable<CarViewModel>.
如果您已经获取所有的Car记录了数据库,那么你可以先做你的映射,然后调用解决这个问题.ToDataSourceResult()上IQueryable<CarViewModel>。
回答by Skorunka Franti?ek
I don't like the way Kendo has implemented "DataSourceRequestAttribute" and "DataSourceRequestModelBinder", but thats another story.
我不喜欢 Kendo 实现“DataSourceRequestAttribute”和“DataSourceRequestModelBinder”的方式,但那是另一回事了。
To be able to filter/sort by VM properties which are "flattened" objects, try this:
为了能够按“扁平化”对象的 VM 属性进行过滤/排序,请尝试以下操作:
Domain model:
领域模型:
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; }
}
View model:
查看型号:
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; }
}
Extensions:
扩展:
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('_', '.');
}
}
Attributes:
属性:
[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();
}
}
}
Controller action for Ajax data load:
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);
}
回答by Neshta
Franti?ek's solution is very nice! But be careful with casting Filters to FilterDescriptor. Some of them can be composite.
Franti?ek 的解决方案非常好!但是在将过滤器转换为 FilterDescriptor 时要小心。其中一些可以是复合的。
Use this implementation of DataSourceRequestExtensions instead of Franti?ek's:
使用 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('_', '.');
}
}
回答by tjeuten
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.
如果您对数据使用 Telerik Data Access 或任何其他支持 IQueryable 的接口/ORM,解决它的一种好方法是直接在您的数据库 RDBMS 中创建视图,将一对一(使用自动映射器)映射到您的视图模型。
Create the viewmodel you wish to use
public class MyViewModelVM { public int Id { get; set; } public string MyFlattenedProperty { get; set; } }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
CREATE VIEW MyDatabaseView AS SELECT t1.T1ID as Id, t2.T2SomeColumn as MyFlattenedProperty FROM MyTable1 t1 INNER JOIN MyTable2 t2 on t2.ForeignKeyToT1 = t1.PrimaryKeyConfigure AutoMapper to map your ORM view class to your viewmodel
Mapper.CreateMap<MyDatabaseView, MyViewModelVM>();In your Kendo grid Read action, use the view to build your query, and project the ToDataSourceQueryResult using Automapper
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)); }
创建您要使用的视图模型
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)); }
It's a bit of overhead but it will help you in achieve performance on two levels when working with large datasets:
这有点开销,但它会帮助您在处理大型数据集时在两个级别上实现性能:
- You are using native RDBMS views which you can tune yourself. Will always outperform complex LINQ queries you build in .NET
- You can leverage the Telerik ToDataSourceResult benefits of filtering, grouping, aggregating, ...
- 您正在使用可以自行调整的本机 RDBMS 视图。将始终胜过您在 .NET 中构建的复杂 LINQ 查询
- 您可以利用 Telerik ToDataSourceResult 的过滤、分组、聚合、...
回答by abeloqp
I followed the suggestion of CodingWithSpike and it works. I created an extension method for the DataSourceRequest class:
我遵循了 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;
}
}
}
}
Then in your controller, add the usingto the extension class and before the call to ToDataSourceResult(), add this:
然后在您的控制器中,添加using到扩展类,并在调用 ToDataSourceResult() 之前添加:
request.RenameRequestFilterMember("IsActiveText", "IsActive");
回答by Manprit Singh Sahota
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.
我遇到了同样的问题,经过大量研究后,我使用 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);
}
Remember to configure Automapper using CreateMap.
请记住使用 CreateMap 配置 Automapper。
Note: Here getCars will return IQueryable result car.
注意:这里 getCars 将返回 IQueryable 结果汽车。

