Por que usar 'virtual' para propriedades de classe nas definições de modelo do Entity Framework?

223

No blog a seguir: http://weblogs.asp.net/scottgu/archive/2010/07/16/code-first-development-with-entity-framework-4.aspx

O blog contém o seguinte exemplo 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; }
}

Qual é o propósito de usar virtualao definir uma propriedade em uma classe? Que efeito isso tem?

Gary Jones
fonte
9
Você está pedindo para entender o objetivo geral da palavra-chave 'virtual' em C # ou como ela se refere especificamente ao Entity Framework?
M.Babcock
2
@ M.Babcock: Estou perguntando qual é o objetivo no que diz respeito às propriedades, porque nunca vi isso antes.
Gary Jones
1
Se você estiver familiarizado com o modo como a palavra-chave virtual afeta o polimorfismo nos métodos, é o mesmo para propriedades.
M.Babcock
20
@ M.Babcock: como eu poderia ter tornado mais evidente? A pergunta é intitulada "Por que usar 'virtual' para propriedades em classes?".
Gary Jones
2
@ Gary - getter / setter propriedades são compiladas estaticamente em métodos. Portanto, eles não são campos de classe tradicionais, como 'jantar virtual público';
precisa

Respostas:

248

Ele permite que o Entity Framework crie um proxy em torno da propriedade virtual para que a propriedade possa suportar carregamento lento e rastreamento de alterações mais eficiente. Consulte Que efeito (s) a palavra-chave virtual pode ter no Código POCO do Entity Framework 4.1 primeiro? para uma discussão mais aprofundada.

Edite para esclarecer "criar um proxy ao redor": Por "criar um proxy ao redor", estou me referindo especificamente ao que o Entity Framework faz. O Entity Framework exige que suas propriedades de navegação sejam marcadas como virtuais para que o carregamento lento e o rastreamento eficiente de alterações sejam suportados. Consulte Requisitos para criar proxies POCO .
O Entity Framework usa herança para oferecer suporte a essa funcionalidade, e é por isso que exige que certas propriedades sejam marcadas como virtuais em seus POCOs de classe base. Ele literalmente cria novos tipos derivados de seus tipos de POCO. Portanto, seu POCO está agindo como um tipo base para as subclasses criadas dinamicamente do Entity Framework. Isso é o que eu quis dizer com "criar um proxy".

As subclasses criadas dinamicamente que o Entity Framework cria tornam-se aparentes ao usar o Entity Framework em tempo de execução, não no tempo de compilação estática. E somente se você ativar os recursos de rastreamento lento de carregamento ou alteração do Entity Framework. Se você optar por nunca usar os recursos de rastreamento lento de carregamento ou alteração do Entity Framework (que não é o padrão), não será necessário declarar virtual nenhuma das propriedades de navegação. Você é responsável por carregar essas propriedades de navegação, usando o que o Entity Framework chama de "carregamento rápido" ou recuperando manualmente os tipos relacionados em várias consultas ao banco de dados. Você pode e deve usar os recursos de rastreamento lento de carregamento e alteração para as propriedades de navegação em muitos cenários.

Se você criar uma classe independente e marcar propriedades como virtuais, e simplesmente construir e usar instâncias dessas classes em seu próprio aplicativo, completamente fora do escopo do Entity Framework, suas propriedades virtuais não ganharão nada com isso. próprio.

Edite para descrever por que as propriedades seriam marcadas como virtuais

Propriedades como:

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

Não são campos e não devem ser considerados como tal. Eles são chamados de getters e setters e, no momento da compilação, são convertidos em 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;

É por isso que eles são marcados como virtuais para uso no Entity Framework, pois permite que as classes criadas dinamicamente substituam as funções gete geradas internamente set. Se seus getter / setters de propriedades de navegação estiverem trabalhando para você no uso do Entity Framework, tente revisá-los para apenas propriedades, recompile e veja se o Entity Framework ainda pode funcionar corretamente:

 public virtual ICollection<RSVP> RSVPs;
Shan Plourde
fonte
2
O que você quer dizer com 'criar um proxy'? O que realmente está acontecendo aqui?
Gary Jones
2
Olá Gary, revisei minha resposta para esclarecer o que quero dizer com "criar um proxy". Espero que ajude um pouco.
Shan Plourde
2
Dizer "propriedades ... não são propriedades" é inútil. Todas as propriedades são implementadas como métodos getter e / ou setter, portanto, não faz sentido dizer "essa propriedade é realmente um método getter e setter, não uma propriedade".
precisa
1
Obrigado pelo seu feedback Ben, eu deveria ter esclarecido que "propriedades não são campos". Deixe-me saber se você tiver outros comentários ou perguntas.
Shan Plourde
Alterei a redação e adicionei outro exemplo de código para ajudar a explicar um pouco melhor as "propriedades não são propriedades", reverta se não desejar.
Scott Chamberlain
75

A virtualpalavra-chave em C # permite que um método ou propriedade seja substituído por classes filho. Para obter mais informações, consulte a documentação do MSDN na palavra-chave 'virtual'.

ATUALIZAÇÃO: Isso não responde à pergunta como atualmente solicitada, mas deixarei aqui para quem procura uma resposta simples para a pergunta original e não descritiva original.

M.Babcock
fonte
23
@ Hooch, isso não está marcado como correto, porque o que é considerado "correto" não depende apenas do título da pergunta. Eu imagino que a maioria das pessoas, incluindo eu e o OP, primeiro lide com virtualpropriedades por meio do Entity Framework - mesmo que isso não esteja explícito no título do OP. A resposta aceita é porque ela toca no lado das coisas do Entity Framework e como / por que as virtualpropriedades são usadas nesse contexto.
Don Cheadle
22

Entendo a frustração dos OPs, esse uso do virtual não é para a abstração modelada pela qual o modificador virtual defacto é eficaz.

Se alguém ainda está lutando com isso, eu ofereceria meu ponto de vista, ao tentar manter as soluções simples e o jargão no mínimo:

O Entity Framework em uma peça simples utiliza carregamento lento, o que equivale a preparar algo para execução futura. Isso se encaixa no modificador 'virtual', mas há mais para isso.

No Entity Framework, o uso de uma propriedade de navegação virtual permite denotá-la como o equivalente a uma Chave Externa anulável no SQL. Você não precisa ingressar ansiosamente em todas as tabelas com chave ao executar uma consulta, mas quando precisa das informações, elas se tornam orientadas pela demanda.

Mencionei também anulável porque muitas propriedades de navegação não são relevantes a princípio. ou seja, em um cenário de cliente / pedidos, você não precisa esperar até o momento em que um pedido é processado para criar um cliente. Você pode, mas se tiver um processo de várias etapas para conseguir isso, poderá encontrar a necessidade de persistir os dados do cliente para conclusão posterior ou para implantação em pedidos futuros. Se todas as propriedades de navegação foram implementadas, você teria que estabelecer todos os campos de chave estrangeira e relacional ao salvar. Isso realmente apenas coloca os dados de volta na memória, o que anula o papel da persistência.

Portanto, embora possa parecer enigmático na execução real em tempo de execução, eu achei a melhor regra geral a ser usada: se você estiver produzindo dados (lendo em um modelo de exibição ou modelo serializado) e precisar de valores antes das referências, não use virtual; Se o seu escopo estiver coletando dados que podem estar incompletos ou que seja necessário pesquisar e não exija todos os parâmetros de pesquisa concluídos para uma pesquisa, o código fará bom uso da referência, semelhante ao uso das propriedades de valor nulo int? longo?. Além disso, abstrair sua lógica de negócios da coleta de dados até a necessidade de injetá-la traz muitos benefícios de desempenho, semelhantes a instanciar um objeto e iniciá-lo em nulo. O Entity Framework utiliza muita reflexão e dinâmica, o que pode prejudicar o desempenho, e a necessidade de ter um modelo flexível que possa ser dimensionado conforme a demanda é fundamental para gerenciar o desempenho.

Para mim, isso sempre fazia mais sentido do que usar jargões de tecnologia sobrecarregados, como proxies, delegados, manipuladores e outros. Depois de atingir o terceiro ou quarto idioma de programação, pode ficar confuso com eles.

Nathan Teague
fonte
14

É bastante comum definir propriedades de navegação em um modelo para serem virtuais. Quando uma propriedade de navegação é definida como virtual, ela pode tirar proveito de determinadas funcionalidades do Entity Framework. O mais comum é o carregamento lento.

O carregamento lento é um recurso interessante de muitos ORMs, pois permite acessar dinamicamente dados relacionados de um modelo. Ele não buscará desnecessariamente os dados relacionados até que eles sejam realmente acessados, reduzindo assim a consulta antecipada de dados do banco de dados.

Do livro "ASP.NET MVC 5 com Bootstrap e Knockout.js"

Hassan Rahman
fonte
3

No contexto do EF, marcar uma propriedade como virtual permite que o EF use carregamento lento para carregá-lo. Para que o carregamento lento funcione, o EF precisa criar um objeto proxy que substitua suas propriedades virtuais por uma implementação que carrega a entidade referenciada quando ela é acessada pela primeira vez. Se você não marcar a propriedade como virtual, o carregamento lento não funcionará com ela.

Shakeer Hussain
fonte
0

A palavra-chave virtual é usada para modificar um método, propriedade, indexador ou declaração de evento e permitir que ela seja substituída em uma classe derivada. Por exemplo, esse método pode ser substituído por qualquer classe que o herda:

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

Você não pode usar o modificador virtual com os modificadores estático, abstrato, privado ou de substituição. O exemplo a seguir mostra uma propriedade 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
fonte
Isso está totalmente fora de tópico, mano.
eru
0

Não podemos falar sobre membros virtuais sem nos referirmos ao polimorfismo . De fato, uma função, propriedade, indexador ou evento em uma classe base marcada como virtual permitirá a substituição de uma classe derivada.

Por padrão, os membros de uma classe não são virtuais e não podem ser marcados como modificadores estáticos, abstratos, privados ou de substituição.

Exemplo Vamos considerar o método ToString () no System.Object . Como esse método é membro de System.Object, é herdado em todas as classes e fornecerá os métodos ToString () para todos eles.

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

A saída do código anterior é:

VirtualMembersArticle.Company

Vamos considerar que queremos alterar o comportamento padrão dos métodos ToString () herdados de System.Object em nossa classe Company. Para atingir esse objetivo, basta usar a palavra-chave override para declarar outra implementação desse método.

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

Agora, quando um método virtual é chamado, o tempo de execução verifica se há um membro substituinte em sua classe derivada e o chama se presente. A saída do nosso aplicativo será então:

Name: Microsoft

De fato, se você verificar a classe System.Object, verá que o método está marcado como virtual.

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