为什么使用'virtual'用于实体框架模型定义中的类属性

本文关键字:定义 属性 模型 用于 virtual 为什么 实体 框架 | 更新日期: 2023-09-27 18:18:21

在以下博客:http://weblogs.asp.net/scottgu/archive/2010/07/16/code-first-development-with-entity-framework-4.aspx

博客包含以下代码示例:

public class Dinner
{
   public int DinnerID { get; set; }
   public string Title { get; set; }
   public DateTime EventDate { get; set; }
   public string Address { get; set; }
   public string HostedBy { get; set; }
   public virtual ICollection<RSVP> RSVPs { get; set; }
}
public class RSVP
{
   public int RsvpID { get; set; }
   public int DinnerID { get; set; }
   public string AttendeeEmail { get; set; }
   public virtual Dinner Dinner { get; set; }
}

在类中定义属性时使用virtual的目的是什么?它有什么作用?

为什么使用'virtual'用于实体框架模型定义中的类属性

它允许实体框架在虚拟属性周围创建代理,以便该属性可以支持延迟加载和更有效的更改跟踪。先看看在实体框架4.1 POCO代码中虚拟关键字可以有什么效果?进行更深入的讨论。

编辑以澄清"创建代理"; 通过"创建一个代理",我特别指的是实体框架的作用。实体框架要求你的导航属性被标记为虚拟,这样就可以支持延迟加载和有效的变更跟踪。请参见创建POCO代理要求。
实体框架使用继承来支持此功能,这就是为什么它要求在基类poco中将某些属性标记为virtual的原因。它实际上创建了从POCO类型派生的新类型。因此,您的POCO充当实体框架动态创建的子类的基类型。这就是我所说的"创建一个代理"的意思。

实体框架创建的动态创建的子类在运行时使用实体框架时变得明显,而不是在静态编译时。并且只有当您启用实体框架的延迟加载或更改跟踪功能时。如果你选择不使用延迟加载或改变实体框架的跟踪特性(这不是默认的),那么你就不需要将任何导航属性声明为虚拟属性。然后,你要负责自己加载这些导航属性,要么使用实体框架所称的"渴望加载",要么在多个数据库查询中手动检索相关类型。但是,在很多情况下,你可以而且应该使用延迟加载和更改导航属性的跟踪功能。

如果你要创建一个独立的类,并将属性标记为虚拟的,并简单地在你自己的应用程序中构造和使用这些类的实例,完全在实体框架的范围之外,那么你的虚拟属性不会为你自己带来任何好处。

编辑描述为什么属性将被标记为虚拟

属性,例如:

 public ICollection<RSVP> RSVPs { get; set; }

不是字段,不应该这样考虑。它们被称为getter和setter,在编译时,它们被转换成方法。

//Internally the code looks more like this:
public ICollection<RSVP> get_RSVPs()
{
    return _RSVPs;
}
public void set_RSVPs(RSVP value)
{
    _RSVPs = value;
}
private RSVP _RSVPs;

这就是为什么它们在实体框架中被标记为虚拟的;它允许动态创建的类覆盖内部生成的getset函数。如果你的导航属性getter/setter在你的实体框架使用中为你工作,试着修改它们为属性,重新编译,看看实体框架是否仍然能够正常工作:

 public virtual ICollection<RSVP> RSVPs;

c#中的virtual关键字允许子类覆盖方法或属性。有关更多信息,请参阅MSDN文档中的"virtual"关键字

更新:这并没有回答当前所问的问题,但我将把它留在这里,供任何人寻找原始的,非描述性问题的简单答案。

我理解OPs的挫败感,virtual的这种用法并不是针对模板化抽象的,而事实上virtual修饰符对模板化抽象是有效的。

如果有人还在纠结这个问题,我会提供我的观点,因为我试图保持解决方案的简单和术语最小化:

在一个简单的片段中,

Entity Framework确实利用了延迟加载,这相当于为将来的执行做准备。这符合'virtual'修饰语,但还有更多。

在实体框架中,使用虚拟导航属性允许您将其表示为相当于SQL中的可空外键。在执行查询时,您不必急切地连接每个键表,但是当您需要这些信息时,它就变成了需求驱动的。

我还提到了nullable,因为许多导航属性一开始是不相关的。例如,在客户/订单场景中,您不必等到处理订单的那一刻才创建客户。可以,但是如果您有一个多阶段的过程来实现这一点,您可能会发现需要保存客户数据,以便以后完成或部署到未来的订单。如果实现了所有导航属性,则必须在保存中建立每个外键和关系字段。这实际上只是将数据重新设置到内存中,从而破坏了持久性的作用。

因此,虽然它在运行时的实际执行中看起来很神秘,但我发现最好的经验法则是:如果您正在输出数据(读入视图模型或Serializable模型)并且需要在引用之前值,请不要使用virtual;如果您的作用域正在收集可能不完整或需要搜索的数据,并且不需要为搜索完成每个搜索参数,则代码将很好地利用引用,类似于使用可空值属性int?很长时间吗?。此外,将业务逻辑从数据集合中抽象出来,直到需要注入它时,这有许多性能方面的好处,类似于实例化一个对象并将其从null开始。实体框架使用了大量的反射和动态,这可能会降低性能,需要有一个灵活的模型,可以扩展到需求是管理性能的关键。

对我来说,这总是比使用超载的技术术语,如代理、委托、处理程序等更有意义。一旦掌握了第三或第四种编程语言,就会变得很混乱。

在EF的上下文中,将属性标记为virtual允许EF使用延迟加载来加载它。为了使延迟加载工作,EF必须创建一个代理对象,该对象使用在第一次访问时加载引用实体的实现来覆盖您的虚拟属性。如果你没有将属性标记为virtual,那么惰性加载将无法处理它。

在模型中定义导航属性是很常见的是虚拟的。当导航属性被定义为virtual时,它可以利用实体框架的某些功能。的最常见的是延迟加载。

延迟加载是许多orm的一个很好的特性,因为它允许您动态地访问模型中的相关数据。这不是不必要的获取相关数据,直到实际访问为止减少对数据库数据的预先查询。

选自《ASP. net》

如果不提到多态性,我们就不能谈论虚拟成员。实际上,基类中标记为virtual的函数、属性、索引器或事件允许从派生类重写。

默认情况下,类的成员是非虚的,不能将其标记为静态、抽象、私有或覆盖修饰符。

让我们考虑System.Object中的ToString()方法。因为这个方法是System的成员。对象,它在所有类中继承,并将为所有类提供ToString()方法。

namespace VirtualMembersArticle
{
    public class Company
    {
        public string Name { get; set; }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Company company = new Company() { Name = "Microsoft" };
            Console.WriteLine($"{company.ToString()}");
            Console.ReadLine();
        }   
    }
}

前面代码的输出是:

VirtualMembersArticle.Company

假设我们想要改变继承自System的ToString()方法的标准行为。对象在Company类中。要实现这个目标,使用override关键字来声明该方法的另一个实现就足够了。

public class Company
{
    ...
    public override string ToString()
    {
        return $"Name: {this.Name}";
    }         
}

现在,当调用虚方法时,运行时将检查其派生类中是否存在重写成员,如果存在则调用该成员。应用程序的输出将是:

Name: Microsoft

事实上,如果你检查系统。对象类中,你会发现该方法被标记为virtual。

namespace System
{
    [NullableContextAttribute(2)]
    public class Object
    {
        ....
        public virtual string? ToString();
        ....
    }
}

在实体框架中,使用虚拟导航属性允许您将其表示为相当于SQL中的可空外键。在执行查询时,您不必急切地连接每个键表,但是当您需要信息时,它就变成了需求驱动。