【问题标题】:Why does .NET Core scope affect how db context updates are visible?为什么 .NET Core 范围会影响数据库上下文更新的可见性?
【发布时间】:2021-04-29 12:06:11
【问题描述】:

我在单元测试中发现了 .NET dbContext 和范围界定的一些有趣行为,但我无法弄清楚为什么会发生这种情况。希望有人知道这是为什么。

这是来自测试方法。以下是步骤。为了清楚起见,我已尝试对其进行编辑。

  1. 测试类安装程序添加依赖注入所需的服务,并将数据库创建为 InMemory db,并为测试数据播种。
... add services ...
 services.AddDbContext<ApplicationDbContext>(options => options.UseInMemoryDatabase("ApplicationDb"));
_serviceProvider = services.BuildServiceProvider();
_serviceScopeFactory = _serviceProvider.GetRequiredService<IServiceScopeFactory>();
_dbContext = _serviceProvider.GetRequiredService<MyDbContext>();
... seed data ...
  1. 测试方法修改一些数据,然后调用被测试的目标方法。
//signature: public async Task TargetMethod(SomeTaskDto taskDto, IServiceScopeFactory serviceScopeFactory)
... modify some data using _dbContext created in Setup ...
var _myClass =_serviceProvider.GetRequiredService<MyClass>();
await _myClass.TargetMethod( taskDto, _serviceScopeFactory );
  1. 然后目标方法使用_serviceScopeFactory 获取数据库上下文并修改一些数据。
 using (var scope = serviceScopeFactory.CreateScope())
            {
                var _db = scope.ServiceProvider.GetService<MyDbContext>();
... changes made by calling test method are visible here.
... modify some data ...
                await _db.SaveChangesAsync();
... other stuff ...
          } //end scope
  1. 回到测试方法,调用目标方法后,更改在原来的db上下文中是不可见的:
  var entityShouldBeModified = _dbContext.Products.Where( x => x.Id = idOfModifiedEntity ).FirstOrDefault();
  //this fails:
  Assert.AreEqual( expectedUpdatedValue, entityShouldBeModified.PropertyWhichShouldBeModified );
  1. 如果我在调用目标方法后修改测试方法以创建新范围,并获取新的数据库上下文,则目标方法中的更新数据现在可见:
   using(var scope = _serviceScopeFactory.CreateScope())
            {
                var db = scope.ServiceProvider.GetService<MyDbContext>();
                db.SetAuthenticatedUser(_user); 
                var entityShouldBeModified = db.Products.Where( x => x.Id = idOfModifiedEntity ).FirstOrDefault();
               //this works:
               Assert.AreEqual( expectedUpdatedValue, entityShouldBeModified.PropertyWhichShouldBeModified );
          }

这只是在单元测试中使用 InMemory dbs 发生的事情吗? (我想我真的应该在 Sql Server 数据库上尝试一下。) 即使只是在 InMemory dbs 上,为什么测试方法所做的更改对目标方法可见,而目标方法所做的更改对调用测试方法不可见?

为什么我必须创建一个新的范围和新的数据库上下文才能看到目标方法所做的更改?

谢谢,任何解释表示赞赏。

【问题讨论】:

    标签: c# .net-core dependency-injection scope


    【解决方案1】:

    我认为这是因为 DbContext 是一个缓存。
    您不会读取最新数据,您的修改仍将应用于数据库。
    如下所示的实验。

    var record = dbContext1.Records.First();
    using (var scope = services.CreateScope())
    {
        var dbContext2 = scope.ServiceProvider.GetRequiredService<TestDbContext>();
        
        Debug.WriteLine($"Recourds.Count in dbContext1 = {dbContext1.Records.Count()}");
        Debug.WriteLine($"Recourds.Count in dbContext2 = {dbContext2.Records.Count()}");
        Debug.WriteLine($"1st Record.Name in dbContext1 = {dbContext1.Records.Single(o => o.Id == 1).Name}");
        Debug.WriteLine($"1st Record.Name in dbContext2 = {dbContext2.Records.Single(o => o.Id == 1).Name}"); //if you comment this line, the name won't be cached
    
        dbContext2.Records.Add(new DailyRecord()
        {
            Name = "Sansas",
            Date = DateTime.Today,
        }); // add a record
        dbContext2.SaveChanges();
    
        record.Name = "Candy"; //change name
        dbContext1.SaveChanges();
    
        Debug.WriteLine($"Recourds.Count in dbContext1 = {dbContext1.Records.Count()}");
        Debug.WriteLine($"Recourds.Count in dbContext2 = {dbContext2.Records.Count()}");
        Debug.WriteLine($"1st Record.Name in dbContext1 = {dbContext1.Records.Single(o => o.Id == 1).Name}");
        Debug.WriteLine($"1st Record.Name in dbContext2 = {dbContext2.Records.Single(o => o.Id == 1).Name}");
    

    “dbContext1 中的记录数 = 1”
    “dbContext2 中的 Records.Count = 1”
    “dbContext1 中的 Record.Name = Sansas”
    “dbContext2 中的 Record.Name = Sansas”
    “dbContext1 中的 Records.Count = 2”
    “dbContext2 中的 Records.Count = 2”
    “dbContext1 中的 Record.Name = Candy”
    “dbContext2 中的 Record.Name = Sansas”

    dbContext2 读取缓存的名称。

    【讨论】:

    • 谢谢。我会试试这个。我的猜测是,如果在退出现有作用域后创建了一个新作用域,并且在第二个作用域中创建了一个 dbContext3,那么 dbContext3 会看到更新后的第一条记录名称为“Candy”。这行 - dbContext1.Records.First(); - 创建一个默认记录?另外,感谢您提供有关在未实际访问该属性时不会缓存名称字段的提示。
    • 什么是“- 创建默认记录?”意思是?该行获取数据库中的第一条也是唯一一条记录。
    • 好问题@Sungfu Chiu - 我不记得我在问什么。在您的示例中,您是否假设 dbContext1 已经填充了一条记录,所以 dbContext1.Records.First() 将返回该现有记录?
    猜你喜欢
    • 2016-06-16
    • 2020-07-23
    • 1970-01-01
    • 1970-01-01
    • 2010-10-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多