Introduction
I thought it would be useful to document what it would
take to incorporate building a web application that is designed with the
Microsoft MVC framework, that also incorporated using NHibernate as a O/R
Mapper and uses Microsoft Unity as the Dependency Injection, IoC
framework. I must confess, I am not an
expert of any of these tools, so I welcome feedback from the community. The reason I am doing this to begin with, is
there is not a whole a lot of documentation on of this “stuff” by themselves let
alone all together, so I am hoping fill a little bit of that void. If you think there are better approaches then
what I am doing, feel free to provide feedback.
I am basically doing this for my
own enrichment and if it is also helpful to the community, then—well—even
better.
Since this going to have to be a series of posts, I
will probably not cover every aspect of this application all at once, so if
what you are looking for is not in this post, then be patient and maybe I will
get to it in a later one. As a matter of
fact, since I am going to be adhering to a test first approach (red, green,
refactor), I will probably not get to the MVC framework until several posts
from now. The first few posts will only
be covering tests.
Setting up NHibernate
I have mentioned this before, but if you are new to
NHibernate and don’t know how to get started, I highly recommend the Summer of NHibernate
videos by Stephen Bohlen. The download
of NHibernate is located here. Once downloaded, the first thing I need to do
is put the NHibernate schema files in the Visual Studio schema folder (my
folder is located here C:\Program Files\Microsoft Visual Studio
9.0\Xml\Schemas) so I can get intellisense on the configuration and mapping
files.
In general, for all the external libraries, it is
useful to place them all in a folder location relative to, or just inside your
solution so, for example, if you are using source control the other developer machines
will pick up the references without any problems. Thus, I am doing the same with this
application, and will then be referencing the NHibernate.dll in all my relevant
projects. In my case, I am only going to
have a Web, Core, and Test project so I will reference it for now in the Core
and Test project. I will probably need
to reference in the web project once I set up Unity, but I will leave it out
for now.
The next thing I will need to do is to create a
NHibernate configuration file. One of
the cool concepts NHibernate follows is the Convention
over Configuration paradigm.
That is, as long as I follow a certain convention, I will not need to
configure certain aspects of NHibernate when setting it up. So if I create a configuration file and name
it hibernate.cfg.xml, then set is build action to copy to output folder, I will
not need to tell NHibernate where the configuration file is. Note:
I tried this in a Microsoft Team Test project and for some reason the
test project would not copy that file to the output folder so I ended up having
to configure the path anyway.
Here is the configuration file that is in the root
directory of my test project.
1 <?xml version="1.0" encoding="utf-8" ?>
2 <hibernate-configuration
xmlns="urn:nhibernate-configuration-2.2">
3
<session-factory name="NHibernate.Test">
4
<property name="connection.driver_class">NHibernate.Driver.SqlClientDriver</property>
5
<property name="connection.connection_string">
6
Data Source=(local);Initial Catalog=News;Persist Security Info=True;User
ID=my_dev;Password=my_dev
7
</property>
8
<property name="adonet.batch_size">10</property>
9
<property name="show_sql">true</property>
10
<property name="dialect">NHibernate.Dialect.MsSql2005Dialect</property>
11
<property name="use_outer_join">true</property>
12
<property name="command_timeout">444</property>
13
<property name="query.substitutions">true 1, false 0, yes 'Y', no 'N'</property>
14
<mapping assembly="News.Core"/>
15
</session-factory>
16 </hibernate-configuration>
Notice that in the root tag that since I place the
NHibernate schema files in the Visual Studio schema folder I now have access to
intellisense. You can see the property
setting definitions here,
but here are the important nodes
·
The show_sql property is going to be useful
when debugging so I can see the SQL statements.
I will turn this off in production because it is expensive.
·
The dialect is going to tell NHibernate to
what specific database language and version it is going to translate the SQL
to.
·
The mapping tells NHibernate where the
mapping files and classes are located.
Once the configuration file is created, I then can call
it from my code and create the ISessionFactory.
I will talk about my approach later in another post, but essentially
because ISessionFactory is expensive to create and there also some threading
concerns with creating it; I am going to create it differently in the test
project than in the web project. In both
cases, I am going to implement an interface I created call
ISessionFactoryManager. This interface,
as of now, will have one method called GetSessionFactory. The code in my test project looks like this.
1 using News.Core.Data;
2 using NHibernate;
3 using NHibernate.Cfg;
4
5 namespace News.Web.Tests
6 {
7
internal class TestSessionFactoryManager : ISessionFactoryManager
8
{
9
public ISessionFactory GetSessionFactory()
10
{
11
string path = @"C:\Projects\News\Src\News.Web.Tests\hibernate.cfg.xml";
12
var cfg = new Configuration();
13
cfg.Configure(path);
14
return cfg.BuildSessionFactory();
15
16
}
17
}
18 }
Note: If I want, I can also add properties at run
time. For example, say my connection
string is stored in a super secret place; I could get it and set the
ISessionFactory with the following code.
It just has to be done before the BuildSessionFactory is called, because
once called, it cannot be changed.
15
cfg.Properties.Add("connection.connection_string",connectionString);
Once the BuildSessionFactory method is called,
Nhibernate goes and retrieves the configuration file, and also the Mapping
files (I will discuss later) and returns the session factory. I now can open sessions to the database and
do whatever database CRUD I need to do.
Mapping Tables to Classes
For my example, I am going to have two tables with the
following schema:
For this post, I am only going to worry about the News
table. Again, using convention over
configuration, I have mapping file named NewsItemDto.hbm.xml so I do not have
to tell NHibernate where it is. I need
to make sure this file is an embedded resource, so it can be referenced. The content of the file looks like this:
1 <?xml version="1.0" encoding="utf-8" ?>
2 <hibernate-mapping
xmlns="urn:nhibernate-mapping-2.2" assembly="News.Core" namespace="News.Core.Dto">
3 <class name="News.Core.Dto.NewsItemDto,
News.Core" table="News">
4
<id column="NewsId" name="NewsId" type="long" unsaved-value="0">
5
<generator class="native"></generator>
6
</id>
7
<property column="AuthorId" name="AuthorId" type="long" not-null="true"/>
8
<property column="DateAdded" name="DateAdded" type="DateTime" not-null="true"/>
9
<property column="DateUpdated" name="DateUpdated" type="DateTime" not-null="true"/>
10
<property column="DatePublished" name ="DatePublished" type="DateTime" not-null="true" />
11
<property column="Title" name="Title" type="string" not-null="true"/>
12
<property column="ShortDescription" name="ShortDescription" type="string" not-null="false"/>
13
<property column="Body" name="Body" type="string" not-null="true"/>
14
<property column="IsFrontPage" name="IsFrontPage"/>
15
<property column="IsPublished" name="IsPublished"/>
16
</class>
17 </hibernate-mapping>
Again, the root references the NHibernate mapping
schema which will give me intellisense.
You can see all the property settings here. In the class, I tell it the namespace and
assembly name where the class is located.
The ID identifies the primary key of the table. By setting the generator attribute to native,
I am telling NHibernate that the field is an identity field and the value is
created by the database.
Here is the code for the class:
5
public class NewsItemDto
6
{
7
public virtual long NewsId { get; set; }
8
9
public virtual long AuthorId { get; set; }
10
11
public virtual DateTime DateAdded { get; set; }
12
13
public virtual DateTime DatePublished { get; set; }
14
15
public virtual DateTime DateUpdated { get; set; }
16
17
public virtual string Title { get; set; }
18
19
public virtual string ShortDescription { get; set; }
20
21
public virtual string Body { get; set; }
22
23
public virtual bool IsFrontPage { get; set; }
24
25
public virtual bool IsPublished { get; set; }
26
}
The properties are all virtual so NHibernate
can utilize the proxy
pattern for performance reasons. This will come more into play when use the
Authors table in following posts.
Creating the test
As a part of the buildup and teardown of my NHibernate
tests, I will have each of my test create a session object that connects to the
data base before the test starts and then close the session object once the
test finishes.
1 using News.Core.Data;
2 using Microsoft.VisualStudio.TestTools.UnitTesting;
3 using NHibernate;
4
5 namespace News.Web.Tests
6 {
7
public class DatabaseBaseTest
8
{
9
private ISessionFactoryManager sessionFactoryManager = new TestSessionFactoryManager();
10
protected ISession session;
11
12
13
14
[TestInitialize]
15
public void SetUp()
16
{
17
19
session = sessionFactoryManager.GetSessionFactory().OpenSession();
20
}
21
22
[TestCleanup]
23
public void CleanUp()
24
{
25
session.Close();
26
session = null;
27
}
28
}
29 }
Now that I have Session I can create my first
test. This test will simply return 1
record from the NewsItem table by passing the NewsItemId parameter.
78
[TestMethod()]
79
public void GetNewsItemByItemIdShouldReturnMatchingTitle()
80
{
81
var target = new NHibernateDataProvider(session);
82
const string expected = "test";
83
const int itemId = 2;
84
Assert.AreEqual(expected, target.GetNewsItemByItemId(itemId).Title);
85
}
My database has a record which contains the Title value
“test”.
The Repository
The data access code that gets the records looks like
this:
17
public NewsItemDto GetNewsItemByItemId(long newsItemId)
18
{
19
return _session.Get<NewsItemDto>(newsItemId);
20
21
}
In the next post I will add the Authors table to the
mix and some functionality retrieving those joined records.