Linq 'Where' 子句查询结果是否会根据查询是延迟执行还是非延迟执行而有所不同?



下面是我的代码的简化版本。我希望p1p2相等,p1_afterp2_after相等,因为GetPerson1()GetPerson2()之间的唯一区别是强制执行查询的.ToList()子句,而不对选择标准进行任何更改。

我的假设不正确吗?我在程序中发现了一个错误,该错误归结为p1_after并且p2_after不同(由于年龄已更改为 26,因此p2_after如预期的那样为空。但是p1_after仍然包含与p1相同的实例)。

这种行为正常吗?这对我来说似乎不合逻辑,这就是我想检查的原因。特别是当p1_after.Age返回 26 时,尽管p1_after已被选中,因此其Age为 25。

public void OnGet()
{
Person p1 = GetPerson1();
Person p2 = GetPerson2();
p1.Age = 26;
p2.Age = 26;
Person p1_after = GetPerson1(); // not null, but p1_after.Age is 26
Person p2_after = GetPerson2(); // null
}
public Person GetPerson1()
{
return _context
.Persons
.Where(p => p.Age == 25)
.SingleOrDefault();
}
public Person GetPerson2()
{
return _context
.Persons
.ToList()
.Where(p => p.Age == 25)
.SingleOrDefault();
}

这是一个疯狂的猜测,但我有一个假设,为什么你的程序会这样。

更改年龄后,您没有调用任何SaveChanges/SaveChangesAsync方法,因此您的更改不会反映在数据库中,而只会反映到您的代码中。

现在,当您再次调用该方法时GetPerson1您要求从数据库中获取年龄为25岁的人,并且由于数据库未反映在您的更改中,因此您将获得与以前相同的结果。
奇怪的部分是为什么当你调用GetPerson2方法时你会得到不同的结果,这是我的猜测 -GetPerson2方法中,你执行一个ToList,将所有的人带入内存,然后你在内存中过滤结果与GetPersons1方法相反,当过滤发生在数据库级别时, 我的猜测是,当您第二次调用该方法时GetPerson2因为您使用相同的上下文EntityFramework使用某种缓存机制来检索所有人员,这使您正在过滤的列表受到您的更改的影响,并且在此列表中没有任何年龄为 25 岁的人,这就是为什么p2_after为空。

为了确认或拒绝我的假设,我将尝试三种不同的方案:

  • 在两次调用之间保存对数据库的更改:

    public void OnGet()
    {
    Person p1 = GetPerson1();
    Person p2 = GetPerson2();
    p1.Age = 26;
    p2.Age = 26;
    _context.SaveChanges();
    Person p1_after = GetPerson1();
    Person p2_after = GetPerson2();
    }
    

    在这种情况下,我想p1_after和p2_after将是相同的(都是空的),因为现在您的更改也会反映到数据库中。

  • 为每个调用使用新的上下文:

    public void OnGet()
    {
    Person p1 = GetPerson1();
    Person p2 = GetPerson2();
    p1.Age = 26;
    p2.Age = 26;
    Person p1_after = GetPerson1();
    Person p2_after = GetPerson2();
    }
    public Person GetPerson1()
    {
    using(var context = new ...)
    {
    return context
    .Persons
    .Where(p => p.Age == 25)
    .SingleOrDefault();
    }
    }
    public Person GetPerson2()
    {
    using(var context = new ...)
    {
    return context
    .Persons
    .ToList()
    .Where(p => p.Age == 25)
    .SingleOrDefault();
    }
    }
    

    在这种情况下,我想p1_after和p2_after将是相同的(就像之前的 p1 和 p2 一样),因为现在您的更改不会反映到数据库中,并且没有任何缓存可行性,因为您每次调用都使用新的上下文。

  • 使用 AsNoTracking:

    public void OnGet()
    {
    Person p1 = GetPerson1();
    Person p2 = GetPerson2();
    p1.Age = 26;
    p2.Age = 26;
    Person p1_after = GetPerson1();
    Person p2_after = GetPerson2();
    }
    public Person GetPerson1()
    {
    return _context
    .Persons
    .AsNoTracking()
    .Where(p => p.Age == 25)
    .SingleOrDefault();
    }
    public Person GetPerson2()
    {
    return _context
    .Persons
    .ToList()
    .AsNoTracking()
    .Where(p => p.Age == 25)
    .SingleOrDefault();
    }
    

    在这种情况下,我想p1_after和p2_after将是相同的(就像之前的 p1 和 p2 一样),因为现在 EF 跟踪被禁用 - AKA 没有缓存。

最新更新