为什么在Entity Framework模型定义中使用'virtual'作为类属性?

时间:2011-12-17 05:44:05

标签: c# class properties 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的目的是什么?它有什么影响?

6 个答案:

答案 0 :(得分:227)

它允许实体框架围绕虚拟属性创建代理,以便该属性可以支持延迟加载和更有效的更改跟踪。有关更全面的讨论,请参阅What effect(s) can the virtual keyword have in Entity Framework 4.1 POCO Code First?

修改以澄清“围绕创建代理”: 通过“创建一个代理”,我具体指的是实体框架的作用。实体框架要求将导航属性标记为虚拟,以便支持延迟加载和高效更改跟踪。见Requirements for Creating POCO Proxies
实体框架使用继承来支持此功能,这就是为什么它要求在基类POCO中将某些属性标记为虚拟。它从字面上创建了源自POCO类型的新类型。因此,您的POCO充当实体框架动态创建的子类的基本类型。这就是我所说的“围绕创建代理”。

在运行时使用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函数。如果您的导航属性getter / setter在您的Entity Framework使用中正在为您工作,请尝试将它们修改为仅属性,重新编译,并查看实体框架是否仍能正常运行:

 public virtual ICollection<RSVP> RSVPs;

答案 1 :(得分:71)

C#中的virtual关键字允许子类重写方法或属性。有关详细信息,请参阅the MSDN documentation on the 'virtual' keyword

更新:这不会回答当前要求的问题,但我会留在这里,以便任何寻找original非描述性问题的简单答案的人。

答案 2 :(得分:20)

我理解OP的挫败感,虚拟化的这种用法不适用于defacto虚拟修饰符有效的模板化抽象。

如果有人仍然在努力解决这个问题,我会提出我的观点,因为我试图将解决方案简单化并将术语降至最低:

实体框架在一个简单的部分确实利用延迟加载,这相当于为将来执行准备一些东西。这符合“虚拟”修饰符,但还有更多内容。

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

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

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

对我而言,总是比使用代理,代理,处理程序等重载技术术语更有意义。一旦你达到了你的第三或第四个编程语言,它就会变得混乱。

答案 3 :(得分:12)

  

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

     

延迟加载是许多ORM的一个很好的功能,因为它允许你   动态访问模型中的相关数据。它不会没有必要   获取相关数据,直到实际访问它为止   减少数据库中数据的前期查询。

来自Book&#34; ASP.NET MVC 5与Bootstrap和Knockout.js&#34;

答案 4 :(得分: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";
            }
        }
    }
}

答案 5 :(得分:0)

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

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

示例 让我们考虑 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();
        ....
    }
}