【问题标题】:How to block NHibernate from reading child collection?如何阻止 NHibernate 读取子集合?
【发布时间】:2019-09-10 09:20:59
【问题描述】:

我的数据库中有文档列表。每个文档都有一组子元素。现在,我的情况是: - 阅读文件(仅从文件表) - 关闭会话并让用户做一些工作。

但是当我这样做时,Document 会尝试在某些地方加载子元素。我不想要它。我只想明确地阅读子元素。对于第一部分,我只需要阅读简单的文档值。

那么有什么方法可以对 nHibernate 说 - “嘿,永远不要阅读这个集合!”?

【问题讨论】:

    标签: c# nhibernate


    【解决方案1】:

    将您收藏的延迟加载设置为LazyExtra,也许您的设置为NoLazy(也称为急切加载)。

    最好将其设置为Extra 而不是仅Lazy。因为当您只想获取子集合的.Count().Any() 时,它会阻止 NHibernate 获取子集合的行。 Extra 就像是更懒惰的懒惰版本:)

    使用 NoLazy / eager-loading

    var post = session.Get<Post>(1);
    

    即使您没有从应用程序访问帖子的子集合 cmets,也会从数据库中的 post 表中读取一行并从 cmets 表中读取一行。

    使用Lazyvar post = session.Get&lt;Post&gt;(1)只会从posts表中加载一行,NHibernate不会从数据库中读取post的子集合cmets。

    至于 Lazy vs Extra

    使用 懒惰

    var commentsCount = post.Comments.Count()
    

    这将从数据库中加载帖子的 cmets:

    select * from comments where post_id = 1;
    

    .Count() 仅发生在应用程序端。

    使用 Extravar commentsCount = post.Comments.Count(),NHibernate 将只发出计数查询,而不是读取所有行。

    select count(*) from comments where post_id = 1
    

    如果您使用 NHibernate 的自动映射,这是一个设置子集合加载机制的示例配置,您可以在 BeforeMapSet 事件上设置该设置:

    当您需要预先加载配置为LazyExtra 的子集合时,请使用FetchMany

    【讨论】:

    • 我已经把它设置为懒惰了。我根本不想得到那些记录。我什至不想从数据库中获取计数,因为会话不再存在。这就是我不想获得这些记录的原因。
    【解决方案2】:

    作为临时解决方案,我创建了一个简单的 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。

    【讨论】:

    • 嗯..这很危险。它有可能从表中删除子集合 Period 。也许即使您将子集合的Lazy 加载策略设置为Lazy(或Extra),而不是NonLazy,您已经将子集合的Fetch 策略设置为Join?因此,即使未访问子集合 Period 也会被获取。请参阅我的新答案。
    • 它不会删除它,因为它已超出事务。但仍然是一个黑客。我只是不想读周期,除非我告诉他。这就像两个不同的命令。一是:阅读所有文件。只有文件。另一个命令是:获取此文档的句点。但在这期间,我可能会阅读一些关于 Periods 集合的资料,但我还不想阅读它。
    【解决方案3】:

    即使您不访问文档的 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

    【讨论】:

    • 这不是我的问题。我不希望 nHibernate 从数据库中读取这个集合。除非我明确告诉他:“现在,从数据库中获取这个集合”。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-01-04
    • 1970-01-01
    • 2016-01-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多