Зачем использовать «виртуальный» для свойств класса в определениях модели Entity Framework?

223

В следующем блоге: 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при определении свойства в классе? Какой эффект это имеет?

Гари Джонс
источник
9
Вы просите понять общее назначение ключевого слова virtual в C # или как оно относится конкретно к Entity Framework?
М.Бабкок
2
@ M.Babcock: я спрашиваю, какова цель, поскольку это относится к свойствам, потому что я никогда не видел этого раньше.
Гари Джонс
1
Если вы знакомы с тем, как виртуальное ключевое слово влияет на полиморфизм в методах, то оно то же самое для свойств.
М.Бабкок
20
@ M.Babcock: как я мог сделать это более очевидным? Вопрос называется «Зачем использовать« виртуальный »для свойств в классах?».
Гари Джонс
2
@Gary - свойства getter / setter фактически статически компилируются в методы. Так что это не традиционные поля класса, такие как «общедоступный виртуальный ужин»;
Шан Плурд

Ответы:

248

Это позволяет Entity Framework создавать прокси вокруг виртуального свойства, чтобы свойство могло поддерживать отложенную загрузку и более эффективное отслеживание изменений. См. Какие эффекты могут иметь виртуальное ключевое слово в Entity Framework 4.1 POCO Code First? для более тщательного обсуждения.

Изменить, чтобы уточнить "создать прокси вокруг": под "создать прокси вокруг" я имею в виду конкретно то, что делает Entity Framework. Entity Framework требует, чтобы ваши навигационные свойства были помечены как виртуальные, чтобы поддерживалась отложенная загрузка и эффективное отслеживание изменений. См. Требования для создания прокси POCO .
Entity Framework использует наследование для поддержки этой функциональности, поэтому требует, чтобы определенные свойства были помечены как виртуальные в ваших POCO базового класса. Он буквально создает новые типы, которые происходят от ваших типов POCO. Таким образом, ваш POCO действует как базовый тип для динамически создаваемых подклассов Entity Framework. Вот что я имел в виду под «создать прокси вокруг».

Динамически создаваемые подклассы, которые создает Entity Framework, становятся очевидными при использовании Entity Framework во время выполнения, а не во время статической компиляции. И только если вы включите ленивую загрузку Entity Framework или измените функции отслеживания. Если вы решите никогда не использовать функции отложенной загрузки или отслеживания изменений Entity Framework (что не является значением по умолчанию), вам не нужно объявлять какие-либо из ваших свойств навигации как виртуальные. Затем вы несете ответственность за загрузку этих свойств навигации самостоятельно, используя то, что Entity Framework называет «быстрой загрузкой», или вручную извлекая связанные типы по нескольким запросам к базе данных. Вы можете и должны использовать ленивую загрузку и изменять функции отслеживания для своих навигационных свойств во многих сценариях.

Если вы создадите автономный класс и отметите свойства как виртуальные, а просто создадите и будете использовать экземпляры этих классов в своем собственном приложении, полностью выходя за рамки Entity Framework, тогда ваши виртуальные свойства не принесут вам никакой пользы. своя.

Изменить, чтобы описать, почему свойства будут помечены как виртуальные

Свойства, такие как:

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

Не являются полями и не должны рассматриваться как таковые. Они называются геттерами и сеттерами, и во время компиляции они преобразуются в методы.

//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, это позволяет динамически создаваемым классам переопределять внутренне сгенерированные getи setфункции. Если ваш получатель / установщик свойств навигации работает для вас при использовании Entity Framework, попробуйте изменить их на просто свойства, перекомпилировать и посмотреть, может ли Entity Framework по-прежнему функционировать должным образом:

 public virtual ICollection<RSVP> RSVPs;
Шан Плурд
источник
2
Что вы подразумеваете под «создать прокси вокруг»? Что на самом деле здесь происходит?
Гари Джонс
2
Привет Гэри, я пересмотрел свой ответ, чтобы уточнить, что я имею в виду под «создать прокси вокруг». Надеюсь, это немного поможет.
Шан Плурд
2
Сказать «свойства ... не являются свойствами» совершенно бесполезно. Все свойства реализованы как методы получения и / или установки, поэтому нет смысла говорить «это свойство действительно является методом получения и установки, а не свойством».
Бен Фойгт
1
Спасибо за ваш отзыв, Бен, я должен был уточнить, что «свойства не поля». Дайте мне знать, если у вас есть другие отзывы или вопросы.
Шан Плурд
Я изменил формулировку и добавил еще один пример кода, чтобы немного лучше объяснить «свойства - это не свойства». Пожалуйста, откатитесь, если вы этого не хотите.
Скотт Чемберлен
75

virtualКлючевое слово в C # позволяет метод или свойство быть переопределены дочерними классами. Дополнительную информацию см. В документации MSDN по ключевому слову «virtual».

ОБНОВЛЕНИЕ: Это не отвечает на вопрос, как в настоящее время задается, но я оставлю это здесь для любого, кто ищет простой ответ на оригинальный , не описательный вопрос, заданный.

M.Babcock
источник
23
@ Да, это не помечено как правильное, потому что то, что считается «правильным», зависит не только от названия вопроса. Я предполагаю, что большинство людей, включая меня и OP, сначала имеют дело со virtualсвойствами через Entity Framework - даже если это не указано в заголовке OP. Принятый ответ таков, потому что он касается стороны Entity Framework и того, как / почему virtualсвойства используются в этом контексте.
Дон Чидл
22

Я понимаю разочарование OPs, такое использование виртуального не для шаблонной абстракции, для которой эффективный модификатор defacto эффективен.

Если кто-то все еще борется с этим, я бы предложил свою точку зрения, поскольку я стараюсь сохранить решения простыми и жаргоном к минимуму:

Простая структура Entity Framework использует ленивую загрузку, что эквивалентно подготовке чего-либо для будущего выполнения. Это соответствует модификатору 'virtual', но это еще не все.

В Entity Framework использование свойства виртуальной навигации позволяет обозначать его как эквивалент обнуляемого внешнего ключа в SQL. Вы не должны охотно присоединяться к каждой таблице ключей при выполнении запроса, но когда вам нужна информация - она ​​становится управляемой спросом.

Я также упомянул nullable, потому что многие свойства навигации поначалу не актуальны. т.е. в сценарии клиент / заказы вам не нужно ждать, пока будет обработан заказ, чтобы создать клиента. Вы можете, но если у вас был многоэтапный процесс для достижения этой цели, вам может потребоваться сохранить данные клиента для последующего завершения или для размещения в будущих заказах. Если бы все свойства nav были реализованы, вам нужно было бы установить каждый внешний ключ и реляционное поле при сохранении. Это на самом деле просто возвращает данные обратно в память, что побеждает роль постоянства.

Поэтому, хотя это может показаться загадочным при реальном выполнении во время выполнения, я обнаружил, что лучшее практическое правило для использования будет таким: если вы выводите данные (считываете в модель представления или в сериализуемую модель) и вам нужны значения перед ссылками, не использовать виртуальный; Если ваша область собирает данные, которые могут быть неполными или требовать поиска и не требовать, чтобы каждый поисковый параметр был завершен для поиска, код будет эффективно использовать ссылку, аналогично использованию свойств int со значением nullable. длинный?. Кроме того, абстрагирование вашей бизнес-логики от сбора данных до тех пор, пока не потребуется вводить их, имеет много преимуществ для производительности, подобно созданию экземпляра объекта и его запуску с нуля. Entity Framework использует много отражений и динамики, которые могут ухудшить производительность, и необходимость иметь гибкую модель, которая может масштабироваться по требованию, имеет решающее значение для управления производительностью.

Для меня это всегда имело больше смысла, чем использование перегруженного технического жаргона, такого как прокси, делегаты, обработчики и тому подобное. Как только вы достигнете своего третьего или четвертого языка программирования, он может запутаться в этом.

Натан Тиг
источник
14

Весьма распространено определять свойства навигации в модели как виртуальной. Когда свойство навигации определено как виртуальное, оно может использовать определенные функции Entity Framework. Наиболее распространенным является ленивая загрузка.

Ленивая загрузка - хорошая функция многих ORM, потому что она позволяет вам динамически получать доступ к связанным данным из модели. Он не будет без необходимости извлекать связанные данные до тех пор, пока к ним фактически не будет получен доступ, тем самым уменьшая предварительный запрос данных из базы данных.

Из книги "ASP.NET MVC 5 с Bootstrap и Knockout.js"

Хасан Рахман
источник
3

В контексте EF пометка свойства как виртуального позволяет EF использовать ленивую загрузку для его загрузки. Чтобы ленивая загрузка работала, EF должен создать прокси-объект, который переопределяет ваши виртуальные свойства с помощью реализации, которая загружает ссылочную сущность при первом обращении к ней. Если вы не отметите свойство как виртуальное, то отложенная загрузка не будет работать с ним.

Шейкер Хуссейн
источник
0

Ключевое слово virtual используется для изменения метода, свойства, индексатора или объявления события и позволяет переопределять его в производном классе. Например, этот метод может быть переопределен любым классом, который его наследует:

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

Вы не можете использовать виртуальный модификатор с модификаторами 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";
            }
        }
    }
}
FatalMan
источник
Это совершенно не по теме, братан.
Эра
0

Мы не можем говорить о виртуальных членах, не обращаясь к полиморфизму . Фактически, функция, свойство, индексатор или событие в базовом классе, помеченные как виртуальные, позволяют переопределять производный класс.

По умолчанию члены класса не виртуальны и не могут быть помечены как таковые, если используются модификаторы static, abstract, private или override.

Пример Рассмотрим метод ToString () в System.Object . Поскольку этот метод является членом 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

Давайте рассмотрим, что мы хотим изменить стандартное поведение методов ToString (), унаследованных от System.Object в нашем классе Company. Для достижения этой цели достаточно использовать ключевое слово 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();
        ....
    }
}
Иван Порта
источник