Elad
Elad

Reputation: 319

Nhibernate Ordered List Collection is not Fetched by its Order

My understanding is that when mapping a collection as list and giving a column for indexing then:

  1. NHibernate maintains the ordinal position in the given column while that collection changes
  2. When the collection is loaded its items are loaded according to the maintained order column

Unfortunately, my experience shows that only (1) is working. Here is the mapping:

            HasMany(x => x.Attachments)
            .AsList( index => index.Column("OrderInProduct") )
            .Cascade.AllDeleteOrphan()  // Handle cascade upserts
            ;

Is my expectation for (2) wrong? Is my mapping wrong?

Upvotes: 2

Views: 724

Answers (3)

ket
ket

Reputation: 758

You're probably not looking for an answer any longer, but I'll post this here for posterity's sake. There's either a bug in Fluent NHibernate or I'm trying to do something that's disallowed, but when I configured the session factory to export the .HBM.xml file, I found that the HasMany mapping was getting specified as a bag instead of a list, although the sequence number was updated properly. The mapping was responsive to other changes (e.g. .Not.LazyLoad()), but for the life of me I couldn't get it to map as a list.

I ended up putting in a hack, which was partially inspired by this link. In the parent (transcription errors aside :):

private IList<Child> _children;
public virtual IReadOnlyList<Child> Children => _children.OrderBy(la => la.Position).ToList();

protected internal int GetPositionOf(Child child) => _children.ToList().IndexOf(child);

In the child:

        private int? _position;
        protected internal int Position
        {
            get
            {
                var position = _position ?? (_position = Parent.GetPositionOf(this));
                return position.HasValue ? position.Value : throw new InvalidOperationException("Child position has no value");
            }
            set { _position = value; }
        }

I continue to map using .AsList so the position gets updated properly:

HasMany(x => x.Children).Access.CamelCaseField(Prefix.Underscore).AsList(x => x.Column(ColumnNames.Position));

In case it helps someone, you can export the .hbm files to see what Fluent Migrator actually generates:

                FluentConfiguration configuration = Fluently.Configure()
                    .Database(dbConfig)
                    .Mappings(m => m.FluentMappings.AddFromAssembly(Assembly.GetExecutingAssembly())
                        .ExportTo(@"c:\temp\mappings")

Note that you have to have the folder generated in advance or you get an exception.

I'd be interested to hear if anyone else has seen similar behavior; I am using Fluent Migrator 2.1.2 and NHibernate 5.2.7. I've got intentions to submit a bug report, but gotta clear some work off my plate first.

Upvotes: 1

xhafan
xhafan

Reputation: 2416

I just played with this, and 2) works for me. Your mapping is OK.

I believe NHibernate orders the collection in memory based on the index column value without issuing "Order By " sql statement. I got the idea that NHibernate could do a memory sorting here - This performs the ordering in the SQL query, not in memory.

I could not find the collection memory sorting in NHibernate code, so to prove the memory sorting works, I removed the primary key (Id, Index) from the child entity table, persisted 2 child entities (index 0, and index 1), manually - using manual sql - switched the index values (so the first record got index 1, and the second record got index 0), then I loaded the parent entity and checked that records were loaded in expected order.

Upvotes: 0

kleinohad
kleinohad

Reputation: 5912

you can add orderby:

HasMany(x => x.Attachments)
            .AsList( index => index.Column("OrderInProduct") )
            .OrderBy( o => o.Column("OrderInProduct") )
            .Cascade.AllDeleteOrphan()  // Handle cascade upserts
            ;

Upvotes: 1

Related Questions