Showing posts with label Fluent NHibernate. Show all posts
Showing posts with label Fluent NHibernate. Show all posts

Saturday, August 28, 2010

How To Do A One To One Mapping In Fluent NHibernate Using HasOne! It Is So Easy! (Lets Map A One To One With Fluent NHibernate..YEAH!!)

I am going to give you a solid example of a One to One Fluent NHibernate mapping. Fluent NHibernate can be a fabulous tool once you learn how to use it. Once you get the hang of it, it is so much simpler than Entity Framework. I know that your eyes may bulge at that statement because what could be easier than dragging and dropping on a GUI. Automapping in Fluent Nhibernate is really even easier than that - ONCE YOU UNDERSTAND HOW TO DO CERTAIN THINGS. One of the ways you can learn more about Fluent NHibernate is by reading the documentation, however, sometimes that is not enough. One of the things that I found when trying to figure out how to do a one to one mapping using .HasOne is that the documentation on using .HasOne is sparse at best. What I found, over and over, was that there were no EASY, reasonable, concrete, full examples of exactly how you should map a one-to-one in Fluent NHibernate using .HasOne. So, I sat down and invested some time in figuring out how to do a one-to-one mapping in Fluent NHibernate using .HasOne and now I'm going to make things easier for you by telling you how to do it without adding all kinds of crazy things to your code and without pulling out your hair.

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

  1. Assign a new user to a name using the handy-dandy .AssignUser method we created in Step 2.
  2. Save the name.
  3. Save the user
These things need to be done in order to avoid conflicts.


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.

Tuesday, August 24, 2010

One Solution To The Fluent NHibernate Error "The given key was not present in the dictionary."

Problem:
You have set up some mappings and you get the following Fluent NHibernate error - "The given key was not present in the dictionary."

Solution:
This error can be generated for multiple reasons. One of the reasons is that you forget to map an entity. Before you go any further, check that you have mapped EVERY entity and check that you have registered every entity if you are using something like StructureMap or any other IOC.

Smooches,

Kila

Saturday, August 14, 2010

NHibernate Not Saving And You Get A One Way To Fix Unexpected row count: 0; expected: 1 Error In NHibernate

I love NHibernate and I love Fluent NHibernate. Fluent NHibernate allows you to use NHibernate without the XML configuration files that would normally be required by NHibernate. In fact, by using the AutoMap feature of Fluent NHibernate, you can use NHibernate without writing ANY additional lines of code beyond adding virtual to your POCO classes. That sounds great.

Sometimes, however, NHibernate can give you error messages that aren't as intuitive as they could be. This is a small complaint because we software developers can always figure out how to do things....right? So you have created your classes, set up Fluent NHibernate and tried to save something only to find that NHibernate is not saving your entity. You try and try, but NHibernate will not save your entity. You look at the error message and you see the following:

Unexpected row count: 0; expected: 1

That message doesn't look good. Why won't NHibernate save? Whatever could the NHibernate problem be? Well I don't know all of the reasons that this error message might occur, but I do know one reason that I'm going to tell you about.

If you are using GUIDs or INTs as the primary key on your entity, NHibernate needs to see that entity id as empty so that it will know that the item you are saving is new. You will get the error shown above if you try to add the GUID or INT yourself. I know. I know. This is kind of counter to how you do things in Entity Framework, but isn't NHibernate smart? It can and will generate the id for you so you can sit back and relax (well you can't really relax, but NHibernate will create the id for you). To solve the problem, do not try to assign an int or Guid to your id. When NHibernate sees that the value is empty, NHibernate will recognize this to be a new item and perform a database Insert instead of an Update.

Wasn't that simple. I just told you to do nothing and I solved your problem.
Hope this saves you some time!

Smooches,

Kila

Problem:

Understanding Fluent NHibernate - The entity doesn't have an Id mapped. Use the Id method to map your identity property. For example: Id(x => x.Id)

The entity 'YourEntityName' doesn't have an Id mapped. Use the Id method to map your identity property. For example: Id(x => x.Id). Don't you just love Fluent NHibernate error messages?

So you have set up your database, got your tables together, created some classes and tried to use Fluent NHibernate to map and......you get the following - The entity 'YourEntityName' doesn't have an Id mapped. Use the Id method to map your identity property. For example: Id(x => x.Id). It seems like a straightforward error right? It looks like Fluent NHibernate is looking for an Id and you don't have it. But you think to yourself, "Self...you know you put that YourPropertyNameId in there. What could be going on?"

Well the truth is that the error is not as verbose as it seems. It means EXACTLY what it says. Let us say for a moment that you have a table named Projects. This Projects table has a primary key column called ProjectId. You are using Fluent NHibernate and NHibernate in ASP.NET MVC to automagically generate NHibernate mappings so that you don't have to do it by hand. Well, Fluent NHibernate expects to see that primary key column name displayed as Id NOT as ProjectId! What???? That is correct - Fluent NHibernate can't be expected to just "know" what everyone using it will name the primary key - soooo when you are using the AUTOMAPPING FEATURE that allows you to generate mapping files WITHOUT typing a line of code, you have to give those identity columns a nice GENERIC name of Id. Using something like {get; private set;} is not enough. If that naming convention doesn't work for you, you can do a few things. You can override the IsId feature. The override below, which would be placed in the configuration file, specifies that anything with the EntityNameId is the Id.


public override bool IsId(Member member)
{
return member.Name == member.DeclaringType.Name + "Id";
}


Now, that is the way to do it IF you are using configuration files. I don't.
I should because that was the suggested upgrade from Fluent NHibernate 1.0 to 1.1,
but I don't because the creator in his infinite wisdom still allows me to do it like this.



public ISessionFactory GetSessionFactory()
{
var connectionString = ConfigurationManager
.ConnectionStrings["YOUR_CONNECTION
_STRING_NAME_AS_DEFINED_
IN_YOUR_WEB_CONFIG_FILE
"]
.ConnectionString;

//Fluent NHibernate configurartion
var sessionFactory = Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2005
.ConnectionString(x => x
.Is(connectionString)))
.Mappings(m => m.AutoMappings
.Add(AutoMap.AssemblyOf<YOUR_ENTITY_NAME>()
.Setup(cfg =>
{cfg.FindIdentity = member => member
.Name == member
.DeclaringType.Name + "Id";}
)))
.BuildSessionFactory();

return sessionFactory;
}


For
YOUR_ENTITY_NAME shown above, you can select ANY class in the directory that you want. Fluent NHibernate just uses that class to tell where ALL of your classes are so don't worry about which one to specify there. You can also use Fluent NHibernate to generate the actual mapping classes decorated with ClassMap and then make any adjustments . However, that means that you can't use the Automapping magical save-me-some-work feature.

Fluent NHibernate can make your life sooo much easier if you are using NHibernate, but you have to read the automapping area on the Fluent NHibernate Wiki if you really want to be able to use it to its fullest potential. READ IT HERE :-). No one likes to spend precious minutes/hours looking for why you get small issues like this, but if you read the wiki maybe you won't have to. I promise you that it won't hurt!

Smooches,
Kila

Problem:
When using Fluent NHibernate with the AutoMap feature, you get the following error - The entity 'YourEntityName' doesn't have an Id mapped. Use the Id method to map your identity property.

Solution:
Rename your primary key from whatever it was to Id or create the ClassMap files by hand and designate the column to be used as your primary key in that class. Don't forget to change your Fluent.Configure .Mappings area to something like this - .Mappings(x => x.FluentMappings.AddFromAssemblyOf() - so that you remove the Automap from the Mappings attribute.

You can also designate your column as a primary key by using the hbm.xml file structure, but that is too much work and you don't have type checking to make sure you didn't screw something up AND you defeat the purpose of Fluent NHibernate. Don't defeat the purpose people - just don't do it!