6

在 MVC 中加载相关对象可能会非常混乱。

如果您真的想知道在编写实体模型类和控制器时正在做什么,您需要了解和学习很多术语。

我有很长时间的几个问题是:virtual关键字是如何工作的,什么时候应该使用它?Include扩展方法是如何工作的,我应该什么时候使用它?

这就是我要说的;

virtual关键词:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace LazyLoading.Models
{
    public class Brand
    {
        public int BrandId { get; set; }
        public string Name { get; set; }
        public int ManufacturerId { get; set; }
        public virtual Manufacturer Manufacturer { get; set; }
    }
}

以及Include扩展方法:

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using LazyLoading.Models;

namespace LazyLoading.Controllers
{
    public class LazyLoadingStoreController : Controller
    {
        private UsersContext db = new UsersContext();

        //
        // GET: /LazyLoadingStore/

        public ActionResult Index()
        {
            var brands = db.Brands.Include(b => b.Manufacturer);
            return View(brands.ToList());
        }

        ... Other action methods ...

请注意,Index()操作方法是由 Visual Studio 自动生成的。是的,Visual Studio 自动添加了.Include(b => b.Manufacturer). 这很不错。

4

3 回答 3

12

注意:我写这个答案花了太长时间才在另外两个出现时丢弃它......

虚拟关键字与两个属性一起使用DbContext.Configuration

  • ProxyCreationEnabled- 当 EF 创建对象时允许 EF crating 动态代理
  • LazyLoadingEnabled- 允许动态代理在第一次使用导航属性时加载相关实体

延迟加载是通过动态代理透明实现的。动态代理是派生自您的实体的类,它由 EF 在运行时创建和编译。它会覆盖您的虚拟导航属性并实施逻辑检查相关实体是否已加载。如果不是,它会触发上下文加载(向数据库发出新查询)。延迟加载只能在实体所附加的上下文范围内执行 - 如果您处置上下文,您将无法使用它。

动态代理的创建由第一个提到的属性控制。一旦实体实例被创建为代理,您就不能“删除”代理。此外,如果您创建没有代理的实体(例如通过自己调用构造函数),您以后无法添加它(但您可以使用DbSet<T>.Create而不是构造函数来获取代理实例)。

第二个属性可以在实体实例的实时期间更改,因此您可以通过将其更改为 false 来避免在处理实体时对数据库进行不必要的查询(有时非常有用)。

Include表示急切加载。急切加载将相关实体与主实体一起加载,并作为主实体查询的一部分执行(它将 SQL 连接添加到查询并构建大结果集)。

急切加载的好处是通过一次往返数据库即可预先获取所有数据。特别是如果您知道您将需要所有这些,这可能是一种方法。如果您使用太多包含以及一些限制(您无法向加载的实体添加排序或过滤 - 它总是加载所有相关对象),那么急切加载的缺点是非常大的结果集。

延迟加载的好处是仅在您真正需要时才加载数据。如果您不知道是否真的需要它们,这将很有帮助。缺点是 EF 在某些情况下会在您不期望的情况下生成额外的查询(对属性的任何首次访问都会触发延迟加载 - 即使Count在导航集合上也会触发所有数据的加载,以便能够在您的应用程序中进行计数从数据库中查询计数 - 这称为额外延迟加载并且它还没有被 EF 原生支持)。另一个大问题是N+1问题。如果您加载多个品牌,并且您将通过循环访问所有已加载品牌而不使用预先加载来访问其制造商属性,您将生成 N+1 个对数据库的查询(其中 N 是品牌数)——一个用于加载所有品牌,一个用于每个品牌的制造商。

还有另一个选项称为显式加载。这就像延迟加载,但它不会为您透明地执行。您必须使用上下文类自己执行它:

context.Entry(brand).Reference(b => b.Manufacturer).Load();

在这种情况下它不是很有用,但如果您在 Manufacturer 类上有 Brands 导航属性,它会很有用,因为您可以这样做:

var dataQuery = context.Entry(manufacturer).Collection(m => m.Brands).Query();

现在您有了一个IQueryable<Brand>实例,您可以添加任何条件、排序甚至额外的急切加载,并针对数据库执行它。

于 2013-05-08T20:55:27.643 回答
7

我创建了一个测试 MVC4 互联网应用程序。

这是我发现的:

首先,创建您的实体模型类 - 注意属性的virtual关键字Manufacturer

public class Manufacturer
{
    public int ManufacturerId { get; set; }
    public string Name { get; set; }
    public ICollection<Brand> Brands { get; set; }
}

public class Brand
{
    public int BrandId { get; set; }
    public string Name { get; set; }
    public int ManufacturerId { get; set; }
    public virtual Manufacturer Manufacturer { get; set; }
}

接下来,创建您的控制器 - 我使用 CRUD 操作方法和视图创建(使用创建新控制器对话框自动生成)我的控制器。由于模型类中的关系,请注意Include由 Visual Studio 自动生成的扩展方法。Brand

public class LazyLoadingStoreController : Controller
{
    private UsersContext db = new UsersContext();

    //
    // GET: /LazyLoadingStore/

    public ActionResult Index()
    {
        var brands = db.Brands.Include(b => b.Manufacturer);
        return View(brands.ToList());
    }

现在让我们删除该Include部分,以便我们的操作方法如下所示:

public ActionResult Index()
{
    var brands = db.Brands;
    return View(brands.ToList());
}

这就是Index添加几个Brand对象后视图在 Page Inspector 中的外观 - 请注意,Visual Studio 自动添加了下拉列表以及它如何自动为列搭建Manufacturer脚手架- 甜蜜!: NameManufacturer在此处输入图像描述 在此处输入图像描述

Create动作方法:

//
// GET: /LazyLoadingStore/Create

public ActionResult Create()
{
    ViewBag.ManufacturerId = new SelectList(db.Manufacturers, "ManufacturerId", "Name");
    return View();
}

惊人的。一切都是为我们自动生成的!

现在,如果我们从属性中删除virtual关键字会发生什么?Manufacturer

public class Brand
{
    public int BrandId { get; set; }
    public string Name { get; set; }
    public int ManufacturerId { get; set; }
    public Manufacturer Manufacturer { get; set; }
}

这就是将会发生的事情 - 我们的制造商数据已经消失:

在此处输入图像描述

好吧,有道理。如果我添加回Include扩展方法(virtual仍然从Manufacturer属性中删除)怎么办?

public ActionResult Index()
{
    var brands = db.Brands.Include(b => b.Manufacturer);
    return View(brands.ToList());
}

这是添加Include扩展方法的结果 -Manufacturer数据回来了!:

在此处输入图像描述

这就是所有这些东西的工作原理。

接下来是解释在两种情况下(延迟加载和急切加载)在幕后生成的 T-SQL 类型。我会留给别人。:)

注意:Include(b => b.Manufacturer)无论您是否添加virtual关键字,Visual Studio 都会自动生成。

注2:哦,是的。差点忘了。以下是一些不错的 Microsoft 资源的链接。

第二个链接讨论了另一个链接所缺乏的性能注意事项,如果这是让你前进的东西。

于 2013-05-08T20:37:58.633 回答
1

延迟加载

Brand是一个 POCO(普通的旧 CLR 对象)。是执着无知。换句话说:它不知道它是由实体框架数据层创建的。它更不知道如何加载它的Manufacturer.

不过,当你这样做时

var brand = db.Brands.Find(1);
var manufacturer = brand.Manufacturer;

Manufacturer即时加载(“懒惰”)。如果您监视发送到数据库的 SQL,您会看到发出第二个查询以获取Manufacturer.

这是因为在幕后,EF 不会创建Brand实例,而是创建派生类型proxy,它填充了执行延迟加载的接线。这就是virtual启用延迟加载需要修改器的原因:代理必须能够覆盖它。

延迟加载通常用于上下文具有相对较长生命周期的智能客户端应用程序(例如每个表单的上下文)。尽管我必须说,即使在智能客户端应用程序中使用短期上下文也是有益的并且完全有可能。

渴望加载

急切加载意味着您​​一次性加载具有粘附对象(父母和/或孩子)的对象。这就是该Include方法的用途。在示例中

db.Brands.Include(b => b.Manufacturer)

您会看到 EF 创建了一个带有连接的 SQL 查询,并且访问 aBrand不再Manufacturer产生单独的查询。

在大多数情况下(尤其是在断开连接的场景中),急切加载是要走的路,因为处理上下文实例的推荐方法是使用它们并为每个工作单元处理它们。因此,延迟加载不是一种选择,因为要延迟加载导航属性,上下文必须是活动的。

于 2013-05-08T20:50:12.770 回答