0

EDIT: This question has been closed because someone thought it is a duplicate for another question on SO. It is not. The duplicate question, and all of the (old) docs I've found, deal with adding virtual to entity class navigation properties to enable lazy loading. This question is about what happens if I add virtual to the DbContext DbSet<> property.

We now return you to the original post:

I'm looking for definitive guidance about the effect of adding virtual to my existing DbSet DbContext properties in EF Core 7 or 8.

This came about because I want to use the Moq.EntityFrameworkCore NuGet package to mock my DbContext, which requires my DbSet<TableName> statements to be virtual.


EDIT: The above statement is the intended, complete question. The following is the original version of the question, which includes my observation that the virtual keyword in question has something to do with "lazy loading". I now know that is incorrect, and I understand that my question (above) implies an additional question: Why can my DbSet and my entity classes have virtual navigation properties and DbSet properties? Where is the concrete implementation of these virtual properties? The answer explains that very nicely.

I am using EF Core 8. Earlier versions of EF Core provided lazy loading features related to decorating the DbSet<TableName> statements in the DbContext class with the virtual keyword, for example:

public virtual DbSet<PatientTestEntity> PatientTestEntities { get; set; }

The current documentation makes no mention of this (as far as I can tell).

I'm looking for definitive guidance about the effect of adding virtual to my existing DbSet DbContext properties in EF Core 7 or 8.

This all came about because I want to use the Moq.EntityFrameworkCore NuGet package to mock my DbContext, which requires my DbSet<TableName> statements to be virtual.

Bob.at.Indigo.Health
  • 11,023
  • 13
  • 64
  • 111
  • Lazy loading never required DbSets to be virtual. Only navigation properties. Click on "Lazy loading" in your documentation link. – Gert Arnold Feb 26 '23 at 18:43
  • That is exactly why I wrote the question. All the commentary and (old) docs I dug out deal with lazy loading and virtual navigation properties. I'm looking for a definitive answer about **what happens if I add `virtual` to the DbContext `DbSet<>` property. – Bob.at.Indigo.Health Feb 26 '23 at 22:34
  • Nothing. EF Core will call your property setter. It will not extend your `DbContext` to replace the getter implementation. See https://github.com/dotnet/efcore/blob/0993a63ea79bece20e782aaed7b5bfa448ba7bc6/src/EFCore/DbContext.cs#L124 – Jeremy Lakeman Feb 26 '23 at 23:47
  • *lazy loading features related to decorating the DbSet statements in the DbContext class with the virtual keyword* -- How can one not conclude that you think virtual DbSets have to do with lazy loading? In the end you question boils down to: what does the `virtual` modifier do in C#? – Gert Arnold Feb 27 '23 at 07:45
  • Yeah, I was confused by all of the "lazy loading" stuff I found online while researching the question. If you Google "ef core virtual dbset", 100% of the articles talk about virtualizing the navigation properties. Even @jepozdemir's answer talks mainly about lazy loading and virtual navigation properties. When I wrote the OP I still had "lazy loading" on the brain. And, yes, I know what the `virtual` modifier does. The part I didn't understand is why the DBSet property could be **either** concrete or virtual, and things seemed to work the same. EF Magic. – Bob.at.Indigo.Health Feb 28 '23 at 00:45

1 Answers1

0

The answer to the OP is: Adding virtual to the DbSet<> property in the DbContext has no effect. See below for an explanation of how EF creates proxies from the original DbContext and entity classes.

I think, if a library needs (such as EF or some Moq test libraries) to generate a proxy class at runtime to achieve its purpose, it's a good practise to sign all complex type properties as virtual to enable extending actual class.

Let's illustrate this through the Lazy loading feature of the Entity framework;

Ef needs to virtual keyword on the navigation properties to enable lazy loading mechanism. (also "LazyLoadingEnabled" property of the configuration must be enabled)

For Lazy Loading purpose, Entity Framework needs a hook, i.e. it must know when you access the navigation property, which is in general impossible because this would need additional code in the getter of this property. Unfortunately Entity Framework is unable to alter the POCO entity classes because it's your code, but what it can do is deriving from your class and overriding the property to inject it's magic.

To do so, EF uses proxy class generation mechanism. Which means, it creates what is called a proxy, i.e. it creates a class at runtime which derives from your POCO class, like:

public class EntityTypeProxy : EntityType
{
    public override ICollection<AnotherEntity> AnotherEntities
    {
        get { /* Load child data here */ }
        set { /* ... */ }
    }
}

So what you get when you retrieve the entities is actually a bunch of proxy instances, which you can confirm during debugging.

If you would not add the virtual keyword to the navigation property or if you sealed the class, Entity Framework could not override the property or could not derive from the class at all and thus lazy loading would not work.

Bob.at.Indigo.Health
  • 11,023
  • 13
  • 64
  • 111
jepozdemir
  • 260
  • 1
  • 4
  • 1
    Thanks @jepozdemir. That's a great explanation of why we sometimes see the `virtual` keyword used with EF entities, but it doesn't directly answer the simply question in the OP: What is the effect of adding `virtual` to existing DbContext `DbSet<>` statements. Apparently, the answer is "No effect, unless you add additional code to enable lazy loading." Would you be so kind as to add this to the beginning of your answer (but keep the rest of your answer!). – Bob.at.Indigo.Health Feb 25 '23 at 23:40
  • Thanks again. I'm tempted to mark this as the correct answer, but as Gert Arnold points out in the comment to the OP, this answer doesn't directly answer the actual question, which didn't deal with navigation properties at all. I will restate the OP to clarify... – Bob.at.Indigo.Health Feb 26 '23 at 22:38