【问题标题】:How to disable eager loading when using InMemoryDatabase使用 InMemoryDatabase 时如何禁用急切加载
【发布时间】:2021-03-09 15:41:31
【问题描述】:

我有一个未启用延迟加载的 EF.Core 2.1 DataContext。

我的配置如下:

services.AddDbContext<DataContext>(options =>  
    options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

我的测试使用相同的 DataContext,但使用不同的选项,如下所示:

options.UseInMemoryDatabase(databaseName: "ProjectSpecs")

这一切都很好,除了我在内存中的 DataContext 急切地加载所有内容。

如果我要求一个实体,它会加载所有相关的对象吗?

这意味着如果我想实际加载相关属性而忘记这样做,我的测试将在加载相关实体时通过。但在实际应用中,由于.include 被遗忘,它会失败。

我可以让内存中的 DataContext 的行为与真实的相同吗?

【问题讨论】:

  • 嘿,伙计,你想明白了吗?我遇到了同样的问题,甚至 MSDN 文档也没有太大帮助。如果您能找到答案,将不胜感激。谢谢!
  • 不怕,目前我只是凑合着看。如果我找到答案,我会在这里更新。
  • 今天刚发现同样的问题。我有一个通过的测试,但生产代码没有做正确的事情,因为内存数据库正在加载所有导航属性,但真正的上下文没有。

标签: entity-framework entity-framework-core


【解决方案1】:

我遇到了同样的问题,在阅读了一堆关于该主题的文章后,我得出的结论是,问题确实是因为被测代码是从 ChangeTracker 中读取的,其中测试代码已经组装了对象图。有了这些知识,我使用了我的 DbContext 并覆盖了 SaveChanges 方法,如下所示。

public override int SaveChanges()
{
    var affectedRows = base.SaveChanges();

    if (Database.ProviderName == "Microsoft.EntityFrameworkCore.InMemory")
    {
        ChangeTracker.Entries()
            .Where(e => e.Entity != null)
            .ToList()
            .ForEach(e => e.State = EntityState.Detached);
    }

    return affectedRows;
}

通过分离 ChangeTracker 中的每个对象,它会强制被测代码返回数据库,而不是从 ChangeTracker 中提取现有对象图。

【讨论】:

  • 很好,它成功了。一个小的改进可以是把它作为一个扩展,这样生产和测试代码就可以完全分离。喜欢: public static class DbContextExtensions { public static int SaveChangesIMTest(this DbContext dbContext) { var affectedRows = dbContext.SaveChanges(); dbContext.ChangeTracker.Entries() .Where(e => e.Entity != null) .ToList() .ForEach(e => e.State = EntityState.Detached);返回受影响的行; } }
【解决方案2】:

一个小而重要的观点。使用 Include() 是 eager 加载。让 EF 在需要时加载实体是 lazy 加载。您想禁用延迟加载,以便您可以测试预加载 - 正确使用 Include()。

默认情况下,延迟加载禁用的。要在测试代码中启用它,请将 UseLazyLoadingProxies() 添加到您的 DbContextOptions 中,就像在应用程序代码中一样。除非最好不要这样做,这样您就可以测试您是否正确地进行了急切加载。

这里的问题并不是严格来说您使用的是延迟加载,而是您使用相同的 DbContext 来配置测试数据,就像测试它一样。因此,数据保留在 DbContext 中,根本不会从内存数据库中加载。

只需确保为设置和测试使用不同的 DbContext。但是,它必须具有相同的数据库名称。事实上,您可以使用完全相同的选项对象。

【讨论】:

  • +1。这是关于如何在不触及非测试代码的情况下解决问题的一个很好的解释(尽可能走的路)。我还提供了an actual example 这正在实践中。谢谢。
  • 这应该被标记为正确答案
【解决方案3】:

我认为 Jasper Kent 的回答是更好的方法,因为非测试代码应该尽可能少地知道测试甚至存在(即不要因为自动测试需要而弄乱 SaveChanges)。

我对这个想法的实现如下:

InMemoryTestBase(由任何测试类继承)

protected IApplicationDbContext CreateContext()
{
    var options = new DbContextOptionsBuilder<ApplicationDbContext>()
        .UseInMemoryDatabase($"ApplicationDbContext_{Guid.NewGuid()}")
        .EnableSensitiveDataLogging(true)
        .Options;

    var dbContext = new ApplicationDbContext(options);

    Populate(dbContext);
    dbContext.SaveChanges();

    // getting another context instance
    var newContext = new ApplicationDbContext(options);
    return newContext;
}

private void Populate(IApplicationDbContext dbContext)
{
    dbContext.EnsureDeleted();

    // actual insert of test data in the in-memory database
}

GenericContextOperationServiceTests(使用此设置的示例类)

这个例子展示了一个通用服务的测试,它通过标识符获取实体,但也急切地加载它的细节。流利的断言用于断言。

需要深度/完整克隆以确保测试数据永远不会更改,并且并行运行的测试之间永远不会共享任何引用。

[Theory]
[InlineData(1)]
[InlineData(2)]
public async Task GetByIdWithIncludeReturnsEntityWithDetails(int entityId)
{
    var dbContext = CreateContext();

    var includes = new List<Expression<Func<MockModel, object>>> { e => e.MockModelDetail };
    var entity = await Instance(dbContext).GetById(entityId, includes);

    var expectedHeader = MockModelTestData.MockModelData.FirstOrDefault(e => e.Id == entityId);
    var expectedDetails = MockModelTestData.MockModelDetailData.Where(md => md.MockModelId == entityId).DeepClone().ToList();
    
    entity.Should().BeEquivalentTo(expectedHeader, 
        opt => opt.Including(e => e.Id).Including(e => e.Name));

    entity.MockModelDetail.ForEach(md =>
        md.Should().BeEquivalentTo(expectedDetails.First(ed => ed.Id == md.Id),
            opt => opt.Including(e => e.DetailName).Including(e => e.DateCreation))
    );
}

每个测试都创建自己的数据库上下文,以便它们并行运行(默认使用 XUnit)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-16
    • 2023-03-31
    • 1970-01-01
    • 2014-09-27
    相关资源
    最近更新 更多