简体   繁体   English

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

[英]Why use 'virtual' for class properties in Entity Framework model definitions?

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

The blog contains the following code sample:该博客包含以下代码示例:

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

What is the purpose of using virtual when defining a property in a class?在类中定义属性时使用virtual的目的是什么? What effect does it have?它有什么作用?

It allows the Entity Framework to create a proxy around the virtual property so that the property can support lazy loading and more efficient change tracking.它允许实体框架围绕虚拟属性创建代理,以便该属性可以支持延迟加载和更有效的更改跟踪。 See What effect(s) can the virtual keyword have in Entity Framework 4.1 POCO Code First?请参阅Entity Framework 4.1 POCO Code First 中的 virtual 关键字可以产生什么效果? for a more thorough discussion.进行更深入的讨论。

Edit to clarify "create a proxy around": By "create a proxy around", I'm referring specifically to what the Entity Framework does.编辑以澄清“创建代理”:通过“创建代理”,我特指实体框架的作用。 The Entity Framework requires your navigation properties to be marked as virtual so that lazy loading and efficient change tracking are supported.实体框架要求将导航属性标记为虚拟,以便支持延迟加载和高效的更改跟踪。 See Requirements for Creating POCO Proxies .请参阅创建 POCO 代理的要求
The Entity Framework uses inheritance to support this functionality, which is why it requires certain properties to be marked virtual in your base class POCOs.实体框架使用继承来支持此功能,这就是为什么它需要在基类 POCO 中将某些属性标记为虚拟的。 It literally creates new types that derive from your POCO types.它实际上创建了从您的 POCO 类型派生的新类型。 So your POCO is acting as a base type for the Entity Framework's dynamically created subclasses.因此,您的 POCO 充当实体框架动态创建的子类的基本类型。 That's what I meant by "create a proxy around".这就是我所说的“创建一个代理”的意思。

The dynamically created subclasses that the Entity Framework creates become apparent when using the Entity Framework at runtime, not at static compilation time.在运行时使用实体框架时,实体框架创建的动态创建的子类变得明显,而不是在静态编译时。 And only if you enable the Entity Framework's lazy loading or change tracking features.并且仅当您启用实体框架的延迟加载或更改跟踪功能时。 If you opt to never use the lazy loading or change tracking features of the Entity Framework (which is not the default), then you needn't declare any of your navigation properties as virtual.如果您选择从不使用实体框架的延迟加载或更改跟踪功能(这不是默认设置),那么您无需将任何导航属性声明为虚拟属性。 You are then responsible for loading those navigation properties yourself, either using what the Entity Framework refers to as "eager loading", or manually retrieving related types across multiple database queries.然后,您负责自己加载这些导航属性,或者使用实体框架称为“急切加载”的内容,或者跨多个数据库查询手动检索相关类型。 You can and should use lazy loading and change tracking features for your navigation properties in many scenarios though.不过,在许多情况下,您可以并且应该为导航属性使用延迟加载和更改跟踪功能。

If you were to create a standalone class and mark properties as virtual, and simply construct and use instances of those classes in your own application, completely outside of the scope of the Entity Framework, then your virtual properties wouldn't gain you anything on their own.如果您要创建一个独立的类并将属性标记为虚拟,并在您自己的应用程序中简单地构造和使用这些类的实例,完全超出实体框架的范围,那么您的虚拟属性将不会为您带来任何好处自己的。

Edit to describe why properties would be marked as virtual编辑以描述为什么属性会被标记为虚拟

Properties such as:属性,例如:

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

Are not fields and should not be thought of as such.不是字段,不应被视为字段。 These are called getters and setters, and at compilation time, they are converted into methods.它们被称为 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;

That's why they're marked as virtual for use in the Entity Framework;这就是为什么它们在实体框架中被标记为虚拟的; it allows the dynamically created classes to override the internally generated get and set functions.它允许动态创建的类覆盖内部生成的getset函数。 If your navigation property getter/setters are working for you in your Entity Framework usage, try revising them to just properties, recompile, and see if the Entity Framework is able to still function properly:如果您的导航属性 getter/setter 在您的实体框架使用中为您工作,请尝试将它们修改为仅属性,重新编译,然后查看实体框架是否仍能正常运行:

 public virtual ICollection<RSVP> RSVPs;

The virtual keyword in C# enables a method or property to be overridden by child classes. C# 中的virtual关键字允许子类覆盖方法或属性。 For more information please refer to the MSDN documentation on the 'virtual' keyword有关更多信息,请参阅有关“虚拟”关键字的 MSDN 文档

UPDATE: This doesn't answer the question as currently asked, but I'll leave it here for anyone looking for a simple answer to the original , non-descriptive question asked.更新:这并没有像目前提出的那样回答问题,但我会把它留在这里给任何寻找原始非描述性问题的简单答案的人。

I understand the OPs frustration, this usage of virtual is not for the templated abstraction that the defacto virtual modifier is effective for.我理解 OP 的挫败感,virtual 的这种用法不适用于事实上的 virtual 修饰符对其有效的模板化抽象。

If any are still struggling with this, I would offer my view point, as I try to keep the solutions simple and the jargon to a minimum:如果任何人仍在为此苦苦挣扎,我会提供我的观点,因为我尽量使解决方案简单且行话最少:

Entity Framework in a simple piece does utilize lazy loading, which is the equivalent of prepping something for future execution.实体框架在一个简单的片段中确实利用了延迟加载,这相当于为将来的执行做准备。 That fits the 'virtual' modifier, but there is more to this.这符合“虚拟”修饰符,但还有更多。

In Entity Framework, using a virtual navigation property allows you to denote it as the equivalent of a nullable Foreign Key in SQL.在实体框架中,使用虚拟导航属性允许您将其表示为 SQL 中可为空的外键的等效项。 You do not HAVE to eagerly join every keyed table when performing a query, but when you need the information -- it becomes demand-driven.在执行查询时,您不必急切地加入每个键控表,但是当您需要信息时——它变成了需求驱动的。

I also mentioned nullable because many navigation properties are not relevant at first.我还提到了 nullable 因为许多导航属性起初并不相关。 ie In a customer / Orders scenario, you do not have to wait until the moment an order is processed to create a customer.即在客户/订单场景中,您不必等到处理订单的那一刻来创建客户。 You can, but if you had a multi-stage process to achieve this, you might find the need to persist the customer data for later completion or for deployment to future orders.您可以,但如果您有一个多阶段流程来实现这一点,您可能会发现需要保留客户数据以供以后完成或部署到未来的订单。 If all nav properties were implemented, you'd have to establish every Foreign Key and relational field on the save.如果实现了所有导航属性,则必须在保存时建立每个外键和关系字段。 That really just sets the data back into memory, which defeats the role of persistence.这实际上只是将数据放回内存中,这会破坏持久性的作用。

So while it may seem cryptic in the actual execution at run time, I have found the best rule of thumb to use would be: if you are outputting data (reading into a View Model or Serializable Model) and need values before references, do not use virtual;因此,虽然在运行时的实际执行中它可能看起来很神秘,但我发现使用的最佳经验法则是:如果您正在输出数据(读入视图模型或可序列化模型)并且在引用之前需要值,请不要使用虚拟; If your scope is collecting data that may be incomplete or a need to search and not require every search parameter completed for a search, the code will make good use of reference, similar to using nullable value properties int?如果您的范围正在收集可能不完整的数据或需要搜索并且不需要为搜索完成每个搜索参数,则代码将充分利用引用,类似于使用可空值属性 int? long?.长?。 Also, abstracting your business logic from your data collection until the need to inject it has many performance benefits, similar to instantiating an object and starting it at null.此外,从数据集合中提取业务逻辑直到需要注入它具有许多性能优势,类似于实例化一个对象并从 null 启动它。 Entity Framework uses a lot of reflection and dynamics, which can degrade performance, and the need to have a flexible model that can scale to demand is critical to managing performance. Entity Framework 使用大量反射和动态,这会降低性能,并且需要有一个可以根据需求扩展的灵活模型对于管理性能至关重要。

To me, that always made more sense than using overloaded tech jargon like proxies, delegates, handlers and such.对我来说,这总是比使用代理、委托、处理程序等重载的技术术语更有意义。 Once you hit your third or fourth programming lang, it can get messy with these.一旦你使用了第三个或第四个编程语言,它就会被这些弄得一团糟。

It's quite common to define navigational properties in a model to be virtual.将模型中的导航属性定义为虚拟是很常见的。 When a navigation property is defined as virtual, it can take advantage of certain Entity Framework functionality.当导航属性定义为虚拟时,它可以利用某些实体框架功能。 The most common one is lazy loading.最常见的一种是延迟加载。

Lazy loading is a nice feature of many ORMs because it allows you to dynamically access related data from a model.延迟加载是许多 ORM 的一个很好的特性,因为它允许您从模型动态访问相关数据。 It will not unnecessarily fetch the related data until it is actually accessed, thus reducing the up-front querying of data from the database.在实际访问相关数据之前,它不会不必要地获取相关数据,从而减少从数据库中预先查询数据。

From book "ASP.NET MVC 5 with Bootstrap and Knockout.js"来自“ASP.NET MVC 5 with Bootstrap and Knockout.js”一书

In the context of EF, marking a property as virtual allows EF to use lazy loading to load it.在 EF 的上下文中,将属性标记为虚拟允许 EF 使用延迟加载来加载它。 For lazy loading to work EF has to create a proxy object that overrides your virtual properties with an implementation that loads the referenced entity when it is first accessed.为了延迟加载工作,EF 必须创建一个代理对象,该对象使用一个实现来覆盖您的虚拟属性,该实现在第一次访问时加载引用的实体。 If you don't mark the property as virtual then lazy loading won't work with it.如果您不将该属性标记为虚拟,则延迟加载将无法使用。

The virtual keyword is used to modify a method, property, indexer, or event declaration and allow for it to be overridden in a derived class. virtual 关键字用于修改方法、属性、索引器或事件声明,并允许在派生类中覆盖它们。 For example, this method can be overridden by any class that inherits it:例如,此方法可以被任何继承它的类覆盖:

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

You cannot use the virtual modifier with the static, abstract, private, or override modifiers.不能将 virtual 修饰符与 static、abstract、private 或 override 修饰符一起使用。 The following example shows a virtual property:以下示例显示了一个虚拟属性:

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

We can't talk about virtual members without referring to polymorphism .我们不能在不提及多态的情况下谈论虚拟成员。 In fact, a function, property, indexer or event in a base class marked as virtual will allow override from a derived class.事实上,基类中标记为虚拟的函数、属性、索引器或事件将允许从派生类进行覆盖。

By default, members of a class are non-virtual and cannot be marked as that if static, abstract, private, or override modifiers.默认情况下,类的成员是非虚拟的,不能被标记为静态、抽象、私有或覆盖修饰符。

Example Let's consider the ToString() method in System.Object .示例让我们考虑System.Object 中ToString()方法。 Because this method is a member of System.Object it's inherited in all classes and will provide the ToString() methods to all of them.因为此方法是 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();
        }   
    }
}

The output of the previous code is:前面代码的输出是:

VirtualMembersArticle.Company

Let's consider that we want to change the standard behavior of the ToString() methods inherited from System.Object in our Company class.让我们考虑一下,我们想要更改从 Company 类中的 System.Object 继承的 ToString() 方法的标准行为。 To achieve this goal it's enough to use the override keyword to declare another implementation of that method.为了实现这个目标,使用 override 关键字来声明该方法的另一个实现就足够了。

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

Now, when a virtual method is invoked, the run-time will check for an overriding member in its derived class and will call it if present.现在,当调用虚拟方法时,运行时将检查其派生类中的覆盖成员,并在存在时调用它。 The output of our application will then be:我们的应用程序的输出将是:

Name: Microsoft

In fact, if you check the System.Object class you will find that the method is marked as virtual.实际上,如果您检查 System.Object 类,您会发现该方法被标记为虚拟。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM