C# EF加载所有子项,即使第一个或默认值在何处
我有以下代码:C# EF加载所有子项,即使第一个或默认值在何处,c#,entity-framework,linq-to-sql,C#,Entity Framework,Linq To Sql,我有以下代码: using (var context = new MyDbContext(connectionString)) { context.Configuration.LazyLoadingEnabled = true; context.Configuration.ProxyCreationEnabled = true; context.Database.Log = logValue => File.AppendAllText(logFilePath, lo
using (var context = new MyDbContext(connectionString))
{
context.Configuration.LazyLoadingEnabled = true;
context.Configuration.ProxyCreationEnabled = true;
context.Database.Log = logValue => File.AppendAllText(logFilePath, logValue);
var testItem1 = context.ParentTable
.FirstOrDefault(parent => parent.Id == 1)
.ChildEntities
.FirstOrDefault(child => child.ChildId == 2000);
}
在执行此代码并检查EF 6的日志文件(logFilePath)时,我看到为Id=1的整个ParentTable记录加载了子实体,同时启用了LazyLoading并指定了子表的条件(child.ChildId==2000)
EF不应该只加载相关的子级,还是先执行读取项,然后再执行内存中的data FirstOrDefault
因为如果某个父实体有许多子实体,这样,在加载带有条件的子实体时,会显著降低性能
我想解决方法是分别加载子实体
这是上述代码的完整日志文件(为了便于阅读,排除了某些行):
注意:添加了相关类:
public class MyDbContext : DbContext
{
public DbSet<ParentTable> ParentTable { get; set; }
public DbSet<ChildTable> ChildTable { get; set; }
static MyDbContext()
{
Database.SetInitializer<MyDbContext>(null);
}
public MyDbContext(string connStr)
: base(connStr)
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<ParentTable>()
.HasMany(t => t.ChildEntities);
}
}
[Table("ParentTable", Schema = "dbo")]
public class ParentTable
{
public int Id { get; set; }
public virtual ICollection<ChildTable> ChildEntities { get; set; }
}
[Table("ChildTable", Schema = "dbo")]
public class ChildTable
{
public int ChildId { get; set; }
public int ParentId { get; set; }
[ForeignKey("ParentId")]
public virtual ParentTable Parent { get; set; }
}
公共类MyDbContext:DbContext
{
公共数据库集父表{get;set;}
公共DbSet子表{get;set;}
静态MyDbContext()
{
Database.SetInitializer(null);
}
公共MyDbContext(字符串connStr)
:base(connStr)
{
}
模型创建时受保护的覆盖无效(DbModelBuilder modelBuilder)
{
modelBuilder.Entity()
.HasMany(t=>t.ChildEntities);
}
}
[表(“父表”,Schema=“dbo”)]
公共类父表
{
公共int Id{get;set;}
公共虚拟ICollection子实体{get;set;}
}
[Table(“ChildTable”,Schema=“dbo”)]
公共类子表
{
public int ChildId{get;set;}
public int ParentId{get;set;}
[ForeignKey(“ParentId”)]
公共虚拟父表父{get;set;}
}
使用此查询:
var testItem1 = context.ChildTables
.Include(p=>p.ParentTable)
.Where(ch => ch.ChildId == 2000)
.FirstOrDefault();
您的问题与延迟加载无关。这是因为在LINQ方法序列中过早地使用了
FirstOrDefault
我将首先编写适当的查询,然后解释为什么该查询更好
var result = dbContext.ParentTable
.Where(parent => parent.Id == 1)
.SelectMany(parent => parent.ChildEntities.Where(child => child.ChildId == 2000))
.FirstOrDefault();
如果仔细观察LINQ方法,您会发现有两种类型:返回IQueryable
的类型和其他类型。第一组的LINQ方法使用延迟执行,也称为延迟执行。这意味着这些语句不会执行查询。它们只会更改IQueryable中的表达式。尚未查询数据库
后一组中的LINQ语句将深入调用GetEnumerator()
,大多数情况下会重复调用MoveNext()/Current
。这将把IQueryable.Expression
发送给IQueryable.Provider
,后者将尝试将表达式转换为SQL并执行查询以从数据库中获取数据(确切地说:转换不一定总是SQL,这取决于提供程序)。获取的数据显示为一个IEnumerator
,您可以调用其中的MoveNext()/Current
您的第一个FirstOrDefault
将已经执行查询。除此之外,它执行得太早,可能会获取比您想要的更多的数据,还可能存在返回null
的问题
正确的方法是使用选择。只有最后一条语句应该包含不可查询的方法,如FirstOrDefault
我使用SelectMany而不是Select,因为您只对父对象的子实体感兴趣,而对任何父对象属性都不感兴趣
var result = dbContext.ParentTable
.Where(parent => parent.Id == 1)
.SelectMany(parent => parent.ChildEntities.Where(child => child.ChildId == 2000))
.FirstOrDefault();
虽然这解决了您的问题,但这将获取比您实际计划使用的更多的数据。例如,每个子级都有父级的外键。您知道父项的主键值等于1,因此子项的外键值也将为1。为什么要转移它
在这种情况下,我希望只有一个孩子,所以问题不是太大。但在其他情况下,您可能会经常发送相同的值
使用实体框架时,请始终使用“选择”,并仅选择计划使用的属性。如果计划更新获取的项目,则仅获取整行或使用Include
如果不使用Select,另一件会减慢处理速度的事情是,当您获取完整的行时,原始获取的数据及其副本将放在DbContext.ChangeTracker
中。这样做是为了在调用SaveChanges
时检测必须保存的值。如果您不打算更新提取的数据,请不要浪费处理能力将提取的数据放入更改跟踪器。请显示ParentTable和ChildTable类好吗?添加在问题末尾谢谢!不完全是我想要的,但它可以。仍然不知道为什么EF不能识别并进行适当的查询:)
var result = dbContext.ParentTable
.Where(parent => parent.Id == 1)
.SelectMany(parent => parent.ChildEntities.Where(child => child.ChildId == 2000))
.FirstOrDefault();