DbSet<>和virtual DbSet<>有什么区别?

63
在实体框架(Entity Framework)Code First中,当我声明实体时,必须使用DbSet<>类型的属性。例如:

在 Entity Framework Code First 中,当我声明实体时,我必须使用 DbSet<> 类型的属性。例如:

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

最近我遇到了将DbSet<>声明为虚拟的情况。

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

有什么区别?启用了哪些EF功能?

2个回答

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

在导航属性上使用virtual关键字可以启用延迟加载机制,但必须启用配置的LazyLoadingEnabled属性。

在AccountType :: AccountCodes导航属性上使用virtual关键字将在程序访问该属性时,只要db上下文仍然活动,即会加载所有帐户代码。

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

派生的DbContext类中的虚拟关键字(虚拟DbSet <>)用于测试目的(模拟DbSet属性),但在此情况下,虚拟关键字与延迟加载无关。

===== 更新 =====

通常,我们会针对服务/逻辑进行测试,例如我们有另一层帐户类型服务如下。该服务通过构造函数使用某种依赖注入接受DbContext实例。

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

现在我们需要测试账户类型服务,在这种情况下,我使用了mstest和automoq来创建模拟类。

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

请注意,默认情况下启用了LazyLoad功能:https://learn.microsoft.com/zh-cn/dotnet/api/system.data.entity.infrastructure.dbcontextconfiguration.lazyloadingenabled?view=entity-framework-6.2.0 - Brondahl
那么为什么在测试中需要虚拟? - Furkan Gözükara

13

请注意,在EF Core(目前版本为1.0和2.0)仍未支持懒加载机制,因此使用"virtual"或不使用都没有区别。

顺便说一句,脚手架生成的"virtual"关键字可能会在未来版本的EF Core中支持懒加载技术!

在EF Core 2.1中,开发团队添加了对懒加载的支持。更多信息在这里


2
基本虚拟意味着它可以在派生类中被重写。 - Nikolaus

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接