如何解决穷人NHibernate的集合初始化初始化、如何解决、穷人、NHibernate

2023-09-03 03:02:22 作者:热情消退

nHibernate3;检索4XXX记录了一个EAV数据架构。当NHibernate的,或.NET,去初始化这些藏品是第一次,我们看到了严厉的惩罚。后续调用似乎达到事半功倍的效果。预期回报快的时间运行在SQL Server Management Studio中的结果相同的查询。

使用流利和运行映射来代替的.hbm.xml;好奇,如果序列化映射关系,将有助于在这里?

NHibernate的探查和log4net的记录似乎并没有给我太多的去。共有像140000实体水合这一进程。

附我dotTrace性能跟踪的截图,显示了集合初始化处罚:

是否尝试过加入,渴望fetchtypes,没有明显的效果,但我不是100%肯定我正确实施的 - 不只是父母需要如此指定的,还是做孩子的表也需要被标记

 变种产品=((HandleSession)_handleSession).Session.CreateCriteria(typeof运算(产品))
                    .SetFetchMode(产品,FetchMode.Eager)
                    .LIST<产品>()
                    .AsEnumerable();
 

通过反射优化启用(我认为),通过web.config中:

这是大部分时间花费:

 返回新产品列表(products.Select(P => p.ToProductContract()));
 
资本门入门篇 史上最全股权投资协议8大关键条款与7种退出方式

这是一个简单的扩展方法这样做的:

 公共静态ProductContract ToProductContract(本产品的产品)
        {
            返回新ProductContract
                       {
                           名称= product.ProductName,
                           ProductTypeName = product.ProductType.ProductTypeName,
                           UpdateTimeStamp = product.UpdateDateTime,
                           ProductNumber = product.ProductNumber,
                           属性= product.ProductAttributes.ToCommonAttribute()。了ToList(),
                           GroupCategories = product.ProductGroups.ToGroupCategory()。了ToList(),
                           PublicUniqueId = product.PublicUniqueId
                       };
        }
 

映射:

 内部类ProductMapping:ClassMap<产品>
    {
        私人常量字符串_iscurrentindicator =IsCurrentIndicator = 1;

        公共ProductMapping()
        {
            表(产品);
            ID(Reveal.Member<产品>(的ProductId))GeneratedBy.Identity()列(产品ID)。;
            地图(X => x.ProductNumber).COLUMN(ProductNumber)Not.Nullable()。
            地图(X => x.ProductName).COLUMN(产品名称)Not.Nullable()。
            地图(X => x.InsertDateTime)。.COLUMN(InsertedDateTime)可空()只读();
            地图(X => x.UpdateDateTime)。.COLUMN(UpdatedDateTime)可空();
            地图(X => x.PublicUniqueId)。.COLUMN(ProductGUID)Generated.Insert();

            引用(X => x.ProductType).COLUMN(ProductTypeId)Not.Nullable()。
            的hasMany(X => x.ProductAttributes)
                .KeyColumn(的ProductId)
                。逆()
                。取
                .Subselect()
                。凡(_iscurrentindicator)
                。级联
                .SaveUpdate();

            的hasMany(X => x.ProductGroups)。.KeyColumn(的ProductId)Fetch.Subselect(),其中(_iscurrentindicator)。
            DynamicUpdate();
            的dynamicInsert();
            BATCHSIZE(500);
        }
    }

内部类ProductGroupMapping:ClassMap< ProductGroup>
    {
        公共ProductGroupMapping()
        {
            表(ProductGroup);
            ID(X => x.ProductGroupId)。.COLUMN(ProductGroupId)GeneratedBy.Identity();
            引用(X => x.Product).COLUMN(的ProductId)Not.Nullable()。
            引用(X => x.Group).COLUMN(GroupId的)Not.Nullable()。
            //凡(IsCurrentIndicator = 1);
        }
    }

内部类ProductAttributeMapping:ClassMap< ProductAttribute>
    {
        公共ProductAttributeMapping()
        {
            表(ProductAttribute);
            LazyLoad();
            ID(X => x.ProductAttributeId).GeneratedBy.Identity()列(ProductAttributeID)。
            引用(X => x.Product).COLUMN(产品ID)Not.Nullable()。
            参考文献(X => x.Attribute)。.COLUMN(AttributeID)Not.Nullable()Fetch.Join();
            地图(X => x.PositionNumber)。.COLUMN(PositionNumber)可空();
            地图(X => x.ValueText)。.COLUMN(ValueText)可空();
            地图(X => x.ValueBinary)。.COLUMN(ValueBinary)可空();

            组分(X => x.OperationalAuditHistory,米=>
                        {
                            表(ProductAttribute);
                            m.Map(X => x.ExpirationDateTime)。.COLUMN(ExpirationDateTime)可空();
                            m.Map(X => x.IsCurrent)。.COLUMN(IsCurrentIndicator)Not.Nullable();
                            m.Map(X => x.Operation code)。.COLUMN(操作code)可空();
                            m.Map(X => x.OperationDateTime)。.COLUMN(OperationDateTime)可空();
                            m.Map(X => x.OperationSystemName)。.COLUMN(OperationSystemName)可空();
                            m.Map(X => x.OperationUserName)。.COLUMN(OperationUserName)可空();
                            m.Map(X => x.LastUserPriority)。.COLUMN(LastUserPriority)可空();
                        });

            的dynamicInsert();
            BATCHSIZE(50);
        }
    }
 

不幸的是与.Future我似乎仍然得到了类似的结果。这里有一个新的跟踪;我改了版本,和x64的重点项目,从目前看,所以时间是较低的,但比例依然pretty的大致相同;以及与.Eager:

 变种产品=((HandleSession)_handleSession).Session.CreateCriteria(typeof运算(产品))
                    .SetFetchMode(ProductAttribute,FetchMode.JOIN)语句
                    .SetFetchMode(ProductGroup,FetchMode.JOIN)语句
                    .SetFetchMode(ProductType,FetchMode.JOIN)语句
                    .Future<产品>()
                    .AsEnumerable();
 

与.Eager和.Future到位生成的SQL:

  

选择this_.ProductID作为   ProductID0_1_,this_.ProductNumber作为   ProductN2_0_1_,this_.ProductName作为   ProductN3_0_1_,this_.InsertedDateTime   作为Inserted4_0_1_,   this_.UpdatedDateTime作为   UpdatedD5_0_1_,this_.ProductGUID作为   ProductG6_0_1_,this_.ProductTypeId作为   ProductT7_0_1_,   producttyp2_.ProductTypeID作为   ProductT1_6_0_,   producttyp2_.ProductTypeName作为   ProductT2_6_0_从产品THIS_   内部联接ProductType producttyp2_上   this_.ProductTypeId = producttyp2_.ProductTypeID;

     

选择productatt0_.ProductId作为   ProductId2_,   productatt0_.ProductAttributeID作为   ProductA1_2_,   productatt0_.ProductAttributeID作为   ProductA1_2_1_,   productatt0_.PositionNumber作为   Position2_2_1_,productatt0_.ValueText   作为ValueText2_1_,   productatt0_.ValueBinary作为   ValueBin4_2_1_,productatt0_.ProductID   作为ProductID2_1_,   productatt0_.AttributeID作为   Attribut6_2_1_,   productatt0_.ExpirationDateTime作为   Expirati7_2_1_,   productatt0_.IsCurrentIndicator作为   IsCurren8_2_1_,   productatt0_.Operation code作为   Operatio9_2_1_,   productatt0_.OperationDateTime作为   Operati10_2_1_,   productatt0_.OperationSystemName作为   Operati11_2_1_,   productatt0_.OperationUserName作为   Operati12_2_1_,   productatt0_.LastUserPriority作为   LastUse13_2_1_,   attribute1_.AttributeId作为   Attribut1_1_​​0_,   attribute1_.AttributeName作为   Attribut2_1_0_,   attribute1_.DisplayName作为   DisplayN3_1_0_,   attribute1_.DataTypeName作为   DataType4_1_0_,   attribute1_.ConstraintText作为   Constrai5_1_0_,   attribute1_.ConstraintMin作为   Constrai6_1_0_,   attribute1_.ConstraintMax作为   Constrai7_1_0_,attribute1_.Values​​Min   作为Values​​Min1_0_,   attribute1_.Values​​Max作为   Values​​Max1_0_,ATTRIBUTE1 _。precision   为precision1_0_从ProductAttribute   productatt0_内连接属性   attribute1_上   productatt0_.AttributeID = attribute1_.AttributeId   哪里   (productatt0_.IsCurrentIndicator = 1)   和productatt0_.ProductId中(选择   this_.ProductID从产品THIS_   内部联接ProductType producttyp2_上   this_.ProductTypeId = producttyp2_.ProductTypeID)

     

选择productgro0_.ProductId作为   ProductId1_,   productgro0_.ProductGroupId作为   ProductG1_1_,   productgro0_.ProductGroupId作为   ProductG1_3_0_,productgro0_.ProductId   作为ProductId3_0_,productgro0_.GroupId   作为GroupId3_0_从ProductGroup   productgro0_ WHERE   (productgro0_.IsCurrentIndicator = 1)   和productgro0_.ProductId中(选择   this_.ProductID从产品THIS_   内部联接ProductType producttyp2_上   this_.ProductTypeId = producttyp2_.ProductTypeID)

解决方案

1)序列化映射关系将不仅有利于减少构建SessionFactory的所需的时间。如果上述查询不是第一访问数据库,它不会完成在这方面的任何

2)设置FetchMode需要被施加于儿童,是这样的:

 变种产品=((HandleSession)_handleSession).Session.CreateCriteria(typeof运算(产品))
                .SetFetchMode(ProductChildren,FetchMode.Eager)
                .LIST<产品>()
                .AsEnumerable();
 

3)这看起来像一个N + 1的问题,如果我跨preT中的截图方法正确。你改造产品在查询结果ProductDTOs的名单?如果是这样,它好像孩子藏品都懒的从数据库加载的循环中。

编辑:

为了打击N + 1选择,我们必须告诉NHibernate的预先加载的一切,pferably $ P $与期货。这是一个潜在的解决方案,基本上取从数据库中的所有数据与少数的选择陈述。我并没有包括任何地方条件。这些你就必须相应地增加。

  //任何地方条件必须应用在这里,并在随后的查询
变种产品= session.QueryOver<产品>()
    。未来();

VAR产品2 = session.QueryOver<产品>()
    .Fetch(P => p.ProductType).Eager
    。未来();

VAR产品3 = session.QueryOver<产品>()
    .Fetch(P => p.ProductAttributes).Eager
    。未来();

VAR产品4 = session.QueryOver<产品>()
    .Fetch(P => p.ProductGroups).Eager
    。未来();

//这里,我们在一个往返执行上述所有查询。
//既然我们已经把所有我们能想像中的数据,也没有必要
//为N + 1选择。
返回新产品列表(products.Select(P => p.ToProductContract()));
 

nHibernate3; retrieving 4xxx records out of an EAV data schema. When nHibernate, or .NET, goes to initialize those collections for the first time, we're seeing a severe penalty. Subsequent calls appear to perform more efficiently. Running the same queries in SQL Server Management Studio result in expected quick return times.

Using Fluent and runtime mapping instead of .hbm.xml; curious if serialized mapping would help here?

nHibernate Profiler and log4net logging didn't seem to give me much to go on. A total of something like 140,000 entities are hydrated in this process.

Attached a screenshot of my dotTrace performance tracing that shows the collection initialization penalty:

Have tried join and eager fetchtypes, with no apparent results, but am not 100% certain I implemented those correctly -- does just the parent need to be so designated, or do the children tables also need to be flagged?

var products = ((HandleSession)_handleSession).Session.CreateCriteria(typeof(Product))
                    .SetFetchMode("Product", FetchMode.Eager)
                    .List<Product>()
                    .AsEnumerable();

With reflection optimizer enabled (I think) via web.config:

This is where most time is spent:

return new ProductList(products.Select(p => p.ToProductContract()));

Which is simply an extension method doing this:

public static ProductContract ToProductContract(this Product product)
        {
            return new ProductContract
                       {
                           Name = product.ProductName,
                           ProductTypeName = product.ProductType.ProductTypeName,
                           UpdateTimeStamp = product.UpdateDateTime,
                           ProductNumber = product.ProductNumber,
                           Attributes = product.ProductAttributes.ToCommonAttribute().ToList(),
                           GroupCategories = product.ProductGroups.ToGroupCategory().ToList(),
                           PublicUniqueId = product.PublicUniqueId
                       };
        }

mappings:

internal class ProductMapping : ClassMap<Product>
    {
        private const string _iscurrentindicator = "IsCurrentIndicator=1";

        public ProductMapping()
        {
            Table("Product");
            Id(Reveal.Member<Product>("ProductId")).GeneratedBy.Identity().Column("ProductID");
            Map(x => x.ProductNumber).Column("ProductNumber").Not.Nullable();
            Map(x => x.ProductName).Column("ProductName").Not.Nullable();
            Map(x => x.InsertDateTime).Column("InsertedDateTime").Nullable().ReadOnly();
            Map(x => x.UpdateDateTime).Column("UpdatedDateTime").Nullable();
            Map(x => x.PublicUniqueId).Column("ProductGUID").Generated.Insert();

            References(x => x.ProductType).Column("ProductTypeId").Not.Nullable();
            HasMany(x => x.ProductAttributes)
                .KeyColumn("ProductId")
                .Inverse()
                .Fetch
                .Subselect()
                .Where(_iscurrentindicator)
                .Cascade
                .SaveUpdate();

            HasMany(x => x.ProductGroups).KeyColumn("ProductId").Fetch.Subselect().Where(_iscurrentindicator);
            DynamicUpdate();
            DynamicInsert();
            BatchSize(500);
        }
    }

internal class ProductGroupMapping : ClassMap<ProductGroup>
    {
        public ProductGroupMapping()
        {
            Table("ProductGroup");
            Id(x => x.ProductGroupId).Column("ProductGroupId").GeneratedBy.Identity();
            References(x => x.Product).Column("ProductId").Not.Nullable();
            References(x => x.Group).Column("GroupId").Not.Nullable();
            //Where("IsCurrentIndicator=1");
        }
    }

internal class ProductAttributeMapping : ClassMap<ProductAttribute>
    {
        public ProductAttributeMapping()
        {
            Table("ProductAttribute");
            LazyLoad();
            Id(x => x.ProductAttributeId).GeneratedBy.Identity().Column("ProductAttributeID");
            References(x => x.Product).Column("ProductID").Not.Nullable();
            References(x => x.Attribute).Column("AttributeID").Not.Nullable().Fetch.Join();
            Map(x => x.PositionNumber).Column("PositionNumber").Nullable();
            Map(x => x.ValueText).Column("ValueText").Nullable();
            Map(x => x.ValueBinary).Column("ValueBinary").Nullable();

            Component(x => x.OperationalAuditHistory, m =>
                        {
                            Table("ProductAttribute");
                            m.Map(x => x.ExpirationDateTime).Column("ExpirationDateTime").Nullable();
                            m.Map(x => x.IsCurrent).Column("IsCurrentIndicator").Not.Nullable();
                            m.Map(x => x.OperationCode).Column("OperationCode").Nullable();
                            m.Map(x => x.OperationDateTime).Column("OperationDateTime").Nullable();
                            m.Map(x => x.OperationSystemName).Column("OperationSystemName").Nullable();
                            m.Map(x => x.OperationUserName).Column("OperationUserName").Nullable();
                            m.Map(x => x.LastUserPriority).Column("LastUserPriority").Nullable();
                        });

            DynamicInsert();
            BatchSize(50);
        }
    }

Unfortunately with .Future I still appear to get similar results. Here's a new trace; I've switched to Release, and x64 for the key projects, for the moment, so the times are lower, but the proportions are still pretty much the same; as well as with .Eager:

var products = ((HandleSession) _handleSession).Session.CreateCriteria(typeof (Product))
                    .SetFetchMode("ProductAttribute", FetchMode.Join)
                    .SetFetchMode("ProductGroup", FetchMode.Join)
                    .SetFetchMode("ProductType", FetchMode.Join)
                    .Future<Product>()
                    .AsEnumerable();

Generated SQL with .Eager and .Future in place:

SELECT this_.ProductID as ProductID0_1_, this_.ProductNumber as ProductN2_0_1_, this_.ProductName as ProductN3_0_1_, this_.InsertedDateTime as Inserted4_0_1_, this_.UpdatedDateTime as UpdatedD5_0_1_, this_.ProductGUID as ProductG6_0_1_, this_.ProductTypeId as ProductT7_0_1_, producttyp2_.ProductTypeID as ProductT1_6_0_, producttyp2_.ProductTypeName as ProductT2_6_0_ FROM Product this_ inner join ProductType producttyp2_ on this_.ProductTypeId=producttyp2_.ProductTypeID;

SELECT productatt0_.ProductId as ProductId2_, productatt0_.ProductAttributeID as ProductA1_2_, productatt0_.ProductAttributeID as ProductA1_2_1_, productatt0_.PositionNumber as Position2_2_1_, productatt0_.ValueText as ValueText2_1_, productatt0_.ValueBinary as ValueBin4_2_1_, productatt0_.ProductID as ProductID2_1_, productatt0_.AttributeID as Attribut6_2_1_, productatt0_.ExpirationDateTime as Expirati7_2_1_, productatt0_.IsCurrentIndicator as IsCurren8_2_1_, productatt0_.OperationCode as Operatio9_2_1_, productatt0_.OperationDateTime as Operati10_2_1_, productatt0_.OperationSystemName as Operati11_2_1_, productatt0_.OperationUserName as Operati12_2_1_, productatt0_.LastUserPriority as LastUse13_2_1_, attribute1_.AttributeId as Attribut1_1_0_, attribute1_.AttributeName as Attribut2_1_0_, attribute1_.DisplayName as DisplayN3_1_0_, attribute1_.DataTypeName as DataType4_1_0_, attribute1_.ConstraintText as Constrai5_1_0_, attribute1_.ConstraintMin as Constrai6_1_0_, attribute1_.ConstraintMax as Constrai7_1_0_, attribute1_.ValuesMin as ValuesMin1_0_, attribute1_.ValuesMax as ValuesMax1_0_, attribute1_.Precision as Precision1_0_ FROM ProductAttribute productatt0_ inner join Attribute attribute1_ on productatt0_.AttributeID=attribute1_.AttributeId WHERE (productatt0_.IsCurrentIndicator=1) and productatt0_.ProductId in (select this_.ProductID FROM Product this_ inner join ProductType producttyp2_ on this_.ProductTypeId=producttyp2_.ProductTypeID)

SELECT productgro0_.ProductId as ProductId1_, productgro0_.ProductGroupId as ProductG1_1_, productgro0_.ProductGroupId as ProductG1_3_0_, productgro0_.ProductId as ProductId3_0_, productgro0_.GroupId as GroupId3_0_ FROM ProductGroup productgro0_ WHERE (productgro0_.IsCurrentIndicator=1) and productgro0_.ProductId in (select this_.ProductID FROM Product this_ inner join ProductType producttyp2_ on this_.ProductTypeId=producttyp2_.ProductTypeID)

解决方案

1) A serialized mapping will only help to reduce the time required to build the SessionFactory. If the above query is not the first access to the database, it will not accomplish anything in that regard.

2) Set FetchMode needs to be applied to the children, like this:

var products = ((HandleSession)_handleSession).Session.CreateCriteria(typeof(Product))
                .SetFetchMode("ProductChildren", FetchMode.Eager)
                .List<Product>()
                .AsEnumerable();

3) This looks like a N+1 problem, if I interpret the methods in the Screenshots correctly. Are you transforming the Products in your query result to a list of ProductDTOs? If so, it seems as if the child collections are lazy loaded from the DB within a loop.

Edit:

In order to combat the N+1 Select, we will have to tell NHibernate to load everything beforehand, preferably with Futures. Here is a potential solution that basically fetches all your data from the db with a handful of Select-statements. I did not include any Where-conditions. Those you would have to add accordingly.

// any where-condition will have to be applied here and in the subsequent queries
var products = session.QueryOver<Product>()
    .Future();

var products2 = session.QueryOver<Product>()
    .Fetch(p => p.ProductType).Eager
    .Future();

var products3 = session.QueryOver<Product>()
    .Fetch(p => p.ProductAttributes).Eager
    .Future();

var products4 = session.QueryOver<Product>()
    .Fetch(p => p.ProductGroups).Eager
    .Future();

// Here we execute all of the above queries in one roundtrip.
// Since we already have all the data we could possibly want, there is no need
// for a N+1 Select.
return new ProductList(products.Select(p => p.ToProductContract()));