为什么在实体框架模型定义中为类属性使用“虚拟”?


223

在以下博客中: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在类中定义属性时使用的目的是什么?它有什么作用?


9
您是否要了解C#中“虚拟”关键字的一般用途或它与Entity Framework的特定联系?
M.Babcock 2011年

2
@ M.Babcock:我问的是与属性有关的目的是什么,因为我从未见过。
加里·琼斯

1
如果您熟悉virtual关键字如何影响方法中的多态性,那么属性也是如此。
M.Babcock 2011年

19
@ M.Babcock:我怎么能使它更明显?这个问题的标题是“为什么对类中的属性使用'虚拟'?”。
加里·琼斯

2
@Gary-getter / setter属性实际上是静态编译为方法的。因此,它们不是传统的课堂领域,例如“公共虚拟晚餐”;
Shan Plourde

Answers:


248

它允许实体框架围绕虚拟属性创建代理,以便该属性可以支持延迟加载和更有效的更改跟踪。请参阅“虚拟关键字”在Entity Framework 4.1 POCO Code First中可以起到什么作用?进行更彻底的讨论。

编辑以澄清“在周围创建代理”: 通过“在周围创建代理”,我指的是实体框架的功能。实体框架要求将导航属性标记为虚拟,以便支持延迟加载和有效的更改跟踪。请参阅创建POCO代理的要求
实体框架使用继承来支持此功能,这就是为什么它要求在基类POCO中将某些属性标记为虚拟的原因。它从字面上创建从POCO类型派生的新类型。因此,您的POCO充当实体框架动态创建的子类的基本类型。这就是我“围绕周围创建代理”的意思。

当在运行时而非静态编译时使用Entity Framework时,Entity Framework创建的动态创建的子类变得很明显。并且仅当您启用实体框架的延迟加载或更改跟踪功能时。如果您选择从不使用Entity Framework的延迟加载或更改跟踪功能(这不是默认设置),则无需将任何导航属性声明为虚拟。然后,您将负责自己加载这些导航属性,或者使用Entity Framework所谓的“紧急加载”,或者在多个数据库查询中手动检索相关类型。在许多情况下,您可以并且应该将延迟加载和更改跟踪功能用于导航属性。

如果您要创建一个独立的类并将属性标记为虚拟,并且仅在完全属于Entity Framework范围之外的自己的应用程序中构造和使用这些类的实例,那么您的虚拟属性将不会给您带来任何好处拥有。

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

属性例如:

 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;

这就是为什么在Entity Framework中将它们标记为虚拟的原因,它允许动态创建的类覆盖内部生成的getset函数。如果您的导航属性获取器/设置器在您使用Entity Framework时对您有用,请尝试将它们修改为仅属性,重新编译,然后查看Entity Framework是否仍然能够正常运行:

 public virtual ICollection<RSVP> RSVPs;

2
“在周围创建代理”是什么意思?这里到底发生了什么?
加里·琼斯

2
嗨,加里(Gary),我修改了答案,以阐明“创建代理”的含义。希望那些对你有帮助。
Shan Plourde

2
说“属性...不是属性”是完全没有帮助的。所有属性都以getter和/或setter方法实现,因此说“此属性实际上是getter和setter方法而不是属性”是没有意义的。
Ben Voigt

1
感谢Ben的反馈,我应该澄清“属性不是字段”。让我知道您是否还有其他反馈或问题。
Shan Plourde

我更改了措辞,并添加了另一个代码示例,以帮助您更好地解释“属性不是属性”,如果您不想要它,请回滚。
Scott Chamberlain 2014年

75

virtualC#中的关键字使子类可以覆盖方法或属性。有关更多信息,请参考“虚拟”关键字上的MSDN文档。

更新:这不能回答当前提出的问题,但是我会将其留给所有希望为原始的非描述性问题提供简单答案的人。


23
@Hooch,这没有被标记为正确,因为被认为是“正确”的并不仅仅取决于问题的标题。我想大多数人(包括我本人和OP)首先会virtual通过Entity Framework 处理属性-即使在OP的标题中并没有明确说明。之所以接受答案,是因为它涉及事物的实体框架方面,以及virtual在该上下文中如何/为什么使用属性。
Don Cheadle

22

我了解OP的挫败感,对virtual的这种使用不是针对事实上的virtual修饰符有效的模板化抽象。

如果有任何问题仍在努力中,我将提出自己的观点,因为我试图保持解决方案的简单性并将术语降到最低:

简单的实体框架确实利用了延迟加载,这相当于为将来的执行做准备。这适合“虚拟”修饰符,但还有更多。

在Entity Framework中,使用虚拟导航属性可以将其表示为SQL中可为空的外键的等效项。在执行查询时,您不必急于联接每个键控表,但是当您需要信息时,它就会成为需求驱动的。

我还提到了nullable,因为许多导航属性起初并不相关。即在客户/订单方案中,您不必等到处理订单以创建客户的那一刻。您可以,但是如果您有一个多阶段的流程来实现此目的,则可能会发现需要保留客户数据以供以后完成或部署到将来的订单中。如果实现了所有导航属性,则必须在保存上建立每个外键和关系字段。这实际上只是将数据设置回内存中,从而破坏了持久性的作用。

因此,尽管在运行时实际执行中似乎有些神秘,但我发现要使用的最佳经验法则是:如果要输出数据(读入View模型或Serializable模型),并且在引用之前需要值,则不要使用虚拟 如果您的范围是在收集可能不完整或需要搜索的数据,并且不需要为搜索完成每个搜索参数,则代码将很好地利用引用,类似于使用可空值属性int?长?。另外,从数据收集中抽象业务逻辑直到需要注入它,都具有许多性能优势,类似于实例化对象并将其从null开始。实体框架使用了大量的反射和动态特性,这会降低性能,并且需要具有可以根据需求扩展的灵活模型对于管理性能至关重要。

对我而言,这总是比使用诸如代理,委托,处理程序之类的重载技术术语更有意义。一旦碰到了第三个或第四个编程语言,它们就会变得凌乱。


14

在虚拟模型中定义导航属性是很常见的。将导航属性定义为虚拟属性时,它可以利用某些Entity Framework功能。最常见的一种是延迟加载。

延迟加载是许多ORM的一个不错的功能,因为它允许您动态访问模型中的相关数据。在实际访问之前,它不会不必要地获取相关数据,从而减少了从数据库中对数据的前期查询。

摘自《带有Bootstrap和Knockout.js的ASP.NET MVC 5》


3

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


0

virtual关键字用于修改方法,属性,索引器或事件声明,并允许在派生类中覆盖它。例如,任何继承该方法的类都可以覆盖此方法:

public virtual double Area() 
{
    return x * y;
}

您不能将虚拟修饰符与静态,抽象,私有或替代修饰符一起使用。以下示例显示了一个虚拟属性:

class MyBaseClass
{
    // virtual auto-implemented property. Overrides can only
    // provide specialized behavior if they implement get and set accessors.
    public virtual string Name { get; set; }

    // ordinary virtual property with backing field
    private int num;
    public virtual int Number
    {
        get { return num; }
        set { num = value; }
    }
}


class MyDerivedClass : MyBaseClass
{
    private string name;

    // Override auto-implemented property with ordinary property
    // to provide specialized accessor behavior.
    public override string Name
    {
        get
        {
            return name;
        }
        set
        {
            if (value != String.Empty)
            {
                name = value;
            }
            else
            {
                name = "Unknown";
            }
        }
    }
}

这完全不合主题。
Eru

0

我们不能不谈多态性而谈论虚拟成员。实际上,基类中标记为虚函数的函数,属性,索引器或事件将允许从派生类进行重写。

默认情况下,类的成员是非虚拟的,如果使用静态,抽象,私有或重写修饰符,则不能将其标记为该成员

示例 让我们考虑System.Object中ToString()方法。因为此方法是System.Object的成员,所以它在所有类中都继承,并将为所有类提供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

让我们考虑一下,我们要更改从Company类中的System.Object继承的ToString()方法的标准行为。要实现此目标,只需使用override关键字声明该方法的另一种实现即可。

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

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

Name: Microsoft

实际上,如果您检查System.Object类,您会发现该方法被标记为虚拟方法。

namespace System
{
    [NullableContextAttribute(2)]
    public class Object
    {
        ....
        public virtual string? ToString();
        ....
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.