【发布时间】:2019-09-10 09:20:59
【问题描述】:
我的数据库中有文档列表。每个文档都有一组子元素。现在,我的情况是: - 阅读文件(仅从文件表) - 关闭会话并让用户做一些工作。
但是当我这样做时,Document 会尝试在某些地方加载子元素。我不想要它。我只想明确地阅读子元素。对于第一部分,我只需要阅读简单的文档值。
那么有什么方法可以对 nHibernate 说 - “嘿,永远不要阅读这个集合!”?
【问题讨论】:
标签: c# nhibernate
我的数据库中有文档列表。每个文档都有一组子元素。现在,我的情况是: - 阅读文件(仅从文件表) - 关闭会话并让用户做一些工作。
但是当我这样做时,Document 会尝试在某些地方加载子元素。我不想要它。我只想明确地阅读子元素。对于第一部分,我只需要阅读简单的文档值。
那么有什么方法可以对 nHibernate 说 - “嘿,永远不要阅读这个集合!”?
【问题讨论】:
标签: c# nhibernate
将您收藏的延迟加载设置为Lazy 或Extra,也许您的设置为NoLazy(也称为急切加载)。
最好将其设置为Extra 而不是仅Lazy。因为当您只想获取子集合的.Count() 或.Any() 时,它会阻止 NHibernate 获取子集合的行。 Extra 就像是更懒惰的懒惰版本:)
使用 NoLazy / eager-loading:
var post = session.Get<Post>(1);
即使您没有从应用程序访问帖子的子集合 cmets,也会从数据库中的 post 表中读取一行并从 cmets 表中读取一行。
使用Lazy、var post = session.Get<Post>(1)只会从posts表中加载一行,NHibernate不会从数据库中读取post的子集合cmets。
使用 懒惰:
var commentsCount = post.Comments.Count()
这将从数据库中加载帖子的 cmets:
select * from comments where post_id = 1;
.Count() 仅发生在应用程序端。
使用 Extra、var commentsCount = post.Comments.Count(),NHibernate 将只发出计数查询,而不是读取所有行。
select count(*) from comments where post_id = 1
如果您使用 NHibernate 的自动映射,这是一个设置子集合加载机制的示例配置,您可以在 BeforeMapSet 事件上设置该设置:
当您需要预先加载配置为Lazy 或Extra 的子集合时,请使用FetchMany
【讨论】:
作为临时解决方案,我创建了一个简单的 hack:
public class Document
{
IList<Periods> periods;
public virtual IList<Period> Periods
{
get { return periods; }
set { periods = value; }
}
public virtual void ResetPeriods()
{
periods = new List<Period>();
}
}
这就是我获取文件的方式:
db.BeginTransaction();
IList<Document> list = db.Get<Document>();
db.CommitTransaction();
List<Document> result = new List<Document>();
foreach (var item in list)
{
item.ResetPeriods(); //todo: HACK! Preventing from lazy load of periods
result.Add(item);
}
return result;
当然这个集合被映射为惰性的。 必须将子集合(Periods)定义为返回变量,因为它会阻止 NHibernate Proxy 使用属性 getter。
【讨论】:
Lazy 加载策略设置为Lazy(或Extra),而不是NonLazy,您已经将子集合的Fetch 策略设置为Join?因此,即使未访问子集合 Period 也会被获取。请参阅我的新答案。
即使您不访问文档的 Periods 属性,我也发现了导致文档的 Periods 从数据库加载的原因。
namespace NHibernateFetchJoinTest2
{
using System;
using NHibernateFetchJoinTest2.DomainMapping;
using NHibernateFetchJoinTest2.Domains;
class MainClass
{
public static void Main(string[] args)
{
using (var session = Mapper.SessionFactory.OpenSession())
{
Console.WriteLine("SQL produced: ");
var d = session.Get<Document>(1);
Console.ReadLine();
//Console.WriteLine("Document's periods: ");
//foreach (var period in d.Periods)
//{
// Console.WriteLine($"* {period.PeriodDescription}");
//}
Console.ReadLine();
}
}
}
}
产生这个:
SQL produced:
NHibernate:
SELECT
document0_.Id as id1_0_1_,
document0_.DocumentDescription as documentdescription2_0_1_,
periods1_.DocumentId as documentid3_1_3_,
periods1_.Id as id1_1_3_,
periods1_.Id as id1_1_0_,
periods1_.PeriodDescription as perioddescription2_1_0_
FROM
Document document0_
left outer join
Period periods1_
on document0_.Id=periods1_.DocumentId
WHERE
document0_.Id=@p0;
@p0 = 1 [Type: Int32 (0:0:0)]
您的映射类似于以下内容。您的子集合Lazy-loading 设置为Lazy(而不是NoLazy),但其Fetch 策略设置为Join。也就是说:
namespace NHibernateFetchJoinTest2.DomainMapping.Mappings
{
using NHibernate.Mapping.ByCode.Conformist;
using NHibernateFetchJoinTest2.Domains;
public class DocumentMapping : ClassMapping<Document>
{
public DocumentMapping()
{
Id(x => x.Id);
Property(x => x.DocumentDescription);
Bag(x => x.Periods, collectionMapping =>
{
collectionMapping.Inverse(true);
collectionMapping.Key(k => k.Column("DocumentId"));
collectionMapping.Lazy(NHibernate.Mapping.ByCode.CollectionLazy.Lazy);
// Remove this. This causes Document's Periods to load,
// even if child collection Periods is not accessed yet.
// This is evident in SQL log, it shows LEFT JOIN Period.
collectionMapping.Fetch(NHibernate.Mapping.ByCode.CollectionFetchMode.Join);
}, mapping => mapping.OneToMany());
}
}
public class PeriodMapping: ClassMapping<Period>
{
public PeriodMapping()
{
Id(x => x.Id);
Property(x => x.PeriodDescription);
}
}
}
如果这被删除...
collectionMapping.Fetch(NHibernate.Mapping.ByCode.CollectionFetchMode.Join);
...子集合 Periods 不会被其父级(文档)提前获取:
SQL produced:
NHibernate:
SELECT
document0_.Id as id1_0_0_,
document0_.DocumentDescription as documentdescription2_0_0_
FROM
Document document0_
WHERE
document0_.Id=@p0;
@p0 = 1 [Type: Int32 (0:0:0)]
使用的复制步骤:https://github.com/MichaelBuen/NHibernateFetchJoinTest2
【讨论】: