Linq`Where`子句查询结果是否可以根据查询是延迟执行还是非延迟执行而有所不同?
下面是我的代码的简化版本。我希望Linq`Where`子句查询结果是否可以根据查询是延迟执行还是非延迟执行而有所不同?,linq,entity-framework-core,Linq,Entity Framework Core,下面是我的代码的简化版本。我希望p1和p2相等,并且p1\u在之后和p2\u在之后相等,因为GetPerson1()和GetPerson2()之间的唯一区别是.ToList()子句强制执行查询,而不改变选择条件 我的假设不正确吗?我在程序中发现了一个错误,归结起来是p1\u之后和p2\u之后是不同的(p2\u之后是空的,因为年龄已更改为26岁。但是p1\u之后仍然包含与p1相同的实例) 这种行为正常吗?这对我来说似乎不合逻辑,这就是我想检查的原因。尤其是p1_after.Age返回26,尽管已选
p1
和p2
相等,并且p1\u在之后和p2\u在之后相等,因为GetPerson1()
和GetPerson2()
之间的唯一区别是.ToList()
子句强制执行查询,而不改变选择条件
我的假设不正确吗?我在程序中发现了一个错误,归结起来是p1\u之后
和p2\u之后
是不同的(p2\u之后
是空的,因为年龄已更改为26岁。但是p1\u之后
仍然包含与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
/savechangesync
方法,因此您的更改不会反映在数据库中,只反映在代码中
现在,当您再次调用GetPerson1
方法时,您要求从数据库中获取年龄为25岁的人,并且由于您的更改没有反映数据库,因此您得到的结果与以前相同。
奇怪的是,为什么在调用GetPerson2
方法时会得到不同的结果,下面是我的猜测-在GetPerson2
方法中,您执行一个ToList
将所有人都带到内存中,然后在内存中过滤结果,而不是在过滤时执行getPerson1
方法发生在数据库级别,我的猜测是,当您第二次调用GetPerson2
方法时,因为您使用的是相同的上下文EntityFramework
使用一些缓存机制来检索所有的人,这使得您筛选的列表受您的更改影响,并且在该列表中没有任何年龄为25岁的人,这就是为什么p2_之后为空
要确认或拒绝我的假设,我将尝试三种不同的场景:
- 在两次调用之间保存对数据库的更改:
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将是相同的(都为null),因为现在您的更改也会反映到数据库中
- 为每个调用使用新的上下文:
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跟踪被禁用-即没有缓存
要么ef在使用.ToList()时使用某种形式的内存缓存,要么它是一个bug。我建议去他们的github,创建一个问题,看看开发人员对此有什么看法。如果您知道EF在从数据库重新获取实体时不会覆盖更改跟踪程序中的本地更改,这一切都是有意义的。你甚至会看到p1
、p2
和p1\u之后的p1\u都是对同一个对象的引用,而p2\u之后的p2\u仅仅是一个没有产生任何结果的查询的结果。我认为你描述的正是发生的事情。(这也是我在写这个问题时的假设。)但对我来说,这是错误的/不一致的,是框架中的一个问题。我无法调用SaveChanges()
的原因是,在代码的更深处,我仍然可以决定中止整个更改集。在这种情况下,我根本就不调用SaveChanges()
,也不会持久化任何内容。