¿Por qué usar 'virtual' para las propiedades de clase en las definiciones de modelo de Entity Framework?

223

En el siguiente blog: http://weblogs.asp.net/scottgu/archive/2010/07/16/code-first-development-with-entity-framework-4.aspx

El blog contiene el siguiente ejemplo de código:

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

¿Cuál es el propósito de usar virtualal definir una propiedad en una clase? ¿Qué efecto tiene?

Gary Jones
fuente
99
¿Está solicitando comprender el propósito general de la palabra clave 'virtual' en C # o cómo se relaciona específicamente con Entity Framework?
M.Babcock
2
@ M.Babcock: estoy preguntando cuál es el propósito en lo que respecta a las propiedades, porque nunca antes había visto esto.
Gary Jones
1
Si está familiarizado con la forma en que la palabra clave virtual afecta el polimorfismo en los métodos, entonces es lo mismo para las propiedades.
M.Babcock
20
@ M.Babcock: ¿cómo podría haberlo hecho más evidente? La pregunta se titula "¿Por qué usar 'virtual' para propiedades en clases?".
Gary Jones
2
@Gary: las propiedades getter / setter se compilan estáticamente en métodos. Por lo tanto, no son campos de clase tradicionales como 'cena virtual pública';
Shan Plourde

Respuestas:

248

Permite que Entity Framework cree un proxy alrededor de la propiedad virtual para que la propiedad pueda soportar una carga diferida y un seguimiento de cambios más eficiente. Consulte ¿Qué efecto (s) puede tener la palabra clave virtual en Entity Framework 4.1 POCO Code First? para una discusión más completa

Editar para aclarar "crear un proxy alrededor": por "crear un proxy alrededor" me refiero específicamente a lo que hace el Entity Framework. Entity Framework requiere que sus propiedades de navegación se marquen como virtuales para que se admitan cargas lentas y un seguimiento de cambios eficiente. Consulte los requisitos para crear proxies POCO .
Entity Framework utiliza la herencia para admitir esta funcionalidad, por lo que requiere que ciertas propiedades se marquen virtuales en sus POCO de clase base. Literalmente crea nuevos tipos que se derivan de sus tipos de POCO. Por lo tanto, su POCO actúa como un tipo base para las subclases creadas dinámicamente de Entity Framework. Eso es lo que quise decir con "crear un proxy alrededor".

Las subclases creadas dinámicamente que Entity Framework crea se hacen evidentes cuando se usa Entity Framework en tiempo de ejecución, no en tiempo de compilación estático. Y solo si habilita las funciones de carga diferida de Entity Framework o de seguimiento de cambios. Si opta por no utilizar nunca las funciones de carga diferida o de seguimiento de cambios de Entity Framework (que no es la predeterminada), no necesita declarar ninguna de sus propiedades de navegación como virtuales. Luego, usted es responsable de cargar esas propiedades de navegación usted mismo, ya sea utilizando lo que Entity Framework denomina "carga ansiosa" o recuperando manualmente los tipos relacionados en múltiples consultas de bases de datos. Sin embargo, puede y debe usar funciones de carga diferida y seguimiento de cambios para sus propiedades de navegación en muchos escenarios.

Si crea una clase independiente y marca propiedades como virtuales, y simplemente construye y usa instancias de esas clases en su propia aplicación, completamente fuera del alcance de Entity Framework, entonces sus propiedades virtuales no le darían nada en su propio.

Edite para describir por qué las propiedades se marcarían como virtuales

Propiedades como:

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

No son campos y no deben considerarse como tales. Estos se llaman getters y setters y en el momento de la compilación, se convierten en métodos.

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

Es por eso que están marcados como virtuales para su uso en Entity Framework, permite que las clases creadas dinámicamente anulen las funciones gety las setfunciones generadas internamente . Si sus captadores / establecedores de propiedades de navegación están trabajando para usted en el uso de Entity Framework, intente revisarlos solo para propiedades, recompilar y ver si Entity Framework todavía puede funcionar correctamente:

 public virtual ICollection<RSVP> RSVPs;
Shan Plourde
fuente
2
¿Qué quieres decir con 'crear un proxy alrededor'? ¿Qué está pasando realmente aquí?
Gary Jones
2
Hola Gary, revisé mi respuesta para aclarar lo que quiero decir con "crear un proxy alrededor". Espero que esto ayude un poco.
Shan Plourde
2
Decir "propiedades ... no son propiedades" es bastante inútil. Todas las propiedades se implementan como métodos getter y / o setter, por lo que no tiene sentido decir "esta propiedad es realmente un método getter y setter, no una propiedad".
Ben Voigt
1
Gracias por sus comentarios, Ben, debería haber aclarado que "las propiedades no son campos". Avíseme si tiene algún otro comentario o pregunta.
Shan Plourde
Cambié la redacción y agregué otro ejemplo de código para ayudar a explicar las "propiedades no son propiedades" un poco mejor, por favor retroceda si no lo desea.
Scott Chamberlain
75

La virtualpalabra clave en C # permite que las clases secundarias anulen un método o una propiedad. Para obtener más información, consulte la documentación de MSDN sobre la palabra clave "virtual"

ACTUALIZACIÓN: Esto no responde la pregunta como se hace actualmente, pero la dejaré aquí para cualquiera que busque una respuesta simple a la pregunta original no descriptiva que se hizo.

M.Babcock
fuente
23
@Hooch esto no está marcado como correcto porque lo que se considera "correcto" no depende simplemente del título de la pregunta. Me imagino que la mayoría de las personas, yo y OP incluidos, primero tratamos con virtualpropiedades a través de Entity Framework, aunque no es explícito en el título de OP. La respuesta aceptada es así porque toca el lado de Entity Framework de las cosas y cómo / por qué virtualse usan las propiedades en ese contexto.
Don Cheadle
22

Entiendo la frustración de los OP, este uso de virtual no es para la abstracción con plantilla para la que el modificador virtual de facto es efectivo.

Si alguno todavía está luchando con esto, ofrecería mi punto de vista, ya que trato de mantener las soluciones simples y la jerga al mínimo:

Entity Framework en una pieza simple utiliza carga diferida, que es el equivalente de preparar algo para la ejecución futura. Eso se ajusta al modificador 'virtual', pero hay más en esto.

En Entity Framework, el uso de una propiedad de navegación virtual le permite denotarlo como el equivalente de una Clave externa anulable en SQL. No tiene que unirse con entusiasmo a todas las tablas con clave cuando realiza una consulta, pero cuando necesita la información, se vuelve impulsada por la demanda.

También mencioné anulable porque muchas propiedades de navegación no son relevantes al principio. es decir, en un escenario de cliente / pedidos, no tiene que esperar hasta el momento en que se procesa un pedido para crear un cliente. Se puede, pero si tuviera un proceso de varias etapas para lograr esto, es posible encontrar la necesidad de persistir los datos de los clientes para la finalización de plazo o para su implementación en futuros pedidos. Si se implementaran todas las propiedades de navegación, tendría que establecer cada clave externa y campo relacional en el guardado. Eso realmente solo restablece los datos en la memoria, lo que anula el papel de la persistencia.

Entonces, aunque puede parecer críptico en la ejecución real en tiempo de ejecución, he encontrado que la mejor regla general para usar sería: si está generando datos (leyendo en un Modelo de visualización o Modelo serializable) y necesita valores antes de las referencias, no usar virtual; Si su alcance es la recopilación de datos que pueden estar incompletos o la necesidad de buscar y no requieren que todos los parámetros de búsqueda se completen para una búsqueda, el código hará un buen uso de la referencia, de manera similar al uso de las propiedades de valor anulables int? ¿largo?. Además, abstraer su lógica de negocios de su recopilación de datos hasta la necesidad de inyectarla tiene muchos beneficios de rendimiento, similar a crear instancias de un objeto y comenzarlo desde cero. Entity Framework utiliza mucha reflexión y dinámica, lo que puede degradar el rendimiento, y la necesidad de tener un modelo flexible que se pueda adaptar a la demanda es fundamental para gestionar el rendimiento.

Para mí, eso siempre tuvo más sentido que usar jerga tecnológica sobrecargada como representantes, delegados, controladores y demás. Una vez que llegas a tu tercera o cuarta programación lang, puede ser complicado con estos.

Nathan Teague
fuente
14

Es bastante común definir propiedades de navegación en un modelo para que sea virtual. Cuando una propiedad de navegación se define como virtual, puede aprovechar cierta funcionalidad de Entity Framework. El más común es la carga perezosa.

La carga diferida es una buena característica de muchos ORM porque le permite acceder dinámicamente a datos relacionados desde un modelo. No buscará innecesariamente los datos relacionados hasta que realmente se acceda a ellos, reduciendo así la consulta inicial de datos de la base de datos.

Del libro "ASP.NET MVC 5 con Bootstrap y Knockout.js"

Hassan Rahman
fuente
3

En el contexto de EF, marcar una propiedad como virtual le permite a EF usar carga diferida para cargarla. Para que la carga diferida funcione, EF tiene que crear un objeto proxy que anule sus propiedades virtuales con una implementación que carga la entidad referenciada cuando se accede por primera vez. Si no marca la propiedad como virtual, la carga diferida no funcionará con ella.

Shakeer Hussain
fuente
0

La palabra clave virtual se usa para modificar un método, propiedad, indexador o declaración de evento y permitir que se anule en una clase derivada. Por ejemplo, este método puede ser anulado por cualquier clase que lo herede:

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

No puede usar el modificador virtual con los modificadores estáticos, abstractos, privados o de anulación. El siguiente ejemplo muestra una propiedad virtual:

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";
            }
        }
    }
}
FatalMan
fuente
Esto está totalmente fuera de tema hermano.
Eru
0

No podemos hablar de miembros virtuales sin referirnos al polimorfismo . De hecho, una función, propiedad, indexador o evento en una clase base marcada como virtual permitirá la anulación de una clase derivada.

Por defecto, los miembros de una clase no son virtuales y no se pueden marcar como si fueran modificadores estáticos, abstractos, privados o de anulación.

Ejemplo Consideremos el método ToString () en System.Object . Debido a que este método es miembro de System.Object, se hereda en todas las clases y proporcionará los métodos ToString () a todos ellos.

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

La salida del código anterior es:

VirtualMembersArticle.Company

Consideremos que queremos cambiar el comportamiento estándar de los métodos ToString () heredados de System.Object en nuestra clase Company. Para lograr este objetivo, es suficiente usar la palabra clave de anulación para declarar otra implementación de ese método.

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

Ahora, cuando se invoca un método virtual, el tiempo de ejecución verificará si hay un miembro superior en su clase derivada y lo llamará si está presente. El resultado de nuestra aplicación será:

Name: Microsoft

De hecho, si marca la clase System.Object, encontrará que el método está marcado como virtual.

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