views:

3388

answers:

4

Whenever I load a Task class, the Document property is always null, despite there being data in the db.

Task class:

public class Task
{
    public virtual Document Document { get; set; }

Task Mapping override for AutoPersistenceModel:

public void Override(AutoMap<Task> mapping)
{
    mapping.HasOne(x => x.Document)
        .WithForeignKey("Task_Id");

As you can see form what NHProf says is being run, the join condition is wrong, the WithForeignKey doesnt seem to take effect. In fact, i can write any string in the above code and it makes no difference.

FROM   [Task] this_
    left outer join [Document] document2_
    on this_.Id = document2_.Id

It should be:

FROM   [Task] this_
    left outer join [Document] document2_
    on this_.Id = document2_.Task_Id

If i hack the data in the db so that the ids match, then data is loaded, but obviously this is incorrect - but at least it proves it loads data.

Edit: rummaging in the fluent nhib source to find the XML produces this:

<one-to-one foreign-key="Task_Id" cascade="all" name="Document" class="MyProject.Document, MyProject, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />

Edit: heres the schema:

CREATE TABLE [dbo].[Document](
[Id] [int] IDENTITY(1,1) NOT NULL,
[Task_Id] [int] NOT NULL,

CREATE TABLE [dbo].[Task](
[Id] [int] IDENTITY(1,1) NOT NULL,

Anyone got any ideas?

Thanks

Andrew

+3  A: 

I think the problem here is that the "HasOne" convention means that you are pointing at the other thing(the standard relational way to say "Many To One"/"One to One"); By putting a Task_ID on the document the actual relationship is a HasMany but you have some kind of implicit understanding that there will only be one document per task.

Sorry - I don't know how to fix this, but I will be interested in seeing what the solution is (I don't use NHibernate or Fluent NHibernate, but I have been researching it to use in the future). A solution (from someone with very little idea) would be to make Documents a collection on Task, and then provide a Document property that returns the first one in the collection (using an interface that hides the Documents property so no one thinks they can add new items to it).

Looking through documentation and considering eulerfx's answer, Perhaps the approach would be something like:

References(x => x.Document)
    .TheColumnNameIs("ID")
    .PropertyRef(d => d.Task_ID);

EDIT: Just so this answer has the appropriate solution: The correct path is to update the database schema to match the intent of the code. That means adding a DocumentID to the Task table, so there is a Many-To-One relationship between Task and Document. If schema changes were not possible, References() would be the appropriate resolution.

Chris Shaffer
A possibility I agree, but why doesn't Task_Id get rendered anywhere? (even if its wrong and would therefore cause an exception - but it doesnt)
Andrew Bullock
That does work, however it means i need a Task_id property on Document, which i'd rather not have. Works for now though thanks
Andrew Bullock
+3  A: 

You should use:

References(x => x.Document, "DocumentIdColumnOnTask")

eulerfx
tblDocument has Task_Id, not tblTask has Document_Id
Andrew Bullock
Oh ok missed that part. Then I suppose it would make the most sense to have a docID on the task table. Otherwise the table structure indicates that there maybe mulitple documents for a task.
eulerfx
Yeah I suppose it does. There was a reason its like it is but i cant remember why. Ill have a think and consider reversing it. ta
Andrew Bullock
A: 

As eulerfx pointed out,

the table structure indicates that there maybe mulitple documents for a task

and Chris stated:

By putting a Task_ID on the document the actual relationship is a HasMany but you have some kind of implicit understanding that there will only be one document per task.

This is of course correct so I have reversed it so Task has a nullable Document_Id.

Thanks to both of you for you help!

I flipped a coin for the accepted answer, if i could tick both i would!

Andrew Bullock
hey andrew, PLN's in defo the answer. I would say that by re-doing the db you've actually done the right thing. But sometimes you cannot change the DB, and this is where the Fluent Mappings really shine. HasOne is one such feature, and both you and I were using it incorrectly. PLN points to the right way of using it... my 2 cents
andy
+14  A: 

I ran into the same issue today. I believe the trick is not to use .ForeignKey(...) with the .HasOne mapping, but to use .PropertyRef(...) instead. The following is how I define a One-to-one relationship between an Organisation (Parent) and its Admin (Child):

HasOne(x => x.Admin).PropertyRef(r => r.Organisation).Cascade.All();

The Admin has a simple reference to the Organisation using its Foreign Key:

References(x => x.Organisation, "ORAD_FK_ORGANISATION").Not.Nullable();

When retrieving an Organisation, this will load up the correct Admin record, and properly cascades updates and deletes.

PLN
You are my hero of the day! :)
Arnis L.
You sir saved my day!
ElvisLives
You are awesome sir, thank you very much!
Chance
dude, you. are. the. man! how is your answer not "The Answer"?? dude!
andy
Perfect answer.
Jim Schubert
Worked a treat. Thanks
nfplee

related questions