01 internal override bool HasDeferredLoaders
02 {
03 get
04 {
05 foreach (MetaAssociation association in this.Type.Associations)
06 {
07 if (this.HasDeferredLoader(association.ThisMember))
08 {
09 return true;
10 }
11 }
12 foreach (MetaDataMember member in from p in this.Type.PersistentDataMembers
13 where p.IsDeferred && !p.IsAssociation
14 select p)
15 {
16 if (this.HasDeferredLoader(member))
17 {
18 return true;
19 }
20 }
21 return false;
22 }
23 }
很快就要找到关键点了,在看看this.HasDeferredLoader:
01 private bool HasDeferredLoader(MetaDataMember deferredMember)
02 {
03 if (!deferredMember.IsDeferred)
04 {
05 return false;
06 }
07 MetaAccessor storageAccessor = deferredMember.StorageAccessor;
08 if (storageAccessor.HasAssignedValue(this.current) || storageAccessor.HasLoadedValue(this.current))
09 {
10 return false;
11 }
12 IEnumerable boxedValue = (IEnumerable) deferredMember.DeferredSourceAccessor.GetBoxedValue(this.current);
13 return (boxedValue != null);
14 }
答案揭晓:storageAccessor.HasAssignedValue检测了Association属性是否被赋值(针对 EntityRef),storageAccessor.HasLoadedValue检测了Association属性是否已被加载(针对 EntitySet),如果没有任何的赋值或加载,并且由GetBoxedValue获取的延迟源对象(DeferredSource)不为空,则抛出异常。
要解释Attach为什么在这种情况下会抛出异常?首先要弄明白延迟源对象,这是LINQ2SQL实现延迟加载的关键。在延迟加载的模式 (DataContext.DeferredLoading=true)下,EntitySet和EntityRef属性只有当被访问时,才会产生数据库的查询。以EntitySet为例,当调用GetEnumerator()时:
1 public IEnumerator<TEntity> GetEnumerator()
2 {
3 this.Load();
4 return new Enumerator<TEntity>((EntitySet<TEntity>) this);
5 }
this.Load中调用了延迟源进行数据的加载: