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

在下面的博客: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的目的是什么?它有什么影响?

200729 次浏览

它允许实体框架围绕虚拟属性创建一个代理,这样属性就可以支持延迟加载和更有效的更改跟踪。有关更详细的讨论,请参见在实体框架4.1 POCO Code First中,virtual关键字有什么影响?

编辑澄清“创建代理”: 通过“创建一个代理”,我特别指的是实体框架所做的事情。实体框架要求您的导航属性被标记为虚拟,以便支持延迟加载和有效的更改跟踪。看到创建POCO代理的要求
实体框架使用继承来支持此功能,这就是为什么它需要在基类poco中将某些属性标记为虚拟的原因。它实际上创建了派生自POCO类型的新类型。因此,您的POCO充当实体框架动态创建的子类的基类型。这就是我所说的“创建一个代理”。

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

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

编辑以描述将属性标记为虚拟的原因

属性如:

 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关键字使方法或属性可以被子类覆盖。更多信息请参考关于“virtual”关键字的MSDN文档

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

我理解OPs的挫败感,虚拟的这种使用并不是为了模板化抽象,而事实上的虚拟修饰符是有效的。

如果有人还在为这个问题而挣扎,我将提供我的观点,因为我试图保持解决方案的简单性,并将术语降至最低:

实体框架在一个简单的部分中确实利用了惰性加载,这相当于为将来的执行做准备。这符合“虚拟”修饰符,但还有更多。

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

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

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

对我来说,这总是比使用过多的技术术语(如代理、委托、处理程序等)更有意义。一旦你接触到第三或第四个编程语言,这些就会变得很混乱。

在模型中定义导航属性是很常见的 是虚拟的。当导航属性被定义为虚拟时,它可以 利用实体框架的某些功能。的 最常见的是延迟加载 延迟加载是许多orm的一个很好的特性,因为它允许您 从模型中动态访问相关数据。没有必要 获取相关数据,直到实际访问为止

.

.

.

摘自《ASP。NET MVC 5与Bootstrap和Knockout.js

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

我们不能在不引用多态性的情况下谈论虚拟成员。事实上,基类中标记为virtual的函数、属性、索引器或事件将允许从派生类重写。 . c

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

< >强的例子 让我们考虑系统。对象中的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();
....
}
}