【问题标题】:Using AutoMapper and PagedList in MVC在 MVC 中使用 AutoMapper 和 PagedList
【发布时间】:2014-08-29 10:01:22
【问题描述】:

我看过一篇文章,在Entity Framework中,我们调用.ToList(), Single(), or First()后,查询将被发送到数据库

我有成千上万的数据,所以我不想加载所有数据,而是以分页的形式返回数据。所以我使用 PagedList 在 MVC 中创建分页。如果我们调用例如products.ToPagedList(pageNumber, 10) 时没有出错,那么它将只取10 条数据,而不是全部数据。我说的对吗?

接下来,我使用自动映射器从实体映射到视图模型。

List<ProductViewModel> productsVM = Mapper.Map<List<Product>, List<ProductViewModel>>(products);
return productsVM.ToPagedList(pageNumber, 10);

正如你在上面的sn-p代码中看到的那样,在调用.ToPagedList()之前是否只需要10条记录?如果我们做映射时,它会在里面调用.ToList(),我想它会调用数据库中的所有数据然后返回10条记录。如何追踪?

【问题讨论】:

  • 如果你有 SQL Profiler,只需运行它,看看查询返回什么
  • @ColinBacon 没有 sql profiler 因为我使用的是 express edition。
  • 试用 Sql Express Profiler expressprofiler.codeplex.com 它是免费的。我以前用过,效果很好。

标签: asp.net-mvc entity-framework automapper pagedlist


【解决方案1】:

查看数据库级别的最简单方法是使用Sql Server Profiler。然后就可以看到实体框架正在执行的sql查询了。

如果您使用的是 Sql Express,那么您可以使用 Sql Express Profiler 来做同样的事情。

【讨论】:

    【解决方案2】:

    不,分页列表之前不需要 10 条记录。您的代码的显示方式,AutoMapper 将导致查询在到达分页列表之前延迟执行,这意味着它将返回所有数据(假设 1000 条记录)。然后PagedList会正确检索到已经物化的List中的10条,并识别出总记录量为1000条。

    我认为您想在数据库中过滤 10,这具有更好的性能,因此您应该在您的数据库实体的 IQueryable 中使用 PagedList,如下所示:

    List<Product> filteredProducts = dbContext.Products.OrderBy(p => p.ProductId).ToPagedList(pageNumber, 10);
    return Mapper.Map<List<Product>, List<ProductViewModel>>(filteredProducts);
    

    OrderByPagedList 的必填项。


    小心使用自动映射器

    考虑以下场景。如果您的Product 实体与ProductReviewICollection&lt;ProductReview&gt;)有这样的子关系怎么办:

    public class ProductReview
    {
        public int ProductId { get; set; }
        public string Description { get; set; }
        public int ReviewerId { get; set; }
        public double Score { get; set; }
    }
    
    public class Product
    {
        public int ProductId { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
        public virtual ICollection<ProductReview> Reviews { get; set; }
    }
    

    ...您的ProductViewModel 有一个int 属性ReviewsCount 显示在您的视图中?

    Automapper映射和转换您的实体到视图模型时,它将访问列表中每个产品的Reviews 属性(假设您的情况为10),一个一,让Reviews.Count() 填写ReviewsCount 到你的ProductViewModel

    考虑到我的示例,在该示例中,我从不急切加载 Reviews 的产品,如果打开 延迟加载AutoMapper 将执行十个查询(每个产品一个)以计算 Reviews。计数是一项快速操作,十个产品只是其中的几个。但是,如果您实际上将ProductReview 映射到ProductReviewViewModel 而不是计数,这将有点重。如果关闭延迟加载,我们会得到一个异常,因为Reviews 将是null

    一种可能的解决方案是在映射期间预先加载您可能需要的所有子项,如下所示:

    List<Product> filteredProducts = dbContext.Products.Include("Reviews").OrderBy(p => p.ProductId).ToPagedList(pageNumber, 10);
    return Mapper.Map<List<Product>, List<ProductViewModel>>(filteredProducts);
    

    ...所以 10 个产品及其评论将在一个查询中检索,AutoMapper 不会执行其他查询。

    但是.......我只需要一个计数,我真的需要检索 ALL Reviews 以避免多次查询吗?

    加载所有评论和所有昂贵的字段(如 Description 可能有数千个字符)不是也很繁重吗???

    是的,当然。避免在这些情况下将PagedListAutoMapper 混合使用。

    只要做一个这样的投影:

            List<Product> filteredProducts = dbContext.Products
                .Select(p => new ProductViewModel
                {
                    ProductId = p.ProductId,
                    ProductName = p.Name,
                    ProductDescription = p.Description,
                    ReviewsCount = p.Reviews.Count(),
                    ScoreAverage = p.Reviews.Select(r => r.Score).DefaultIfEmpty().Average()
                })
                .OrderBy(p => p.ProductId).ToPagedList(pageNumber, 10);
    

    现在您正在加载 10 个产品,将它们投影到 ProductViewModel,计算 Reviews 计数和平均得分,而不从数据库中检索所有 Reviews

    当然,在某些情况下,您可能确实需要加载/实现所有子实体,但除此之外,还需要投影 ftw。

    您还可以将Select() 部分放在扩展类中,并将所有投影封装在扩展方法中,这样您就可以像使用AutoMapper 一样重用它们。

    我并不是说AutoMapper 是邪恶的,你不应该使用它,我自己在某些情况下使用它,你只需要在适当的时候使用它。


    编辑:AUTOMAPPER 支持投影

    我找到了this question @GertArnold 解释了以下关于AutoMapper 的内容:

    ...增加了对被翻译的投影的支持的代码库 进入表达式,最后进入 SQL

    所以开心就好,听从他的建议。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-08-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-05-26
      • 2014-09-27
      相关资源
      最近更新 更多