Where's the wiki documentation for HasMany collection mapping?

192 views
Skip to first unread message

Trinition

unread,
Feb 1, 2011, 10:50:27 AM2/1/11
to Fluent NHibernate
The wiki documentation for fluent mapping teases us with a link to
page about a deeper discussion of the various underlying collections
you can map. However, the target of that link is deleted (reportedly
for spam): http://wiki.fluentnhibernate.org/index.php?title=Fluent_mapping_collection_types&action=edit&redlink=1

I'm honestly at a loss of how to map a dictionary because "the cloud"
is filled with a mixture of old ways and new ways and no clear-cut
examples. I think including such examples in the Wiki, as I hoped
were on that missing page, would go a long way to reducing the
confusion.

Joe Brockhaus

unread,
Feb 2, 2011, 7:23:10 PM2/2/11
to fluent-n...@googlegroups.com
were you able to find a solution to your problem?
 
i'm having the same issue. er well, that is the same problem finding how to do it correctly.

Trinition

unread,
Feb 3, 2011, 6:09:05 AM2/3/11
to Fluent NHibernate
No, I'm still hunting.

James Gregory

unread,
Feb 3, 2011, 8:29:14 AM2/3/11
to fluent-n...@googlegroups.com
Well, there isn't any documentation specifically for mapping dictionaries.

Why don't you share your issue with us and we might be able to help. Dictionaries are a pain to map in FNH 1.1, but it's something we're striving to improve for 2.0 (and we've already made leaps and bounds in our master branch).

Joe Brockhaus

unread,
Feb 3, 2011, 5:32:48 PM2/3/11
to fluent-n...@googlegroups.com
for simple collections (non-dictionary):
 
i have my Domain posted here: http://j.mp/eOI3qS
 
1) What is the difference between AsBag() and AsList(), and how can i use AsList()? Should I?
2) Are the KeyColumn, Column, and ForeignKey all required? What do the various combinations of these methods mean?
3) If I use Not.LazyLoad() on NoteMap, do I need to use it on HasMany()?
4) What takes precedent for Cascade, when it's set on both? What does that really mean in both cases (on the HasMany & References, respectively)?
5) How does Inverse affect the save behavior? Is it appropriate for all cases?
 
In short: I haven't been able to get this working for both load, and save, without either breaking the load or the save, or removing constraints from my database.
 
This is a nHib question ply (except how it is affected by my fluent mappings..), but in my app, if I have a new Ticket with 3 notes created Client-side, and RIA sends them over the wire to be saved, and if the Notes are 'inserted' into my server-side datamodel before the Ticket, the save fails, even though I'm persisting them in a transaction after all have been added to the Session. If I force the Ticket to be inserted into the Session before the notes, it works, but only if I remove the FK relationship on the Note table. In addition, if the save completes, the FK's on the Notes aren't updated with the generated ID of the Ticket. In other instances, if I'm adding a note to an existing ticket, RIA won't send back the ticket instance over the wire, but my TicketID field is populated with it. However, NHib tries to insert a null, assumingly because Ticket was null, so TicketID couldn't be gleaned from the instance.
 
TBH, I can't imagine it's very helpful to try and explain all the possible mapping combinations I've tried, all the saving orders I've tried, and all the resulting errors. The above problems are just a small sampling from that. I think it would be a lot better to know how it should be done given my data model, mappings, and schema (which can be changed to make it work as it should).
 
 
public class TicketMap : ClassMap<Ticket>
{
        public TicketMap()
        {
            this.Not.LazyLoad();
            this.Table("TICKETS");
 
            this.Id(x => x.TicketID, Fields.TICKET_ID)
                .GeneratedBy.Sequence("SEQ_TICKET_ID");

            this.HasMany<Note>(x => x.Notes)
                .KeyColumn(Fields.TICKET_ID)
                .Not.LazyLoad()
                //.AsList();
                //.Inverse()
                //.Cascade.AllDeleteOrphan();
         }
}
 
public class NoteMap : ClassMap<Note>
{
        public NoteMap()
        {
            this.Not.LazyLoad();
            this.Table("NOTES");
         
            this.Id(x => x.NoteID, Fields.NOTE_ID)
                .GeneratedBy.Sequence("SEQ_NOTE_ID");
 
            this.References(x => x.Ticket)
                .Column(Fields.TICKET_ID)
                .ForeignKey(Fields.TICKET_ID)
                .Not.Nullable()
                //.Cascade.All()
                ;
         }
}

Joe Brockhaus

unread,
Feb 3, 2011, 8:27:48 PM2/3/11
to fluent-n...@googlegroups.com

nevermind the List question. I don't have indexes stored with the rowdata.

James Gregory

unread,
Feb 4, 2011, 5:35:39 AM2/4/11
to fluent-n...@googlegroups.com
You would do well to read the NHibernate documentation, as it covers a lot of your questions. 
http://www.nhforge.org/doc/nh/en/index.html There's a large section on collection mapping: 

1) What is the difference between AsBag() and AsList(), and how can i use AsList()? Should I?

A bag is an unordered collection. A list is a collection with an explicit index for each item.

2) Are the KeyColumn, Column, and ForeignKey all required? What do the various combinations of these methods mean?

No, they are not. Fluent NHibernate uses (mostly sensible) defaults for all these. You only need to specify them if you're changing the defaults. KeyColumn is used to specify the column used to join the two tables (the foreign key column in your child table). Column, I assume is on References, is the same as KeyColumn on collections (arguably inconsistently named), and ForeignKey is for specifying the foreign key constraint name used in schema generation.

3) If I use Not.LazyLoad() on NoteMap, do I need to use it on HasMany()?

It's up to you. You don't need to do anything. If you do it on both, then it won't matter which entity gets loaded. If you only do it on one, when you load the other entity the relationship will be lazy. I strongly urge you not to disable lazy loading. http://ayende.com/Blog/archive/2010/08/04/nhibernate-is-lazy-just-live-with-it.aspx

4) What takes precedent for Cascade, when it's set on both? What does that really mean in both cases (on the HasMany & References, respectively)?

Same as with lazy. The cascade used will be the one on the entity being saved.

5) How does Inverse affect the save behavior? Is it appropriate for all cases?

Inverse specifies which side of the collection is responsible for saving. This can be considered a kind of override to the default saving behaviour (of saving which ever entity you call Save with on the Session). Normally NHibernate will just save whatever you call Save with, but with the inverse set if there's a collection it may save the children first (or the inverse) depending on your setting. Decide which entity can be considered the owner of the relationship and set inverse appropriately.

Joe Brockhaus

unread,
Feb 4, 2011, 10:08:02 AM2/4/11
to fluent-n...@googlegroups.com
The reason I asked about KeyColumn, Column, and ForeignKey, was because originally, I could not get loading to work without specifying all of them. It seemed odd to me that if I have the KeyColumn set on the HasMany to the correct column for TicketMap, that I would not also need to define it on the NoteMap. After reading the ForeignKey documentation, it doesn't make sense to me that i needed it, as I'm not generating schema. So idk.

For LazyLoading ... perhaps I'm misunderstanding the consequences it will have on my architecture. With my Ticket.Notes example, when I Session.Query<Ticket>() .. I'll get back a set of all Tickets, and no notes? Then when RIA Services sends those all across the wire, will NHib do loads for all those Notes because in order for RIA to serialize the items, it has to read the collection, which spawns the loading? If that's the case, I suppose it's not a big deal. But if I close the NHib Session before the method completes, then it won't be able to LazyLoad, right? (Perhaps I should be maintaining the Session across service requests?)

Inverse ... sounds like what you're saying is the reason for my problems with respect to the order in which I save entities to the Session. Perhaps this question is rhetorical given the duplication needed for KeyColumn/Column, but .. If I don't specify Inverse on the HasMany<Note>, does that mean that the Note Reference inherently infers Inverse? 
-- " but with the inverse set if there's a collection it may save the children first (or the inverse) depending on your setting"
---- if I 'set' HasMany().Inverse() ... then the items in that collection will get saved first? afaik, there is only one way to 'set' inverse, and that's on the HasMany().

---------------------------------------------------------
Here's a problem that must be common:
-- Given my mappings, minus the ForeignKey(..) on the References() in NoteMap ..
1) Create and save a Ticket, with 3 Notes
2) In a different session, Load the entity
2) notice, after the load (this is lazy off) that the Ticket has 3 Note instances in its Notes collection. perfect!
3) however, inspecting the Note instances, observe that, while the Note was loaded and references correctly the Ticket, the TicketID field is 0.
4) to fix this, map the TicketID field using Map()
5) repeat step 1)
6) notice that the save fails, as nHib is trying to add an additional parameter to the generated insert statement, and the params are all out of order from with respect to the insert.
7) the fix? AFAIK, you have to map TicketID, and mark the References with Not.Insert().

Is that expected? Is there another, better way to solve this? Just seems counter-intuitive...but I suppose if you were foreign-keying off a column that you weren't mapping, then you wouldn't want to force References() to have an expression to map the field?


------
Joe Brockhaus
joe.br...@gmail.com
------------


--
You received this message because you are subscribed to the Google Groups "Fluent NHibernate" group.
To post to this group, send email to fluent-n...@googlegroups.com.
To unsubscribe from this group, send email to fluent-nhibern...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/fluent-nhibernate?hl=en.

James Gregory

unread,
Feb 4, 2011, 10:59:46 AM2/4/11
to fluent-n...@googlegroups.com
The fix is to not put your foreign key ids in your entities. If you need to represent that relationship in your domain then you should have a References (many-to-one) relationship there, not the foreign key. Putting foreign key ids in your entities is a big bad practice.

Your Ticket should have a HasMany(x => x.Notes) and your Note should have a References(x => x.Ticket). That's all you need. I assume you'll be saving the Ticket rather than the individual notes directly, so you'll probably want Inverse on the Notes relationship which'll make the Ticket responsible for cascading saves to each of the Notes.

Joe Brockhaus

unread,
Feb 4, 2011, 2:17:09 PM2/4/11
to fluent-n...@googlegroups.com
"If you need to represent that relationship in your domain then you should have a References (many-to-one) relationship there, not the foreign key"
-- not sure we're on the same page.

my Ticket table, by itself, has no knowledge of the instances in the Note table which have a foreign key column (namely Note.TicketID).

Ticket.Notes is mapped as:
        HasMany<Note>(x => x.Notes)
                                  .KeyColumn("TicketID") <-- that's the foreign key column on the Note table
    -- TicketMap also maps Id(x => x.TicketID)

Note.Ticket is mapped as: 
        References(x => x.Ticket)
                                   .Column("TicketID") <-- that's the foreign key column on the Note table.
    -- NoteMap also maps Id(x => x.NoteID)


this, alone, does not work.
-Note.TicketID will NEVER be populated in the resulting Note instances on load.
--- But it makes sense, because nothing maps the Note.TicketID field to the column "TicketID" on the Note table. 
--- This is why I had to Map(x => x.TicketID, "TicketID") in NoteMap. 
----- Doing so makes the LOAD work, but causes the save to fail, because NHibernate tries to add another, extra, parameter to the insert, which the insert sql does not have a placeholder for.
-------- This is why i had to add Not.Insert() to the References(..)

Now, as for saving .. there are 2 different ways a Note can be saved. Either it is saved WITH the Ticket (so it can get the TicketID generated in Oracle), the first time the ticket is saved, or it is being saved (and the ONLY thing being saved) for a ticket that already exists in the DB (and we already know the TicketID).

- In scenario 1, the user has created a new ticket, added notes to it, and clicked save. ALL those entities come back from RIA Services to my DomainService Insert methods. So, if I create 1 Ticket with 3 Notes, InsertNote() gets called 3 times, THEN InsertTicket() is called [by RIA Services]. In these methods, I do Session.Save(instance).
- In scenario 2, the user is 'editing' an existing ticket in order to add a new Note. The ticket instance already exists in the Ticket table, and so we already know the TicketID. Adding a Note to the Ticket.Notes collection does not make the Ticket dirty. When I save the Note, RIA Services ONLY sends the Note instance back to the server to be inserted. So ONLY InsertNote() will get called. Note.Ticket is null, but Note.TicketID is not. 

After these methods are called, RIA Services then calls the (overridden) PersistChangeSet() method, inside which I spin up a Session.BeginTransaction in a using{}, then trans.Commit() to persist it all to the database.

Now, effects of those 2 scenarios with the supposed 'all i need' mapping...
-- scenario 1 (ticket & 3 notes are all saved in the same transaction, but notes were added to NHib session first), the save fails, because NHib is passing a null for Note.TicketID (even though Note.Ticket is not, and even though Note.TicketID is populated)
-- scenario 2 (ticket already exists in database, only new Note is saved in transaction), the new Note has TicketID populated, but Ticket is null, and NHib tries to insert a null for TicketID again. 

Now .. if I go ahead and Map(x => x.TicketID, "TicketID") .. the LOAD works, but breaks the inserts:
-- save scenario 1: same problem with the order, but this time the error is because NHib is supplying an extra parameter to the insert. the error is an 'index outside of bounds' error. the NHib output logging shows clearly the disparity between the parameter count expectation by the insert sql and the extra param passed to it.
-- save scenario 2: same problem ... (again, RIA sends back null Note.Ticket, but Note.TicketID has a value) ... but still the param count mismatches the insert string.

So, like i was saying, adding Not.Insert to the References() in NoteMap solves this problem.

HOWEVER, my outstanding issues: (well, the most pressing atm) 
1)  the order in which I insert the items into the NHibernate session determines (appears to?) the order in which those items are persisted to the database, IN STARK CONTRAST to the relationships defined by the mappings. 
2) saving a NOTE, by itself (save scenario #2), causes a non-null constraint failure in Oracle, even tho Note.TicketID is set to a valid value. Note.Ticket is null, so it looks like NHib is nulling TIcketID as a result. But that's at odds with the original problem where TicketID was being nulled even when Note.Ticket was set (save scenario 1), because Note.TicketID wasn't mapped in NoteMap.
3) save scenario #1: if I force the 'correct' order of inserts into the NHib session (ticket first, then notes), the save 'works'. However, the cache never gets updated. So, before insert, the Note.TicketID == 0, because the Ticket.TicketID is 0, because it hasn't been inserted yet. After I insert, Note.TicketID is never updated to the Ticket's generated ID. If I query again, NHib loads the existing Ticket from the cache, including the 3 Notes, all of which have 0 for TicketID. Why isn't the Note being updated? This breaks the RIA association. (Ticket.Notes.Count == 0).  I said 'works' before, because there are 7 (not including sequence generation) SQL operations for inserting those 4 items (1 ticket, 3 notes): 1 insert Ticket, 3 Insert Note (with valid FK's, assuming i order it all correctly into the session), and 3 Update Note (updating values which are already valid and inserted).


I'm really hoping you can make sense of all that. I made it as clear as it can be. If you think it would help, I can give you the HBM output as well.


------
Joe Brockhaus
joe.br...@gmail.com
------------


On Fri, Feb 4, 2011 at 10:59 AM, James Gregory <jagregory.com@gmail.com> wrote:
The fix is to not put your foreign key ids in your entities. If you need to represent that relationship in your domain then you should have a References (many-to-one) relationship there, not the foreign key. Putting foreign key ids in your entities is a big bad practice.

Your Ticket should have a HasMany(x => x.Notes) and your Note should have a References(x => x.Ticket). That's all you need. I assume you'll be saving the Ticket rather than the individual notes directly, so you'll probably want Inverse on the Notes relationship which'll make the Ticket responsible for cascading saves to each of the Notes.

--

Joe Brockhaus

unread,
Feb 4, 2011, 2:56:40 PM2/4/11
to fluent-n...@googlegroups.com
If i add:
    Cascade.SaveUpdate() to the HasMany() in TicketMap && Not.Insert() on the NoteMap References()
.. save scenario no longer works: error is that the FK doesn't exist.
 -- unlike before, the Note Insert is NOT getting the updated TicketID. (though, I don't know if this is because of Not Insert or Cascade SaveUpdate. I'll try without Not Insert)

James Gregory

unread,
Feb 5, 2011, 3:42:07 AM2/5/11
to fluent-n...@googlegroups.com
I wrote a big reply to this which Google Groups has apparently ate. So you'll have to bare with me if this comes out a little terse, it wasn't like that originally.

1) If you've got a foreign key id in your entity *you're doing it wrong*. It really is as simple as that. Don't put your foreign key ids in your entities, use your relationships as they should be. What you're trying to do with Map(x => x.TicketId) is a hack, and will not work.

2) I think you should read up on Cascading in NHibernate, because you don't seem to be taking advantage of it. I say this because you've said you're calling InsertNote and InsertTicket separately. I think this has probably something to do with your RIA infrastructure, and if that's the case then you shouldn't bother with Cascades at all.

3) Your scenario 2 sets off alarm bells, because again you're using a foreign key instead of the object relation. NHibernate works with objects, not foreign key ids, if you're not setting the relation it isn't going to work property. This is a fundamental issue with using your entities in remoting, as there's always a mismatch between what the client is able to send and what your persistence mechanism needs. DTOs are the best way to handle this stuff, then you could receive an id from the client that you query on and populate the property appropriately in the entity.

I believe it's your infrastructure that hasn't been designed with NHibernate in mind. You need to take a step back from this and get your problem working in an isolated environment away from your existing infrastructure. Creating relationships in Fluent NHibernate is very simple, and the fact that you haven't been able to do it indicates you're either not telling us everything, or more likely your infrastructure is making you do something that NHibernate doesn't like. Create a new project with just FNH, and a clean database, and just try create a bi-directional relationship.

Joe Brockhaus

unread,
Feb 5, 2011, 4:45:13 PM2/5/11
to fluent-n...@googlegroups.com
google has done that to me enough that i never reply on the site.

As for everything else -- I'll have to think about the situation a little more.

I'm still going to make it work -- just not sure how, atm. 
Then the official answer won't be, "sorry, that kind of complicated isn't supported" ;-)

Joe Brockhaus

unread,
Feb 5, 2011, 7:27:31 PM2/5/11
to fluent-n...@googlegroups.com
James, what are your thoughts on this?
Seems to be a working example.
 
Furthermore, if NHib doesn't need my FK in the entity, then so be it.
How can i keep FNH from caring that my entity has the property?
If I will always have a Ticket reference, I can make TicketID based off it so that RIA Services can still function.

Joe Brockhaus

unread,
Feb 7, 2011, 12:02:25 PM2/7/11
to fluent-n...@googlegroups.com
"If you've got a foreign key id in your entity *you're doing it wrong*. It really is as simple as that."
-- That's clearly a matter of opinion.
 
-- Is it a hack because I'm doing it backwards? Shouldn't I be able to:

           References(x => x.Ticket).Column("TicketID").Not.LazyLoad()
           Map(x => x.TicketID).Not.Insert().Not.Update().Generated.Always()
-- References() takes care of setting the FK value in the database. Then mapping TicketID should work, because It will read the always-generated value from the database?
---- Just because YOU or I know that TicketID is a FK, doesn't mean NHib does, right -- It should just be another field. A field that is read from the database AFTER the row is inserted or updated?
 

------
Joe Brockhaus
joe.br...@gmail.com
------------


James Gregory

unread,
Feb 7, 2011, 2:19:10 PM2/7/11
to fluent-n...@googlegroups.com
I don't know. I've advised you not to put your foreign key ids in your entities. If you insist on doing it, I'm afraid I can't help you.

Joe Brockhaus

unread,
Feb 7, 2011, 3:59:00 PM2/7/11
to fluent-n...@googlegroups.com
OK fine.
 
forget my entity doesn't have another property which just happens to be a FK.
 
wow magic! look it's gone! you don't have to be confused anymore.
 
Now ... how about answering all of the questions which don't have anything to do with that?
 
Or if you need something easier, help me translate the automappings of that project into manual mappings?

Joe Brockhaus

unread,
Feb 7, 2011, 4:02:53 PM2/7/11
to fluent-n...@googlegroups.com

or you could answer all of my questions by prefacing your response with "If you take the TicketID property out of your domain ... "

James Gregory

unread,
Feb 7, 2011, 4:06:13 PM2/7/11
to fluent-n...@googlegroups.com
How about dropping the attitude, and maybe I can help you? If not, you're on your own. Fluent NHibernate is provided gratis, and support is provided out of my own free time, if you're not happy with either you know the way out.

If you can't be civil, I'll go spend my time on other things more important.

So, what specific questions would you like answering?

Joe Brockhaus

unread,
Feb 7, 2011, 4:07:13 PM2/7/11
to fluent-n...@googlegroups.com
or, if what i'm trying to do won't work because NHib is getting confused about a TicketID property on my domain with the same name as the TicketID column in the database, you could suggest that I put another column on my table that is GarbageColumnToEaseJamesMind and a property in my domain to match, and then have a trigger copy the value from TicketID to GarbageColumnToEaseJamesMind on insert and update, and then have NHib map that field as an 'always generated' field.
 
but if NHibernate isn't confused by the name, then what is the difference between mapping TicketID versus any other database-generated, non-ID field?
 
So far as I can tell, none.

Joe Brockhaus

unread,
Feb 7, 2011, 4:08:53 PM2/7/11
to fluent-n...@googlegroups.com
i'm sorry, you got the attitude first.
 
non-answers, personal-opinion-first responses ... i didn't come here asking to get into a jousting match about your preference, did i? I asked how i would do something, given the expertise of the community.

Joe Brockhaus

unread,
Feb 7, 2011, 4:12:08 PM2/7/11
to fluent-n...@googlegroups.com
lol. i'd donate to the cause but i dont think it would make you think any deeper than where you stopped, which was "boo your entity has a FK in it, and even tho it shouldnt matter (and even tho NHib should be able to work around it), i'm going to stop helping you because i dont like how you're doing it"
 

Rasmoo

unread,
Feb 7, 2011, 4:18:53 PM2/7/11
to Fluent NHibernate
Well, it's an abstraction layer. It is supposed to separate your
domain from your database. Using NHibernate to put surrogate keys and
such into your domain objects, is perhaps a bit like using your iPhone
to hammer in a nail. Most people will probably not want to see
that. ;-)

Joe Brockhaus

unread,
Feb 7, 2011, 4:42:54 PM2/7/11
to fluent-n...@googlegroups.com
well, so far this discussion has been about the community's (or at least a loud subset) disagreement with Microsoft's take on the matter.
 
I'm not trying nor wanting to get into the middle of that.
People are starting an argument with me about my domain. A domain which doesn't prevent me from accomplishing what i want, but makes the community shake their fist in the air.
 
A new set of classes identical-except-for-one-property classes that sit between my db abstraction (nhib) and my DTO (ria generated) seems like a lot of overhead for purist domain design from the perspective of database-abstraction.
 
I think your iphone-as-hammer metaphor is a lot exaggerated. the difference is the iphone isn't used as a hammer to nail different types of nails, yet my TicketID is merely one specialized type of nail.
 
The 'answers' i'm getting is more like someone asking a guy at the hardware store if he should use his metal hammer to hammer hardened nails, and the guy says no. But that's not complete -- The guy should really say, NO, NOT WITHOUT EYE PROTECTION.
Reply all
Reply to author
Forward
0 new messages