N73*_*73k 5 .net c# entity-framework entity-framework-6
我注意到当我通过不同的"成员路径"导航到同一个实体对象时,我得到了一个不同的对象.(我正在使用更改跟踪代理,因此我获得了一个不同的更改跟踪代理对象.)
这是一个显示我的意思的例子.
var joesInfo1 = context.People.Single(p => p.Name == "Joe").Info;
var joesInfo2 = context.People.Single(p => p.Name == "Joe's Dad").Children.Single(p => p.Name == "Joe").Info;
Run Code Online (Sandbox Code Playgroud)
即使joesInfo1和joesInfo2引用DB(同一实体)中的相同记录,它们也是不同的对象.我认为实体框架确保在这些情况下使用相同的对象.
问题1:这真的是这样吗?或者我的观察错了?
通过Include进行急切加载时,这是一个问题.例如,
IQueryable<Person> allPeople = null;
using(context)
{
allPeople = context.People
//.AsNoTracking()
.Include(p => p.Info)
.Include(p => p.Children)
.Include(p => p.Parent)
.ToList();
}
var joesInfo1 = allPeople.Single(p => p.Name == "Joe").Info; // OK, Info is already there because it was eagerly loaded
var joesInfo2 = allPeople.Single(p => p.Name == "Joe's Dad").Children.Single(p => p.Name == "Joe").Info;
// ERROR: "Object context disposed...", Info is not in the Person object, even though the Person object refers to the same entity (Joe) as above.
Run Code Online (Sandbox Code Playgroud)
因此,看起来急于加载工作,您必须指定您将在程序中使用的所有可能的"成员访问路径".在某些情况下,这是不可能的.因为您的Person对象可能在您的程序中浮动,并且可以在其上调用导航属性"Parent"或"Children"(以及它的父/子)任意次.
问题2:如果没有指定您将在程序中使用的所有"成员访问路径",有没有办法让它工作?
谢谢.
回答:
所以,根据bubi的回答,这是我得出的结论.
如果使用AsNoTracking(),则可以获得不同的"实体对象".(换句话说,在上面的示例中,根据您到达"Joe"Person实体的路径,您可能会得到一个不同的对象.)如果您不使用AsNoTracking,则所有Joes将是同一个对象.
这意味着什么:
您可以急切地加载整个分层或递归对象图,并在上下文之外使用它.怎么样?只是不要使用AsNoTracking().
关于您的代码,在第二个问题中您正在运行第一个查询(allPeople 是 IQueryable)
var joesInfo1 = allPeople.Single(p => p.Name == "Joe").Info; // OK, Info is already there because it was eagerly loaded
Run Code Online (Sandbox Code Playgroud)
上下文已经被处理,所以它不会运行。
无论如何,我想这是你的模型
[Table("People67")]
public class Person
{
public Person()
{
Children = new List<Person>();
}
public int Id { get; set; }
[MaxLength(50)]
public string Name { get; set; }
public virtual Info Info { get; set; }
public virtual ICollection<Person> Children { get; set; }
}
public class Info
{
public int Id { get; set; }
[MaxLength(50)]
public string Description { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
为数据库添加种子后,此代码可以工作(查看断言)
using (var context = new Context(GetConnection()))
{
var joes1 = context.People.Single(p => p.Name == "Joe");
var joes2 = context.People.Single(p => p.Name == "Joe's Dad").Children.Single(p => p.Name == "Joe");
Assert.IsTrue(object.ReferenceEquals(joes1, joes2);
Assert.IsTrue(object.ReferenceEquals(joes1.Info.GetType(), joes2.Info.GetType()));
Assert.IsTrue(object.ReferenceEquals(joes1.Info, joes2.Info));
}
Run Code Online (Sandbox Code Playgroud)
所以关于你的第一个问题,代理是相同类型的,参考是相同的。
如果您查看查询,则更深入一点
ExecuteDbDataReader==========
SELECT TOP 2
[Extent1].[Id] AS [Id],
[Extent1].[Name] AS [Name],
[Extent1].[Person_Id] AS [Person_Id],
[Extent1].[Info_Id] AS [Info_Id]
FROM [People67] AS [Extent1]
WHERE 'Joe' = [Extent1].[Name]
ExecuteDbDataReader==========
SELECT TOP 2
[Extent1].[Id] AS [Id],
[Extent1].[Name] AS [Name],
[Extent1].[Person_Id] AS [Person_Id],
[Extent1].[Info_Id] AS [Info_Id]
FROM [People67] AS [Extent1]
WHERE 'Joe''s Dad' = [Extent1].[Name]
ExecuteDbDataReader==========
SELECT
[Extent1].[Id] AS [Id],
[Extent1].[Name] AS [Name],
[Extent1].[Person_Id] AS [Person_Id],
[Extent1].[Info_Id] AS [Info_Id]
FROM [People67] AS [Extent1]
WHERE ([Extent1].[Person_Id] IS NOT NULL) AND ([Extent1].[Person_Id] = @EntityKeyValue1)
EntityKeyValue1 = 1
ExecuteDbDataReader==========
SELECT
[Extent2].[Id] AS [Id],
[Extent2].[Description] AS [Description]
FROM ( [People67] AS [Extent1]
INNER JOIN [Infoes] AS [Extent2] ON ([Extent1].[Info_Id] = [Extent2].[Id]))
WHERE ([Extent1].[Info_Id] IS NOT NULL) AND ([Extent1].[Id] = @EntityKeyValue1)
EntityKeyValue1 = 2
Run Code Online (Sandbox Code Playgroud)
你可以理解为EF合并了内存中的实体(看第三个查询)。
现在,更准确地说,如果您还Parent_Id向 Person 添加属性,则此行为不会改变。如果 EF 应知道 Joe 已在内存中,也会运行第三个查询。
===================
现在是第二部分
正如我在答案开头所说,您的代码根本不起作用,因为您也在第一个查询中访问具有已处置上下文的 IQueryable。
在这种情况下,我想这是你的代码。
List<Person> allPeople;
using (var context = new Context(GetConnection()))
{
allPeople = context.People
.Include(_ => _.Info)
.Include(_ => _.Children)
.ToList();
}
// This is an in memory query because to the previous ToList
// Take care of == because is an in memory case sensitive query!
Assert.IsNotNull(allPeople.Single(p => p.Name == "Joe").Info);
Assert.IsNotNull(allPeople.Single(p => p.Name == "Joe's Dad").Children.Single(p => p.Name == "Joe").Info);
Assert.IsTrue(object.ReferenceEquals(allPeople.Single(p => p.Name == "Joe").Info, allPeople.Single(p => p.Name == "Joe's Dad").Children.Single(p => p.Name == "Joe").Info));
Run Code Online (Sandbox Code Playgroud)
如果激活探查器,您将看到 EF 在ToList().
===================
那么,什么不起作用呢?如果你插入几件事AsNoTracking()。在这种情况下,EF 行为会有所不同,实体不在上下文中(不被跟踪),并且 EF 需要访问数据库以检索内存中应具有的实体。
例如,这段代码不起作用。
List<Person> allPeople;
using (var context = new Context(GetConnection()))
{
allPeople = context.People
.Include(_ => _.Info)
.Include(_ => _.Children)
.AsNoTracking()
.ToList();
}
// This throw an exception
Assert.IsNotNull(allPeople.Single(p => p.Name == "Joe's Dad").Children.Single(p => p.Name == "Joe").Info);
Run Code Online (Sandbox Code Playgroud)
编辑
您可以通过不同的方式解决使用 AsNoTracking 发现的不同问题。我不知道是否有“解决方案”。
我通常实现==(和Equals,等等)处理字符大小写(DBMS 通常不区分大小写,因此也!=必须不区分大小写)以避免“==”问题(对同一数据库实体的不同引用)。
然后,如果需要,我可以缓存在内存中的实体,并在内存中搜索实体,而不是导航属性。
最后,代码不像使用导航属性那么干净,但它可以工作(Knuth 说,“优化是万恶之源”)。GetHashCode==
| 归档时间: |
|
| 查看次数: |
424 次 |
| 最近记录: |