背景:我正在使用EF6和数据库优先。
我遇到了困扰我的情景。创建新对象后,使用新对象填充导航属性并调用SaveChanges,将重置导航属性。在SaveChanges调用之后引用导航属性的第一行代码将最终从数据库中重新获取数据。这是预期的行为吗?有人可以解释为什么它会这样做吗?以下是我的方案的示例代码块:
using (DbContext context = new DbContext) {
Foo foo = context.Foos.Create();
context.Foos.Add(foo);
...
Bar bar = context.Bars.Create();
context.Bars.Add(bar);
...
FooBar foobar = context.FooBars.Create();
context.FooBars.Add(foobar)
foobar.Foo = foo;
foobar.Bar = bar;
//foo.FooBars is already populated, so 1 is returned and no database query is executed.
int count = foo.FooBars.Count;
context.SaveChanges();
//This causes a new query against the database - Why?
count = foo.FooBars.Count;
}
答案 0 :(得分:1)
我不能说100%,但我怀疑这种行为是专门做出的。
至于它为什么会这样运行,问题的根源是DbCollectionEntry.IsLoaded
属性为false
,直到导航属性被隐式延迟加载或使用Load
方法显式加载。
当实体处于已添加状态时,似乎还会抑制延迟加载,这就是第一次调用不会触发重新加载的原因。但是一旦你调用SaveChanges
,实体状态变为未修改,虽然集合实际上没有设置为“null”或清除,但下次尝试访问集合属性将触发延迟重新加载
// ...
Console.WriteLine(context.Entry(foo).Collection(e => e.FooBars).IsLoaded); // false
//foo.FooBars is already populated, so 1 is returned and no database query is executed.
int count = foo.FooBars.Count;
// Cache the collection property into a variable
var foo_FooBars = foo.FooBars;
context.SaveChanges();
Console.WriteLine(context.Entry(foo).State); // Unchanged!
Console.WriteLine(context.Entry(foo).Collection(e => e.FooBars).IsLoaded); // false
// The collection is still there, this does not trigger database query
count = foo_FooBars.Count;
// This causes a new query against the database
count = foo.FooBars.Count;
如果您想避免重新加载,解决方法是明确将IsLoaded
属性设置为true
。
// ...
context.SaveChanges();
context.Entry(foo).Collection(e => e.FooBars).IsLoaded = true;
context.Entry(bar).Collection(e => e.FooBars).IsLoaded = true;
// No new query against the database :)
count = foo.FooBars.Count;
答案 1 :(得分:0)
为什么你不期望它不重新查询数据库? EF没有持有数据库的完整缓存副本。如果你有一些触发器在你改变的东西之后插入一行,或者某些列作为一个功能列而EF不知道确切的计算结果怎么办?它需要获取最新数据,否则您的Count
将不正确。
答案 2 :(得分:0)
我认为问题是“背景”的概念正在被混淆。当您处于EF环境中时,您处于“连接”状态。它并不总是能够巧妙地重用您的数据。它知道“我通过设置与之对话的上下文作为数据库级别的扩展存在”。如果你有一个你创建的对象或一个已经存在的对象说'FooBars'并且你这样做:
foo.Foobars.(some operation)
如果foo是你的上下文而Foobars是它的一些对象,它被引用作为上下文的扩展。如果您希望实现重用而不会导致数据库往返,请收集上下文范围之外的一个或多个对象,如:
var foobars= new Foobars;
using(var foo = new new DbContext)
{
foobars = foo.FooBars;
}
var cnt = foobars.Count();
一般来说,使用EF,我看到很多人做了一些事情就像整个'使用(var context = new Efcontext())'整个方法可能很长,并且无处不在地执行这个过程。开箱即用并不坏,但是到处都这样做我只想说:“你需要一遍又一遍地打开数据库连接吗?”有时你会这样做,但大部分时间你都没有。