As I continue my research this weekend I decided to get into understanding the basics of the various types of mapping we commonly see in data driven programming: one-to-many, many-to-one, many-to-many.
To start, here is the entity class for Series:
1: public class Series
2: {
3: public virtual int SeriesId { get; private set; }
4: public virtual string Name { get; set; }
5: public virtual Studio Studio { get; set; }
6: public virtual IList Seasons { get; set; }
7: public virtual IList Genres { get; set; }
8: }
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
And now a look at the mapping file:
1: public class SeriesMap : ClassMap
2: {
3: public SeriesMap()
4: {
5: Id(x => x.SeriesId);
6: Map(x => x.Name)
7: .WithLengthOf(100).Not.Nullable();
8: References(x => x.Studio, "StudioId");
9: HasMany(x => x.Seasons)
10: .WithTableName("Seasons").WithKeyColumn("SeriesId");
11: HasManyToMany(x => x.Genres)
12: .WithTableName("SeriesGenres")
13: .WithChildKeyColumn("GenreId")
14: .WithParentKeyColumn("SeriesId");
15: }
16: }
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
To begin lines 5 & 6/7 are calls to simple mapping functions to define the unique identifier for Series and the Name field. The code on the subsequent lines instruct NHibernate what relationships exist and where to map the data.
The first call is to the overload for References which allows to not only specify which property of the mapped object to point at, but also what columns represents the foreign key (in this case StudioId). This is enough to declare the declaration with the minimal amount of extra information. Here is the code again:
Well take a step now and define the other side of a one-to-many relationships, using the HasMany function. This function defines, in this case, that our Series has many Seasons. We make a subsequent call to .WithTableName to dictate which table the collection will pull from (the type is declared via the generic parameter) and we define the column on THE JOINING TABLE that will indicate a match, SeriesId in this case.
The final type of relationship we will show is the Many-to-Many. Most ORMs that I have dealt with have always had difficulty handling this sort of relationship so complexity needed to be introduced either on the configuration side or the development side. In this case, we introduce a SLIGHT amount of complexity on the configuration side, but as you can see from the code, it really is minute.
HasManyToMany(x => x.Genres) .WithTableName("SeriesGenres") .WithChildKeyColumn("GenreId") .WithParentKeyColumn("SeriesId");
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
To begin we make our call to the generically typed function HasManyToMany passing the type we are going after, in this case instances of the Genre class. The call to WithTableName denotes which table is the intermediary in this relationship and the two column calls dictate what columns to look at with what we are going after and from where we are coming. Overall, very well simplified.
Initially I had a lot of trouble with this code because my queries kept breaking due to bad SQL generation. I could tell the reason was because NHibernate was assuming a standard naming convention under the hood, that I was not using because this is a database that I use for many person projects; changing table names would break the other applications. I finally consulted with @jagregory asking about the way one tells NHibernate that a different table name is to be used; I assumed it would be an attribute on the entity, but he indicated it was a simple method call, like such, within the mapping file:
1: public class SeasonMap : ClassMap
2: {
3: public SeasonMap()
4: {
5: WithTable("Seasons");
6: Id(x => x.SeasonId);
7: Map(x => x.Name).WithLengthOf(100).Not.Nullable();
8: Map(x => x.Position);
9: }
10: }
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
Line 5 is the key method call here, with the method inherited from ClassMap. I did send my suggestion to the Fluent Team that perhaps an attribute would be better suited, however, I think the way they have it is best from an extensibility standpoint so that I can reuse mapping code.
The one thing I would like to be able to do, and I have yet to find an ORM yet that can do this easily is weighted relationships. In the example above you see that a Series has many Genres, but what you don’t see is that there is a weight associated with the Genre as the Series may be more Action then Drama for instance. The only way I can see to do this now with the ORMs that I have used is via two One-To-Many relationships with a derived class from the traditional Genre entity you see above. The weight value is stored in the SeriesGenres table along with the SeriesId and GenreId. If anyone has any ideas how to create this, please send a reply to @jfarrell on Twitter.