【问题标题】:Why use 'virtual' for class properties in Entity Framework model definitions?为什么在实体框架模型定义中对类属性使用“虚拟”?
【发布时间】:2012-01-22 11:50:14
【问题描述】:

在以下博客中: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 的目的是什么?有什么作用?

【问题讨论】:

  • 您是想了解 C# 中“虚拟”关键字的一般用途,还是它与实体框架的具体关系?
  • @M.Babcock:我问的是与属性有关的目的是什么,因为我以前从未见过。
  • 如果您熟悉 virtual 关键字如何影响方法中的多态性,那么属性也是如此。
  • @M.Babcock:我怎样才能让它更明显?问题的标题是“为什么对类中的属性使用'虚拟'?”。
  • @Gary - getter / setter 属性实际上被静态编译成方法。所以它们不是“公共虚拟晚餐”等传统的类字段;

标签: c# class properties virtual


【解决方案1】:

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

编辑以阐明“创建代理”: 通过“创建一个代理”,我专门指的是实体框架的作用。实体框架要求将您的导航属性标记为虚拟,以便支持延迟加载和有效的更改跟踪。见Requirements for Creating POCO Proxies
实体框架使用继承来支持此功能,这就是为什么它需要在基类 POCO 中将某些属性标记为虚拟的原因。它实际上创建了从您的 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 在您的 Entity Framework 使用中为您工作,请尝试将它们修改为仅属性,重新编译,然后查看 Entity Framework 是否仍然能够正常运行:

 public virtual ICollection<RSVP> RSVPs;

【讨论】:

  • “创建代理”是什么意思?这里到底发生了什么?
  • 嗨,加里,我修改了我的答案以澄清我所说的“创建代理”的意思。希望对您有所帮助。
  • 说“属性...不是属性”是非常无益的。所有属性都实现为 getter 和/或 setter 方法,因此说“此属性实际上是 getter 和 setter 方法而不是属性”是没有意义的。
  • 感谢您的反馈 Ben,我应该澄清一下“属性不是字段”。如果您有任何其他反馈或问题,请告诉我。
  • 我更改了措辞并添加了另一个代码示例,以帮助更好地解释“属性不是属性”,如果您不想要,请回滚。
【解决方案2】:

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

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

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

【讨论】:

    【解决方案3】:

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

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

    您不能将 virtual 修饰符与 static、abstract、private 或 override 修饰符一起使用。以下示例显示了一个虚拟属性:

    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";
                }
            }
        }
    }
    

    【讨论】:

    • 这完全是题外话。
    【解决方案4】:

    C# 中的virtual 关键字使方法或属性可以被子类覆盖。更多信息请参考the MSDN documentation on the 'virtual' keyword

    更新:这并没有回答当前提出的问题,但我会将其留在这里,以供任何人寻找original 的简单答案,提出的非描述性问题。

    【讨论】:

    • @Hooch 这未标记为正确,因为被视为“正确”的内容不仅仅取决于问题标题。我想大多数人,包括我自己和 OP,首先通过实体框架处理 virtual 属性 - 即使它在 OP 的标题中并没有明确说明。接受的答案是这样,因为它涉及到实体框架方面,以及在该上下文中如何/为什么使用 virtual 属性。
    • 其实,由于标题不再是真实的,任何读者都会在这里搜索纯粹的“虚拟”解释。
    【解决方案5】:

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

    【讨论】:

    • 我喜欢这个简洁的答案。
    【解决方案6】:

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

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

    来自“带有 Bootstrap 和 Knockout.js 的 ASP.NET MVC 5”一书

    【讨论】:

      【解决方案7】:

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

      如果有任何人仍在为此苦苦挣扎,我会提出我的观点,因为我会尽量保持解决方案的简单性并将行话保持在最低限度:

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

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

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

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

      对我来说,这总是比使用代理、委托、处理程序等重载技术术语更有意义。一旦你达到了第三或第四个编程语言,它就会变得一团糟。

      【讨论】:

        猜你喜欢
        • 2013-01-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-07-28
        • 2016-05-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多