12

使用以下类加载/过滤/订购剑道网格的最佳方法是什么:

领域:

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

自动映射器

Mapper.CreateMap<Car, CarViewModel>()
      .ForMember(dest => dest.IsActiveText, 
                 src => src.MapFrom(m => m.IsActive ? "Yes" : "No"));

可查询的

var domainList = RepositoryFactory.GetCarRepository().GetAllQueryable();

数据源结果

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”:汽车

在这种情况下,最好的方法是什么?

4

6 回答 6

12

我不喜欢 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);
    }
于 2013-07-03T07:57:27.293 回答
5

这似乎有些奇怪。你告诉 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,但在服务器上您正在ToDataSourceResult()针对Car对象(an IQueryable<Car>)运行,这些对象没有该名称的属性。映射发生在过滤和排序之后。

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

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

于 2013-05-11T00:45:30.503 回答
4

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('_', '.');
    }
}
于 2015-06-23T14:49:47.407 回答
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");
于 2016-05-11T21:39:51.237 回答
2

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

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

    public class MyViewModelVM
    {
        public int Id { get; set; }
        public string MyFlattenedProperty { get; set; }
    }
    
  2. 在您的 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 视图类映射到您的视图模型

    Mapper.CreateMap<MyDatabaseView, MyViewModelVM>();
    
  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<MyViewModelVM>(r));
    
            return Json(result);
        }
    
        return Json(new List<MyViewModelVM>().ToDataSourceResult(request));
    }
    

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

  • 您正在使用可以自行调整的本机 RDBMS 视图。将始终胜过您在 .NET 中构建的复杂 LINQ 查询
  • 您可以利用 Telerik ToDataSourceResult 的过滤、分组、聚合等优势...
于 2015-11-27T11:51:53.890 回答
2

我遇到了同样的问题,经过大量研究后,我使用 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 结果汽车。

于 2017-01-17T14:53:50.963 回答