Since I like to give clear and concise examples, lets go through this step by step. This example is extremely simple, but it illustrates the point beautifully.
We are going to map two tables - TestUser and TestFullName. In this example, every user needs to have a full name saved with them. There can be only one name for each user. The two tables are connected on the UserId so TestUser.UserId = TestFullName.UserId. OK. Lets get all Fluenty (I made that word up)!
Step 1 - The Database Tables
You can create the two tables in any way you deem appropriate. The tables should have the columns specified in the following image:
(CLICK ON THE IMAGE TO SEE A BIG VERSION OF IT)
Notice the one to one key designation. Also, as you can see, each table has UserId as the primary key, which is the key feature of a one to one mapping in Fluent NHibernate. As you can also see, a relationship has been defined on the two tables. That relationship establishes the TestUser table as being the Primary Key table and the TestFullName table as being the Foreign Key table. This is EXTREMELY important in the mapping. If you accidently set the mapping up using reverse syntax, your mapping will not save and you will sit in front of the screen banging on your head asking yourself, "WHY GOD WHY WON'T THIS SAVE?" Now that we have the tables created, the primary keys assigned and the relationship mapped, we are going to go to step 2 and create the classes.
Step 2 - The Classes
public class TestUser
{
public virtual Guid UserId
{ get; set; }
public virtual string Username
{ get; set; }
public virtual TestFullName FullName
{ get; set; }
}
public class TestFullName
{
public virtual Guid UserId
{ get; set; }
public virtual string FullName
{ get; set; }
public virtual TestUser TestUser
{ get; set; }
public virtual void AssignUser(TestUser user)
{
TestUser = user;
user.FullName = this;
}
}
As you can see, I have a property in both tables that references the other table. Notice also that there is no IList mapping in there because this is a true one to one. Do you see how I created a property that allows you to assign a user?
Step 3 - The Actual Mappings
NOW, here is where it gets pretty guys and gals. Why do I say that? Well if you have been looking for how to do a one to one mapping using Fluent NHibernate, you have likely seen example after example of hacks that just don't feel correct due to the convoluted nature of the answers. It isn't as bad as you thought it would be. I am using the AutoMap feature of Fluent NHibernate to do a one to one mapping, so the code below includes the Override keyword. With the AutoMap feature you don't have to worry about class maps - unless of course you want to. Here is the mapping.
.Override
(map => map.Id(f => f.UserId))
.Override
(map => map
.HasOne(x => x.FullName)
.Class
.Constrained())
.Override
(map => map.Id(f => f.UserId)
.GeneratedBy.Foreign("TestUser"))
.Override
(map => map
.HasOne( x => x.TestUser)
.Class
.Cascade.All()
))
OOOhhhhhh Yeah! Doesn't that look great? Of course it does. Let me explain a little bit about why I did things this way.
First, you will notice that I map the Id. This is because the Id is not clear to Fluent NHibernate. Since UserId isn't intuitive based on the table names, I had to use the map feature to add an Id. Notice that I also use .GeneratedBy.Foreign. This tells Fluent NHibernate where the Id is coming from. Next, you can see that I've thrown Constrained() in there. That tells NHibernate that there is a Foreign key constraint on TestUser that refers to the primary key in TestFullName. On the other side I use a Cascade since things like Updates, Deletes, etc. should be persisted across both entities. There is one additional very important thing to notice here. I use .HasOne on BOTH sides of the mapping. All of these things ensure that you each instance saved will have the same primary key value across both tables.
I'm not showing you how exactly how to save things because I don't know how you have everything set up, but here is the outline of what you do.
Step 4 - Saving Things
- Assign a new user to a name using the handy-dandy .AssignUser method we created in Step 2.
- Save the name.
- Save the user
That's it! Aren't I great, super, fantastic and wonderful? Of course I am! This is how to easily map a one to one relationship in Fluent NHibernate using .HasOne. Now remember, don't go getting yourself confused about what a one to one is. James Gregory has spent so much of his time telling people on Stack Overflow and the Fluent NHibernate group that they want a many to one and not a one to one that I'm surprised he isn't in a straight jacket sitting in a padded room somewhere (Maybe He Is...). So read the documentation on Fluent NHibernate and use this example of how to map a one to one in Fluent NHibernate so you can look super smart.
Smooches,
Kila Morton
Problem:
You can't map a one to one in Fluent NHibernate using .HasOne OR you need to know how to do a one to one mapping in Fluent NHibernate.
Solution:
I won't list it here. You have to read everything above on this one!
As a side note, James Gregory is one of the creators of Fluent NHibernate. I think he has likely rubbed the skin off of his fingers due to how many times he has had to explain that many of the mappings that people consider to be one to one are actually many to one. You can take a look at his post here. We have a true one to one Fluent NHibernate mapping here so I don't think that we will get on James Gregory's bad side.
2 comments:
Hi! I can't get your solution. If I have "constrained(true)" on the one end and "generated by foreign" on the other - how items would be saved? How can I save name before user, if its Id mapping has "GeneratedBy.Foreign("TestUser")? So user must be saved first, but it can't because of constrained violationon name.
PS. A database schema I'm using is generated by fluent nhibernate
PS2. What's the trick? )
Post a Comment