views:

450

answers:

2

Update

Well, as i was told by Stuart Charles and James Gregory, my model was weird enough to begin with. Apparently double-way references between classes while discriminating between the different kinds of relations isn't done, and apparently for a very good reason.

I tore the model down, changed it to a different one using HasManyToMany between Users and Fiches and then i added some coded validations to make sure my Users couldn't be linked to more than one Fiche.

So i guess the answer to this kind of problem is kind of a warning: "Don't do it" ;)

Thank you both for taking time to answer!


Hello everyone

The path to a correct use of Fluent Nhibernate is a hard one, and since i'm beginning to feel really stumped, i figured it wouldn't hurt to ask for help. Here's the thing.

The culprits

I have the following two classes, Fiche and User. A Fiche must have a User owner , and can have many User followers. On the other hand, a User can optionally be linked back to one and only one Fiche. (The EntityUpdateTrace base class contains a guid id and Create/Modification times, for PreSave/PreUpdate use)

Public Class Fiche
    Inherits EntityUpdateTrace(Of Fiche)

    Private m_Owner As User
    Public Property Owner() As User
        ' snip

    Private m_Followers As IList(Of User)
    Public Property Followers() As IList(Of User)
        ' snip

End Class

Public Class User
    Inherits EntityUpdateTrace(Of User)

    Private m_Fiche As Fiche
    Public Property Fiche() As Fiche
        ' snip

End Class

I then have the following mapping

Public Class FicheMapping
Inherits ClassMap(Of Fiche)
Public Sub New()
    [Not].LazyLoad()
    Id(Function(f As Fiche) f.Id).GeneratedBy.GuidComb()
    References(Of User)(Function(f As Fiche) f.Owner).Not.Nullable()
    HasMany(Of User)(Function(f As Fiche) f.Followers).Inverse()
End Sub
End Class

Public Class UserMapping
    Inherits ClassMap(Of User)
    Public Sub New()
        [Not].LazyLoad()
        Id(Function(a As User) a.Id).GeneratedBy.GuidComb()
        References(Of Fiche)(Function(a As User) a.Fiche).Nullable()
    End Sub
End Class

The question

What the hell am i doing wrong? :) I can't seem to have this model working although it looks deceptively simple.

  • Fiche loads all User its related to; sounds logical, and i guess i should discriminate on a specific parameter, but since it would be something entirely database related i don't really like the idea (i'm trying to keep the classes squeaky clean as an exercice in domain hygiene). So if the Fiche veryImportantFiche is created by Alice and followed by Bob, when reloading the followers i find Alice and Bob. Is there a way to discriminate between the two type of User?

  • I would like to use a List in the database to keep the followers ordered, but as soon as i change the mapping to

    HasMany(Of User)(Function(f As Fiche) f.Followers).AsList().Inverse()

the following error pops up:

null index column for collection: Followers

The amount of information on this error is not really overwhelming, which leads me to suppose i'm taking the wrong way to this model, which leads me back to the fist question... What the hell am i doing wrong?

Thank you all for reading, and for any insight into this!

+1  A: 

The List error happening due to the semantics of lists. It's tricky because bags also get mapped to IList by default, but this is due to the lack of a collection type with "bag" semantics in the .NET framework. In a "bag," conceptually, there is no order because items are just thrown into the bag and removed in any order and could easily be moved around inside the bag. A list, however, is inherently ordered and indexed (think of it as an array with additional semantics, so each element has an unique index value). The error message you are getting indicates that NHibernate wants a column that stores the index value for each element (so it must be unique and, since it's an IList, ant integer value). Since you don't appear to have an index column, you'll have to order them another way, perhaps by adding an ORDER BY clause in your query.

As for the first issue, you might need to rethink how you are working with this. Actually, you might be able to leverage NHibernate filters, but I don't have any experience with them to say how it might work out. Your Followers collection is really all users that are related to the Fiche (all Users that reference Fiche), so it's more like RelatedUsers. Using the Repository Pattern, you could have a method like FindFollowers(Fiche fiche) that would query for all RelatedUsers where the User does not equal the Owner. This pattern might additionally benefit you since you wanted to have the Users be ordered. Something like (C#, Criteria query):

session.CreateCriteria(typeof(Fiche))
    .CreateCriteria("RelatedUsers", JoinType.InnerJoin)
        .Add(Restrictions.Not(Restrictions.Eq("Id", fiche.Owner.Id)))
        .AddOrder(Order.Asc("PropertyNameToOrderBy"))
        .List<User>();
Stuart Childs
About the first issue, i don't really see why the indexed column for the list should be the responsibility of the mapped poco class, i find it weird...I'm not sure i understand completely; from where i stand, what you're suggesting is that i should take the Followers relationship out of the Fiche mapping and replace it with a call to a Repository query. But the query you gave as an example uses the mapped relationship (called RelatedUsers in your case); i tried the query and it complained about the property...
samy
Which property did it complain about? What was the error message?
Stuart Childs
I changed the Fiche class and the Fiche mapping to remove the Followers property. When i try the query, i get the following error: could not resolve property: Followers of: DALAGScan.FicheI think that i misunderstood you at some point, because on one hand i understand that i cannot query on a property that isn't mapped, but on the other hand if i map it i won't have the desired behavior from Nhibernate...
samy
Right, you can (and must) leave the collection property on the mapping. I was more talking about renaming Followers to RelatedUsers, but you are of course by no means required to do this. For me it simply makes more sense since, by default, that collection will reference *all* User objects related to the Fiche. "Followers" is a subset of the related User objects. Also, I agree with James. In all likelihood, you'll want to reuse User objects for multiple Fiche objects so you'll need a many-to-many.
Stuart Childs
I finally get the property renaming :) Anyway, if i read correctly, the query you proposed was supposed to overload the Nhibernate generated query? If that's correct, i didn't know about the ability to "unplug" some queries...
samy
+1  A: 

It sounds like you need a many-to-many instead of a one-to-many. That way a single user can be referenced by multiple fiches.

As for the ordering of your collection, AsList won't work unless you have an index column in your table; this is a column that contains a numerical value for each record, with no gaps. If you don't want this behavior, then you'll need to use the order-by attribute.

James Gregory
I finally tried to do it your way; i initialized a mapped column called UniqueIntId with a different integer with no gaps for each User. Then i changed the mapping and told the AsList() method that i wanted to use the UniqueIntId as index. Alas no luck, it insisted on behaving the same way.Are there any examples of this functionality online?
samy

related questions