What is the difference between DbSet<> and virtual DbSet<>?

Entity FrameworkEf Code-First

Entity Framework Problem Overview


In Entity Framework Code First, when I declare entities I have to use DbSet<> type of properties for that. For example:

public DbSet<Product> Products { get; set; }
public DbSet<Customer> Customers { get; set; }

Recently I've met DbSet<> declared as virtual.

public virtual DbSet<Product> Products { get; set; }
public virtual DbSet<Customer> Customers { get; set; }

What is the difference? What EF functionalities is enabled?

Entity Framework Solutions


Solution 1 - Entity Framework

public class AppContext : DbContext
{
    public AppContext()
    {
        Configuration.LazyLoadingEnabled = true;
    }

    public virtual DbSet<AccountType> AccountTypes { get; set; }
}

public class AccountType
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public virtual ICollection<AccountCode> AccountCodes { get; set; }
}

public class AccountCode
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public Guid AccountTypeId { get; set; }
    public virtual AccountType AccountType { get; set; }
}

The virtual keyword on the navigation properties are used to enable lazy loading mechanism, but the LazyLoadingEnabled property of the configuration must be enabled.

The virtual keyword on AccountType::AccountCodes navigation property will load all account codes the moment there is a programmatically access to that property while the db context are still alive.

using (var context = new AppContext())
{
    var accountType = context.AccountTypes.FirstOrDefault();
    var accountCodes = accountType.AccountCodes;
}

While the virtual keyword on the derived DbContext class (virtual DbSet<>) is used for testing purpose (mocking the DbSet property), virtual keyword in this case is not related to lazy loading.

===== update =====

Usually we are doing the testing against the service / logic, for example we have another layer for the account type service as follow. And the service accepts the db context instance using some kind of dependency injection through the constructor.

public class AccountTypeService
{
    public AppContext _context;

    public AccountTypeService(AppContext context)
    {
        _context = context;
    }

    public AccountType AddAccountType(string name)
    {
        var accountType = new AccountType { Id = Guid.NewGuid(), Name = name };
        _context.AccountTypes.Add(accountType);
        _context.SaveChanges();
        return accountType;
    }
}

And now we need to test the account type service, in this case I used mstest and automoq to create the mock class.

[TestClass]
public class AccountTypeServiceTest
{
    [TestMethod]
    public void AddAccountType_NormalTest()
    {
        // Arranges.
        var accountTypes = new List<AccountType>();
        var accountTypeSetMock = new Mock<DbSet<AccountType>>();
        accountTypeSetMock.Setup(m => m.Add(It.IsAny<AccountType>())).Callback<AccountType>(accountType => accountTypes.Add(accountType));

        var appContextMock = new Mock<AppContext>();
        appContextMock.Setup(m => m.AccountTypes).Returns(accountTypeSetMock.Object);
        var target = new AccountTypeService(appContextMock.Object);

        // Acts.
        var newAccountType = target.AddAccountType("test");

        // Asserts.
        accountTypeSetMock.Verify(m => m.Add(It.IsAny<AccountType>()), Times.Once());
        appContextMock.Verify(m => m.SaveChanges(), Times.Once());
        Assert.AreEqual(1, accountTypes.Count);
        Assert.IsNotNull(newAccountType);
        Assert.AreNotEqual(Guid.Empty, newAccountType.Id);
        Assert.AreEqual("test", newAccountType.Name);
    }
}

Solution 2 - Entity Framework

Note that in EF Core (currently 1.0 and 2.0) still not supporting LazyLoading scheme, so using with "virtual" or without does not make a different.

FYI. The scaffolding generate "virtual" keyword may be supporting LazyLoading technique in the future version of EF Core!

In EF Core 2.1 the developer team added support for LazyLoading. More information is here

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionmagosView Question on Stackoverflow
Solution 1 - Entity FrameworkYuliam ChandraView Answer on Stackoverflow
Solution 2 - Entity FrameworkTom MemaneeView Answer on Stackoverflow