context.Pupils.Attach(pupil);
pupil.SchoolclassCodes集合是空的,但必须有一个schoolclassCode,因为在底部的LoadAsync方法是带有Id I查询的SchoolclassCode
await context.Entry(pupil).Collection(p => p.SchoolclassCodes).Query().Where(s => s.Id == schoolclassCodeId).LoadAsync();
用pupil.SchoolclassCodes集合中的一个schoolclassCode填充学生
await context.Entry(pupil).Collection(p => p.SchoolclassCodes).LoadAsync();
为什么底部LoadAsync 有效但顶部LoadAsync ?
更新
很抱歉有关pupil.SchoolclassCodeId的混淆,这是[NotMappedAttribute]
这是关系。
Pupil N有M SchoolclassCodes。
每个实体都有另一个实体的集合。
最后一个LoadAsync正如我所说,关系设置没有问题。
问题是第一个LoadAsync不能如上所述工作!
LAZY装载完全禁用!我在任何地方都没有使用虚拟道具!
更新2
public class SchoolclassCode
{
public SchoolclassCode()
{
Pupils = new HashSet<Pupil>();
}
public int Id { get; set; }
public ISet<Pupil> Pupils { get; set; }
}
public class Pupil
{
public Pupil()
{
SchoolclassCodes = new HashSet<SchoolclassCode>();
}
public int Id { get; set; }
public ISet<SchoolclassCode> SchoolclassCodes { get; set; }
[NotMapped]
public int SchoolclassCodeId { get; set; }
}
答案 0 :(得分:2)
Pupil.SchoolclassCodeId
字段显然未用于此问题,所以让我们忘记它。
您的第二个问题:
await context.Entry(pupil).Collection(p => p.SchoolclassCodes).LoadAsync();
按预期工作。我们可以使用以下代码进行验证:
await context.Entry(pupil).Collection(p => p.SchoolclassCodes).LoadAsync();
Console.WriteLine("IsLoaded = " + context.Entry(pupil).Collection(p => p.SchoolclassCodes).IsLoaded);
foreach (var code in pupil.SchoolclassCodes)
Console.WriteLine(" " + code.Id);
假设pupil
在其SchoolclassCodes
中有三个元素,则IsLoaded
将为true
,而foreach
循环将显示三个ID。
然后是您的第一个查询:
await context.Entry(pupil).Collection(p => p.SchoolclassCodes).Query().Where(s => s.Id == schoolclassCodeId).LoadAsync();
让我们测试一下:
var pupil = context.Pupils.First();
var schoolclassCodeId = 1;
await context.Entry(pupil).Collection(p => p.SchoolclassCodes).Query().Where(s => s.Id == schoolclassCodeId).LoadAsync();
Console.WriteLine("IsLoaded = " + context.Entry(pupil).Collection(p => p.SchoolclassCodes).IsLoaded);
foreach (var code in pupil.SchoolclassCodes)
Console.WriteLine(" " + code.Id);
假设确实存在SchoolclassCode
Id
为1
,AsyncLoad
应该只将一个SchoolclassCode
加载到内存中。然而,在输出中,您可以看到IsLoaded = false
,foreach
完全没有任何内容!为什么呢?
好吧,首先AsyncLoad
不适用于Collection(p => p.SchoolclassCodes)
,而是IQueryable
从IsLoaded
派生,因此false
应为SchoolclassCode
,这是可以理解的
但是一个foreach (var code in context.SchoolclassCodes.Local)
Console.WriteLine(" " + code.Id);
确实被加载到上下文中:
foreach
此1
输出一个SchoolclassCode
。那么为什么我们无法在pupil.SchoolclassCodes
中找到SchoolclassCode
?
答案是:Pupil
和SchoolclassCode
之间的关系是多对多的。在这种情况下,实体框架不会进行关系修正,即自动将Pupil.SchoolclassCodes
添加到Query
,因此您不会在那里看到它。如果你真的想要修复关系,你必须手动完成。
引自MSDN:
ToList
方法提供对实体框架在加载相关实体时将使用的基础查询的访问。然后,您可以在执行查询之前使用LINQ将过滤器应用于查询,方法是调用LINQ扩展方法,例如Load
,Query
等。public class Pupil { public Pupil() { Book = new HashSet<Book>(); SchoolclassCodes = new HashSet<SchoolclassCode>(); } public int Id { get; set; } public ISet<Book> Books { get; set; } public ISet<SchoolclassCode> SchoolclassCodes { get; set; } } public class Book { public int Id { get; set; } public Pupil Pupil { get; set; } } public class SchoolclassCode { public SchoolclassCode() { Pupils = new HashSet<Pupil>(); } public int Id { get; set; } public ISet<Pupil> Pupils { get; set; } }
方法可以同时使用引用和集合导航属性,但对于可用于仅加载集合的一部分的集合最有用。
有点令人困惑。这似乎与我的论点相矛盾,但事实并非如此。实际上,在上面引用了“#34; load&#34;意味着加载到上下文中,而不是加载到导航属性中,所以MSDN和我的回答都是正确的。为了证明我的主张,让我们从几个实验开始,然后我们将深入研究源代码。
出于演示目的,我们在模型中添加了另一个类:
Pupil
SchoolclassCode
和Pupil
之间的关系与以前一样是多对多关系,Book
与新添加的public class SchoolEntities: DbContext
{
public SchoolEntities()
: base("name=SchoolEntities")
{
}
public DbSet<Pupil> Pupils { get; set; }
public DbSet<Book> Books { get; set; }
public DbSet<SchoolclassCode> SchoolclassCodes { get; set; }
}
之间的关系是一对一的关系许多。上下文类是:
Pupil (Id = 1)
the Books property contains:
Book (Id = 1)
Book (Id = 2)
the SchoolclassCodes property contains:
SchoolclassCode (Id = 1)
SchoolclassCode (Id = 2)
SchoolclassCode (Id = 3)
我们在数据库中有以下条目:
Load
我们将相关数据直接加载到导航属性中。为简单起见,我们使用LoadAsync
方法而不是using (var context = new SchoolEntities()) {
Console.WriteLine("Books direct load");
var pupil = context.Pupils.First();
context.Entry(pupil).Collection(p => p.Books).Load();
Console.WriteLine(" IsLoaded = " + context.Entry(pupil).Collection(p => p.Books).IsLoaded);
Console.WriteLine(" Items in the pupil:");
foreach (var item in pupil.Books)
Console.WriteLine(" " + item.Id);
Console.WriteLine(" Items in the context:");
foreach (var item in context.Books.Local)
Console.WriteLine(" " + item.Id);
}
using (var context = new SchoolEntities()) {
Console.WriteLine("SchoolclassCodes direct load");
var pupil = context.Pupils.First();
context.Entry(pupil).Collection(p => p.SchoolclassCodes).Load();
Console.WriteLine(" IsLoaded = " + context.Entry(pupil).Collection(p => p.SchoolclassCodes).IsLoaded);
Console.WriteLine(" Items in the pupil:");
foreach (var item in pupil.SchoolclassCodes)
Console.WriteLine(" " + item.Id);
Console.WriteLine(" Items in the context:");
foreach (var item in context.SchoolclassCodes.Local)
Console.WriteLine(" " + item.Id);
}
。它们完全相同,只是前者是同步的而后者是异步的。代码:
Books direct load
IsLoaded = True
Items in the pupil:
1
2
Items in the context:
1
2
SchoolclassCodes direct load
IsLoaded = True
Items in the pupil:
1
2
3
Items in the context:
1
2
3
和输出:
Books
实验分为两部分,一部分用于SchoolclassCodes
,另一部分用于Load
。使用两个上下文来确保这两个部分不会相互干扰。我们使用集合的IsLoaded
方法将相关数据直接加载到导航属性中。结果表明:
true
属性设置为pupil.Books
; pupil.SchoolclassCodes
和context.Books.Local
); context.SchoolclassCodes.Local
和Query
)。我们使用Where
方法加载部分相关数据,然后加载using (var context = new SchoolEntities()) {
Console.WriteLine("Books partial query load");
var pupil = context.Pupils.First();
context.Entry(pupil).Collection(p => p.Books).Query().Where(s => s.Id == 1).Load();
Console.WriteLine(" IsLoaded = " + context.Entry(pupil).Collection(p => p.Books).IsLoaded);
Console.WriteLine(" Items in the pupil:");
foreach (var item in pupil.Books)
Console.WriteLine(" " + item.Id);
Console.WriteLine(" Items in the context:");
foreach (var item in context.Books.Local)
Console.WriteLine(" " + item.Id);
}
using (var context = new SchoolEntities()) {
Console.WriteLine("SchoolclassCodes partial query load");
var pupil = context.Pupils.First();
context.Entry(pupil).Collection(p => p.SchoolclassCodes).Query().Where(s => s.Id == 1).Load();
Console.WriteLine(" IsLoaded = " + context.Entry(pupil).Collection(p => p.SchoolclassCodes).IsLoaded);
Console.WriteLine(" Items in the pupil:");
foreach (var item in pupil.SchoolclassCodes)
Console.WriteLine(" " + item.Id);
Console.WriteLine(" Items in the context:");
foreach (var item in context.SchoolclassCodes.Local)
Console.WriteLine(" " + item.Id);
}
:
context.Entry(pupil)...
大部分代码与实验1相同;请注意以Books partial query load
IsLoaded = False
Items in the pupil:
1
Items in the context:
1
SchoolclassCodes partial query load
IsLoaded = False
Items in the pupil:
Items in the context:
1
开头的行。输出:
IsLoaded
看到区别?
false
现在都是SchoolclassCodes
; Books
案例中的导航属性,而会进入Books
案例。差异是由关系类型引起的:SchoolclassCodes
是一对多,而Query
是多对多。实体框架以不同的方式处理这两种类型。
那么如果我们在没有Where
的情况下使用using (var context = new SchoolEntities()) {
Console.WriteLine("Books full query load");
var pupil = context.Pupils.First();
context.Entry(pupil).Collection(p => p.Books).Query().Load();
// output statements omitted...
}
using (var context = new SchoolEntities()) {
Console.WriteLine("SchoolclassCodes full query load");
var pupil = context.Pupils.First();
context.Entry(pupil).Collection(p => p.SchoolclassCodes).Query().Load();
// output statements omitted...
}
该怎么办?我们来看看:
Books full query load
IsLoaded = False
Items in the pupil:
1
2
Items in the context:
1
2
SchoolclassCodes full query load
IsLoaded = False
Items in the pupil:
Items in the context:
1
2
3
输出:
IsLoaded
即使我们加载了所有相关数据,SchoolclassCodes
仍然是假的,加载的数据仍然不会进入Load()
。显然Query().Load()
与Query
不同。
那么引擎盖下发生了什么? EF6的源代码可以在CodePlex找到。以下context.Entry(pupil).Collection(p => p.Books).Query()
电话:
string sourceQuery = GenerateQueryText();
var query = new ObjectQuery<TEntity>(sourceQuery, _context, mergeOption);
AddQueryParameters(query);
return query;
可以追溯到下面的代码片段,为了清楚起见,我编辑了这段代码:
TEntity
此处Book
为_context
,ObjectContext
为DbContext
后面的sourceQuery
,SELECT VALUE [TargetEntity]
FROM (SELECT VALUE x
FROM [SchoolEntities].[Pupil_Books] AS x
WHERE Key(x.[Pupil_Books_Source]) = ROW(@EntityKeyValue1 AS EntityKeyValue1)) AS [AssociationEntry]
INNER JOIN [SchoolEntities].[Books] AS [TargetEntity]
ON Key([AssociationEntry].[Pupil_Books_Target]) = Key(Ref([TargetEntity]))
为以下实体SQL语句:< / p>
AddQueryParameters
在@EntityKeyValue1
后,参数1
绑定到值Id
,即pupil
的{{1}}。所以上面的查询基本上与:
context.Books.Where(s => s.Pupil.Id == pupil.Id)
也就是说,Query
方法只构建一个查询Books
,Pupil.Id
匹配给定学生的Id
。它与将数据加载到pupil.Books
无关。这也适用于pupil.SchoolclassCodes
。
接下来我们检查以下方法调用:
context.Entry(pupil).Collection(p => p.Book).Load()
此Load
来电会导致以下内容(为清晰起见再次编辑):
var sourceQuery = CreateSourceQuery<TEntity>(mergeOption, out hasResults);
IEnumerable<TEntity> refreshedValues;
refreshedValues = sourceQuery.Execute(sourceQuery.MergeOption);
Merge(refreshedValues, mergeOption, true /*setIsLoaded*/);
正如您所看到的,它构造了一个查询,这与我们上面看到的查询完全相同,然后它执行查询并接收refreshedValues
中的数据,最后将数据合并到导航属性,即pupil.Books
。
如果我们在Load
之后Query
做了什么?
context.Entry(pupil).Collection(p => p.Book).Query().Load()
此Load
被定义为QueryableExtensions
课程中的扩展方法,而且非常简单:
public static void Load(this IQueryable source)
{
Check.NotNull(source, "source");
var enumerator = source.GetEnumerator();
try
{
while (enumerator.MoveNext())
{
}
}
finally
{
var asDisposable = enumerator as IDisposable;
if (asDisposable != null)
{
asDisposable.Dispose();
}
}
}
是的,这次显示完整的源代码;我没有编辑任何东西。这是正确的,它实际上是一个空的foreach
,循环遍历所有加载的项目,并且对它们完全没有任何作用。除了已经完成的事情:这些项目被添加到上下文中,如果关系是一对多,则关系修复启动并修复关联。这是调查员工作的一部分。
在上面我们看到集合的Query
方法只是构造一个普通的查询(一个IQueryable)。当然,构建这样的查询有多种方法。我们不必从context.Entry(...).Collection(...)
开始。我们可以从顶部开始:
using (var context = new SchoolEntities()) {
Console.WriteLine("Books side load");
var pupil = context.Pupils.First();
context.Books.Where(s => s.Pupil.Id == pupil.Id).Load();
// output statements omitted...
}
using (var context = new SchoolEntities()) {
Console.WriteLine("SchoolclassCodes side load");
var pupil = context.Pupils.First();
context.SchoolclassCodes.Where(s => s.Pupils.Select(t => t.Id).Contains(pupil.Id)).Load();
// output statements omitted...
}
输出:
Books side load
IsLoaded = False
Items in the pupil:
1
2
Items in the context:
1
2
SchoolclassCodes side load
IsLoaded = False
Items in the pupil:
Items in the context:
1
2
3
与实验3完全相同。
要删除多对多关系中的部分关联,官方建议的方法是首先Load
所有相关对象,然后删除关联。例如:
context.Entry(pupil).Collection(p => p.SchoolclassCodes).Load();
var code = pupil.SchoolclassCodes.Where(...).First();
pupil.SchoolclassCodes.Remove(code);
context.SaveChanges();
当然,这可能会从数据库中加载不需要的相关对象。如果这是不合需要的,我们可以下拉到ObjectContext并使用ObjectStateManager:
var code = context.Entry(pupil).Collection(p => p.SchoolclassCodes).Query().Where(...).First();
var objectStateManager = ((IObjectContextAdapter)context).ObjectContext.ObjectStateManager;
objectStateManager.ChangeRelationshipState(pupil, code, p => p.SchoolclassCodes, EntityState.Deleted);
context.SaveChanges();
这样只加载相关的相关对象。事实上,如果我们已经知道相关对象的主键,那么即使可以消除它:
var code = new SchoolclassCode { Id = 1 };
context.SchoolclassCodes.Attach(code);
var objectStateManager = ((IObjectContextAdapter)context).ObjectContext.ObjectStateManager;
objectStateManager.ChangeRelationshipState(pupil, code, p => p.SchoolclassCodes, EntityState.Deleted);
context.SaveChanges();
但请注意EF7 will remove ObjectContext,因此如果我们希望将来迁移到EF7,则必须修改上述代码。