如何使用因懒惰(延迟加载)而未加载的实体对象?
How to use entity's objects which are not loaded because of laziness (lazy-loading)?
我正在尝试为使用实体对象的实体(根据外键从数据库加载的其他实体)设置 属性。我的代码如下所示:
[Table("Notes")]
public class Note : FullAuditedEntity
{
public virtual int EntityAId { get; set; }
public EntityA EntityA { get; set; }
public virtual int EntityBId { get; set; }
public EntityB EntityB { get; set; }
public List<NoteXHashtag> AdditionalHashtags { get; set; } = new List<CardXHashtag>();
[NotMapped]
public List<Hashtag> AllHashtags
{
get
{
List<Hashtag> allHashtags = new List<Hashtag>();
allHashtags.AddRange(AdditionalHashtags.Select(x => x.Hashtag));
allHashtags.Add(EntityA.Hashtag);
allHashtags.Add(EntityB.Hashtag);
return allHashtags;
}
}
}
当我尝试从外部使用 属性 AllHashtags
时,EntityA 和 EntityB 为空。当我需要使用它们时,如何告诉 Entity Framework 从数据库加载它们?
注意:当我从外部调用 AllHashtags
时,我同时包含了 EntityA 和 EntityB:
noteRepository
.GetAll()
.Include(x => x.AdditionalHashtags).ThenInclude(x => x.Hashtag)
.Include(x => x.EntityA).ThenInclude(x => x.Hashtag)
.Include(x => x.EntityB).ThenInclude(x => x.Hashtag)
.Select(x => new NoteDetailDto()
{
Id = x.Id,
AllHashtags = x.AllHashtags
});
如果您使用的是投影 (.Select()
),则无需使用 .Include()
即可访问相关实体。
我首先要查看您的存储库 .GetAll() 方法是什么 returning。 .Include()
仅对 IQueryable
有效,因此如果存储库方法有效地执行如下操作:
return _context.Notes.AsQueryable();
或者这个:
return _context.Notes.Where(x => x.SomeCondition);
然后您可以在存储库方法之外利用 Include()
。但是,如果您 return 是这样的:
return _context.Notes.Where(x => x.SomeCondition).ToList().AsQueryable();
然后该类型将授予对 Include()
方法的访问权限,但 Include 实际上不会包含相关表。您可以通过在数据库上使用分析器来检查查询来观察这一点。使用 Include,查询会将 EntityA 和 EntityB 表连接到 SELECT 语句中。
关于您的示例语句,这显然是您提供的一个简化示例,但据我所知,如果 GetAll() 方法是 returning EF IQueryable
.如果它 return 一个 EF IQueryable
那么访问 Select
中的 AllHashtags 属性 将导致错误,因为 AllHashtags 不是 Note 的映射 属性。这意味着如果您的真实代码看起来像那样,那么 GetAll() 不是 returning EF IQueryable
,或者您可能已经为 Include
/[=29 创建了扩展方法=] for IEnumerable
执行 AsQueryable()
以满足 EF 包含操作。 (这些都行不通)
为了获得您想要的结果,我建议避免在实体上使用未映射的 属性,而是采用匿名类型的两步方法并将业务逻辑转换保留在业务层:
第 1 步。确保存储库方法只是 returning EF IQueryable
,因此 context.[DbSet<TEntity>].AsQueryable()
或 context.[DbSet<TEntity>].Where([condition])
没问题:
return _context.Notes.AsQueryable();
第 2 步。为您的标签定义一个 DTO。 (避免 send/receive 个实体)
[Serializable]
public class HashtagDto
{
public int Id { get; set;}
public string Text { get; set; }
// etc.
}
第 3 步。Select 将您关心的适当字段转换为匿名类型并将其具体化:
var noteData = repository.GetAll()
.Select( x= > new {
x.Id,
AdditionalHashtags = x.AdditionalHashtags.Select(h => new HashtagDto { Id = h.Id, Text = h.Text }).ToList(),
EntityAHashtag = new HashTagDto { Id = x.EntityA.Hashtag.Id, Text = x.EntityA.Hashtag.Text },
EntityBHashtag = new HashTagDto { Id = x.EntityB.Hashtag.Id, Text = x.EntityB.Hashtag.Text },
).ToList();
第 4 步。编写您的视图模型/DTO:
var noteDetails = noteData.Select(x => new NoteDetailDto
{
Id = x.Id,
AllHashTags = condenseHashtags(x.AdditionalHashtags, EntityAHashtag, EntityBHashtag);
}).ToList();
condenseHashtags 只是一个简单的实用方法:
private static ICollection<HashTagDto> condenseHashtags(IEnumerable<HashtagDto> source1, HashtagDto source2, HashtagDto source3)
{
var condensedHashtags = new List<HashtagDto>(source1);
if (source2 != null)
condensedHashtags.Add(source2);
if (source3 != null)
condensedHashtags.Add(source3);
return condensedHashtags;
}
上面的示例是同步的,如果负载性能是服务器响应能力的关注点,则可以将其转换为异步代码而不会太麻烦。
步骤 3 和 4 可以组合在一个语句中,但它们之间需要有一个 .ToList()
,因为步骤 4 需要针对 Linq2Object 运行 来压缩主题标签。第 3 步确保 Linq2EF 表达式构成一个高效的查询,只查询 return 关于笔记的信息,以及我们关心的相关主题标签,仅此而已。第 4 步将这些单独的细节压缩到我们打算 return.
的 DTO 结构中
第 2 步很重要,因为您应该避免将实体发送回 client/consumers。如果启用了延迟加载,发送实体可能会导致性能问题和可能的错误,或者如果未启用延迟加载并且您忽略了预加载相关信息,则会导致传递的数据不完整。 (相关细节真的是#null,还是你忘了包括它?)它还可以揭示比消费者必须知道的更多的数据结构,并反映比需要的更大的数据传输包。从客户端接受实体是非常不可取的,因为它打开了意外数据篡改、陈旧数据覆盖以及处理重新连接上下文可能知道的分离实体的典型错误策略。如果您的代码只是将实体附加到 DbContext、设置修改后的状态并保存更改。即使您今天不这样做,如果实体已经存在于调用中,它也会为以后修改开始做这件事打开大门。接收 DTO、加载实体、验证行版本、针对实体验证 DTO 中的 snot,并且仅更新预期会更改的字段。
我正在尝试为使用实体对象的实体(根据外键从数据库加载的其他实体)设置 属性。我的代码如下所示:
[Table("Notes")]
public class Note : FullAuditedEntity
{
public virtual int EntityAId { get; set; }
public EntityA EntityA { get; set; }
public virtual int EntityBId { get; set; }
public EntityB EntityB { get; set; }
public List<NoteXHashtag> AdditionalHashtags { get; set; } = new List<CardXHashtag>();
[NotMapped]
public List<Hashtag> AllHashtags
{
get
{
List<Hashtag> allHashtags = new List<Hashtag>();
allHashtags.AddRange(AdditionalHashtags.Select(x => x.Hashtag));
allHashtags.Add(EntityA.Hashtag);
allHashtags.Add(EntityB.Hashtag);
return allHashtags;
}
}
}
当我尝试从外部使用 属性 AllHashtags
时,EntityA 和 EntityB 为空。当我需要使用它们时,如何告诉 Entity Framework 从数据库加载它们?
注意:当我从外部调用 AllHashtags
时,我同时包含了 EntityA 和 EntityB:
noteRepository
.GetAll()
.Include(x => x.AdditionalHashtags).ThenInclude(x => x.Hashtag)
.Include(x => x.EntityA).ThenInclude(x => x.Hashtag)
.Include(x => x.EntityB).ThenInclude(x => x.Hashtag)
.Select(x => new NoteDetailDto()
{
Id = x.Id,
AllHashtags = x.AllHashtags
});
如果您使用的是投影 (.Select()
),则无需使用 .Include()
即可访问相关实体。
我首先要查看您的存储库 .GetAll() 方法是什么 returning。 .Include()
仅对 IQueryable
有效,因此如果存储库方法有效地执行如下操作:
return _context.Notes.AsQueryable();
或者这个:
return _context.Notes.Where(x => x.SomeCondition);
然后您可以在存储库方法之外利用 Include()
。但是,如果您 return 是这样的:
return _context.Notes.Where(x => x.SomeCondition).ToList().AsQueryable();
然后该类型将授予对 Include()
方法的访问权限,但 Include 实际上不会包含相关表。您可以通过在数据库上使用分析器来检查查询来观察这一点。使用 Include,查询会将 EntityA 和 EntityB 表连接到 SELECT 语句中。
关于您的示例语句,这显然是您提供的一个简化示例,但据我所知,如果 GetAll() 方法是 returning EF IQueryable
.如果它 return 一个 EF IQueryable
那么访问 Select
中的 AllHashtags 属性 将导致错误,因为 AllHashtags 不是 Note 的映射 属性。这意味着如果您的真实代码看起来像那样,那么 GetAll() 不是 returning EF IQueryable
,或者您可能已经为 Include
/[=29 创建了扩展方法=] for IEnumerable
执行 AsQueryable()
以满足 EF 包含操作。 (这些都行不通)
为了获得您想要的结果,我建议避免在实体上使用未映射的 属性,而是采用匿名类型的两步方法并将业务逻辑转换保留在业务层:
第 1 步。确保存储库方法只是 returning EF IQueryable
,因此 context.[DbSet<TEntity>].AsQueryable()
或 context.[DbSet<TEntity>].Where([condition])
没问题:
return _context.Notes.AsQueryable();
第 2 步。为您的标签定义一个 DTO。 (避免 send/receive 个实体)
[Serializable]
public class HashtagDto
{
public int Id { get; set;}
public string Text { get; set; }
// etc.
}
第 3 步。Select 将您关心的适当字段转换为匿名类型并将其具体化:
var noteData = repository.GetAll()
.Select( x= > new {
x.Id,
AdditionalHashtags = x.AdditionalHashtags.Select(h => new HashtagDto { Id = h.Id, Text = h.Text }).ToList(),
EntityAHashtag = new HashTagDto { Id = x.EntityA.Hashtag.Id, Text = x.EntityA.Hashtag.Text },
EntityBHashtag = new HashTagDto { Id = x.EntityB.Hashtag.Id, Text = x.EntityB.Hashtag.Text },
).ToList();
第 4 步。编写您的视图模型/DTO:
var noteDetails = noteData.Select(x => new NoteDetailDto
{
Id = x.Id,
AllHashTags = condenseHashtags(x.AdditionalHashtags, EntityAHashtag, EntityBHashtag);
}).ToList();
condenseHashtags 只是一个简单的实用方法:
private static ICollection<HashTagDto> condenseHashtags(IEnumerable<HashtagDto> source1, HashtagDto source2, HashtagDto source3)
{
var condensedHashtags = new List<HashtagDto>(source1);
if (source2 != null)
condensedHashtags.Add(source2);
if (source3 != null)
condensedHashtags.Add(source3);
return condensedHashtags;
}
上面的示例是同步的,如果负载性能是服务器响应能力的关注点,则可以将其转换为异步代码而不会太麻烦。
步骤 3 和 4 可以组合在一个语句中,但它们之间需要有一个 .ToList()
,因为步骤 4 需要针对 Linq2Object 运行 来压缩主题标签。第 3 步确保 Linq2EF 表达式构成一个高效的查询,只查询 return 关于笔记的信息,以及我们关心的相关主题标签,仅此而已。第 4 步将这些单独的细节压缩到我们打算 return.
第 2 步很重要,因为您应该避免将实体发送回 client/consumers。如果启用了延迟加载,发送实体可能会导致性能问题和可能的错误,或者如果未启用延迟加载并且您忽略了预加载相关信息,则会导致传递的数据不完整。 (相关细节真的是#null,还是你忘了包括它?)它还可以揭示比消费者必须知道的更多的数据结构,并反映比需要的更大的数据传输包。从客户端接受实体是非常不可取的,因为它打开了意外数据篡改、陈旧数据覆盖以及处理重新连接上下文可能知道的分离实体的典型错误策略。如果您的代码只是将实体附加到 DbContext、设置修改后的状态并保存更改。即使您今天不这样做,如果实体已经存在于调用中,它也会为以后修改开始做这件事打开大门。接收 DTO、加载实体、验证行版本、针对实体验证 DTO 中的 snot,并且仅更新预期会更改的字段。