Chapter 23. Data access with NHibernate

This chapter covers

  • Decoupling data access from the core and UI
  • Configuring NHibernate mappings
  • Bootstrapping NHibernate
  • Invoking data access from ASP.NET MVC

Even though the ASP.NET MVC Framework is focused on the presentation layer, many developers work on small applications that don’t need several layers of business logic and separation between the presentation layer and the data store. For these small applications, simple separation patterns may be appropriate, but many small applications grow much larger than originally anticipated. When this happens, separation of concerns is critical to the long-term maintainability of the software.

To achieve separation of concerns when communicating with a relational database, we can use an object-relational mapping tool such as the popular open source NHibernate project. NHibernate makes data access with relational databases trivial. As with anything new, a learning curve is associated with understanding the method of configuring the mapping between objects and tables. This chapter demonstrates how to configure and leverage NHibernate when developing an application whose UI takes advantage of the ASP.NET MVC Framework. This example is equally applicable in ASP.NET MVC 1 and 2.

23.1. Functional overview of reference implementation

The example we’ll explore in this chapter builds on the ASP.NET MVC 2 default project template that we get when creating a new project through Visual Studio. The functionality that’s added is the capability for each page to track visitors to the site. The site tracks the following pieces of data:

  • URL
  • Login name
  • Browser
  • Date and time
  • IP address

Figure 23.1 shows that when we run the application the most recent visits are displayed at the bottom of the page. Each page displays its recent visits.

Figure 23.1. Recent visitors are displayed at the bottom of every page.

We’ve intentionally kept the scope of this application small so we can focus on using NHibernate as the data access library that allows us to persist and retrieve Visitor objects. Before we go into each layer of the application, let’s review the architecture of this application at a high level.

23.2. Application architecture overview

At a broad level, this application uses some concepts from domain-driven design (DDD) inside an onion architecture, although most of the DDD concepts would be overkill for such a simple application. At a high level, the application is composed of a domain model at its core. Figure 23.2 shows a reference layout of the onion architecture.

Figure 23.2. The onion architecture uses the concept of an application core that doesn’t depend on external libraries, such as NHibernate.

The solution structure implements the decoupling strategy that the onion architecture requires. In figure 23.3, you can see this structure with the Core project’s references expanded. The application has a simple core, and the libraries referenced to implement the core are straightforward.

Figure 23.3. The Core project has minimal references and no external dependencies.

Notice that there’s no reference to NHibernate.dll from the Core project. It’s important that the core remain portable and not coupled to external libraries that will change over time. As time goes on, the libraries you use will change, as will the versions of the libraries. Keeping the core free from this churn will keep it stable. As with everything in software, this is a trade-off. You may feel comfortable coupling to some libraries, but be sure to evaluate the consequences carefully. This example employs the Inversion of Control (IoC) principle through abstract factories and dependency injection.

 

Inversion of Control is a principle, not a tool

With the popularity of IoC containers, many developers aren’t aware of how to implement IoC without a library like StructureMap. Many developers have experience with dependency injection, but only through the use of an IoC container.

The example in this chapter employs IoC through liberal use of dependency injection via constructor injection. The decoupling mechanism employs the abstract factory pattern with start-up time bootstrapping code to initialize the abstract factories. For more on IoC, refer back to chapter 13, where we cover IoC in more detail.

 

If we expand more of the projects, as in figure 23.4, we can see that no project references the Infrastructure project except for IntegrationTests, which isn’t deployed to production anyway. Only the Infrastructure project references NHibernate.dll. When we examine the UI project, we’ll see how the application is organized at runtime to function properly.

Figure 23.4. No project references Infrastructure. This arrangement is important for decoupling.

 

Note

The example in this chapter isn’t focused on automated testing, so many of the necessary automated tests are omitted for the sake of brevity.

 

Now that we understand how the application is structured at a high level, we’ll explore each layer bit by bit. We’ll begin with the domain model.

23.3. Domain model—the application core

The domain model is the most important part of the application. Without the domain model, all of the pertinent concepts would be represented only in the UI. Our particular domain model contains a single aggregate made up of a single entity, the Visitor. The code for the Visitor class is shown in listing 23.1.

Listing 23.1. The Visitor class, the domain model for this example
using System;

namespace Core
{
public class Visitor
{
public virtual Guid Id { get; set; }
public virtual string PathAndQuerystring { get; set; }
public virtual string LoginName { get; set; }
public virtual string Browser { get; set; }
public virtual DateTime VisitDate { get; set; }
public virtual string IpAddress { get; set; }
}
}

We have no business logic here, and at first glance it looks just like a data structure. All other concerns have been left out in an effort to include only abstractions and logic that are necessary for leveraging NHibernate in a loosely coupled way.

The Visitor class contains properties for all the pieces of information that we want to record. The Id property exists as an identifier for the particular visit. We could certainly use Int32 as the ID, but in a data persistence environment, that forces a dependency on the data store for the generation of a unique Int32 value. Sometimes this is appropriate, but in DDD, the developer errs on the side of giving responsibility to the domain model, not the data store. In line with that, the Id is a Guid, and the application will generate a Guid before attempting to save to the database.

The mechanism for persisting or retrieving a Visitor is called a repository. The repository will save our entity as well as retrieve it. It can also represent filtering operations. In our domain model, we have an IVisitorRepository. This interface is seen in listing 23.2.

Listing 23.2. The repository that defines the persistence operations
namespace Core
{
public interface IVisitorRepository
{
void Save(Visitor visitor);
Visitor[] GetRecentVisitors(int numberOfVisitors);
}
}

With our repository, we’re able to save a Visitor as well as get a specific number of the most recent visitors. In figure 23.4, you see that the Core project doesn’t contain any class that implements IVisitorRepository. This is important because the class that does the work represented by the interface will be responsible for the persistence, which isn’t a domain model concern. Persistence is infrastructure. This functionality would work equally well if we persisted the data to a file instead of the database. The mechanism of persistence isn’t a concern for the domain model, so the class responsible for it isn’t in the Core project.

The concern that’s in the Core project is an abstract factory capable of locating or creating an instance of IVisitorRepository. The VisitorRepositoryFactory is responsible for returning an instance of our repository. Listing 23.3 shows that the knowledge for creating the repository doesn’t reside with the factory. This factory merely represents the capability to return the repository.

Listing 23.3. The factory that provides the repository

To even the inexperienced eye, this class doesn’t seem useful alone. When BuildFactory() is called, an exception will be thrown. Out of the box, the domain model doesn’t know the implementation of IVisitorRepository that will be used, so there’s no way to embed this knowledge into compiled code. The public static RepositoryBuilder property will have to be set to something useful before the factory will work properly. We’ll see how this is accomplished after all the pieces have been introduced.

This explicit factory isn’t necessary if you’re using an IoC container, which has been left out for the sake of simplicity. This domain model is intentionally simple.

The next step is to understand how we configure NHibernate to automatically persist our entity to the database.

23.4. NHibernate configuration—infrastructure of the application

There’s little code to write in order to leverage NHibernate for seamless persistence. NHibernate is a library, not a framework, and the difference is important. Frameworks provide templates of code, and we then fill in the gaps to create something useful. Libraries are usable without providing templates. NHibernate doesn’t require our entities to derive from a specific base class or the implementation of a specific interface. NHibernate can persist any type of object as long as the configuration is correct.

In this section, we’ll walk through the configuration of NHibernate and see how we can save and retrieve the Visitor object. For this chapter, we’re using NHibernate 2.1 with Fluent NHibernate 1.0 for configuration help. Fluent NHibernate provides XML-less, compile-safe, automated, convention-based mappings for NHibernate. You can find it at http://fluentnhibernate.org/.

Before we dive into the configuration, let’s examine the implementation of the IVisitorRepository interface specified in the domain model. We’ll start with this class to demonstrate how little code is written when calling NHibernate to perform a persistence operation. Listing 23.4 shows the VisitorRepository class located in the Infrastructure project.

Listing 23.4. Repository implementation coupled to NHibernate APIs

This class uses the NHibernate API to save Visitor instances as well as retrieve a collection of recent visitors to the site . The GetRecentVisitors method makes use of Hibernate Query Language (HQL) to perform the query against the database.

Now that we see what it looks like to call NHibernate, we’ll walk through the NHibernate configuration process and explore each step. We’ll start with the main configuration.

23.4.1. NHibernate’s configuration

The beginning of the configuration process is the hibernate.cfg.xml file. This file has the same name as the configuration file used by the Hibernate library in Java. Because NHibernate started as a port from Hibernate, this is just one of the many similarities—knowledge of one largely translates directly to the other.

The contents of the hibernate.cfg.xml file can also be put into the Web.config file or app.config file. For simple applications, embedding this information into the .NET configuration file may be adequate; but this example stresses separation, so that when applied to a medium-sized application, the code and configuration don’t run together. We’ve seen Web.config files grow large, and it’s trivial to store the NHibernate configuration in a dedicated file.

Listing 23.5 shows the contents of the hibernate.cfg.xml file.

Listing 23.5. The hibernate.cfg.xml file

This is a simple configuration, and there are many other options discussed in the NHibernate documentation (http://nhforge.org/doc/nh/en/index.html). The most obvious piece of information is the connection string . Also, the driver class and dialect specify the details of the database engine used. This example uses SQL Server 2005, but these values would change if you wanted to use a version of Oracle, SQLite, or the many other database engines supported out of the box.

The show_sql property will output each SQL query to the console as the statement is sent to the database, which is useful for debugging. The adonet.batch_size property controls how many updates, deletes, or inserts will be sent to the database in a single batch. It’s more efficient to send multiple statements in a single network call than to make a separate network call for each statement. NHibernate will do this automatically.

The last configuration item is the proxy factory to use for mappings using lazy loading, which is the default. If we were using XML mapping files, we’d also configure the assembly in which NHibernate could find the embedded mappings, but that’s not necessary here because we’re using code-based mappings with Fluent NHibernate.

23.4.2. The NHibernate mapping—simple but powerful

NHibernate requires at least one mapping. Figure 23.5 shows the Infrastructure project, and in it you’ll see that there’s a code file named VisitorMap.cs.

Figure 23.5. The Infrastructure project contains the NHibernate mapping for Visitor.

We’re about to explore the VisitorMap.cs file, which contains the mapping information for the Visitor class. First, notice the four files that are linked into the project:

  • Hibernate.cfg.xml
  • Log4Net.config
  • Nhibernate-configuration.xsd
  • Nhibernate-mapping.xsd

These files don’t belong to the project directly; they’re linked from elsewhere. We do this because multiple projects need the same copy of these files.

The first example that needs linked files is IntegrationTests—it will contain tests for all data access. To test the data access, the tests need to leverage the same configuration as the application.

We’ve already covered the hibernate.cfg.xml file. The Log4Net.config file contains log4net configuration information that’s broadly applicable to any type of application. If you’re not familiar with Apache log4net, you can find more information at http://logging.apache.org/log4net/index.html. The two XSD files provide the schema for the NHibernate configuration and the NHibernate mapping files. When added to the project, they enable Visual Studio to provide XML IntelliSense when we’re editing these files, which makes the editing process smooth. In larger applications, you’ll have a mix of code-based mappings and XML mappings (which are the most comprehensive and documented and are necessary in some situations). Without this XML IntelliSense, it would be cumbersome to maintain these XML files.

Let’s now turn to the mapping for the Visitor class. The VisitorMap.cs file is shown in listing 23.6. The equivalent XML mapping is included at the end of the listing for reference.

Listing 23.6. The VisitorMap.cs file, which contains mapping for the Visitor class

The first line is pretty standard and specifies the table to use. The Id method is special, and it has to be the first property mapped on an entity. This will become the primary key on the table, and the generator node has many options for defining how this primary key is generated, including SQL Server “identity” and Oracle “sequence” functionality. We want the Visitor object to have a value in the Id property before being persisted, so we’re configuring NHibernate to generate a Guid for us before issuing the INSERT statement to the database. The GuidComb() generator is special; it generates GUIDs in sequential order so that the clustered index on the primary key column has little to do when a new record is inserted into the table. This sequencing sacrifices a bit of uniqueness in the GUID algorithm, but in this context, the only thing that’s important is that the GUID be unique for this particular table.

 

Note

You can read more about the COMB GUID from the inventor, Jimmy Nilsson, in his article “The Cost of GUIDs as Primary Keys”: http://mng.bz/4q49.

 

The rest of the properties are largely self-explanatory. They have names and constraints, and the strings can have a length specified. If you’re all right with the column name being the same as the property name on the class, a column attribute is unnecessary. When you have all the properties mapped, you’re ready to move on. If you have a more complex class structure, you’ll want to review all your mapping options in the NHibernate Reference Documentation (http://nhforge.org/doc/nh/en/index.html) and Fluent NHibernate documentation (http://fluentnhibernate.org/).

23.4.3. Initializing the configuration

There are two main abstractions in NHibernate: ISessionFactory and ISession. A session factory creates a session, and a session is meant to be used for a single task in the application—this can be a single transaction or multiple successful transactions in quick succession. You should use and then quickly dispose of NHibernate sessions. The session factory, in contrast, is intended to be kept for the life of the application so that it can be used to create all sessions.

The ISession interface is the abstraction, but the implementation provided by NHibernate requires some explanation. Listing 23.7 shows how to create the session factory that will be used for the life of the application.

Listing 23.7. A Configuration object that creates a session factory

The session factory is expensive to create, by which we mean that it accesses the filesystem and parses XML from embedded resources inside DLLs. The configuration object reads the hibernate.cfg.xml file (which is an out-of-process call) and then builds the session factory using this configuration. When building the session factory, it will apply all the properties found in the configuration file. If an assembly was included for embedded XML mappings, it will retrieve all those mapping files from within the DLLs (which is another out-of-process call). Each mapping file will be parsed using the XML DOM. Regardless of whether you use code mappings or XML mappings, NHibernate will use reflection on all the types to ensure that every property declared in the mapping exists on the types referenced. If lazy loading is enabled (the default), it will also check that all public properties and methods are marked as virtual. If we prefer not to mark them virtual, we’ll need to disable lazy loading. With most applications, it takes at least a full second (or more) to create the session factory, so this operation isn’t something we want to do often. If we were to create the session factory for every web request, our web application would slow down dramatically. We push the session factory instance in a static variable so we can hold on to it for the life of the application.

The NHibernate session, on the other hand, is cheap. We’ll create and destroy many of these objects. In a stateful application, we’ll use a session for a single transaction or user operation. For a web application, we’ll use one session per web request. We’ll cover the web application usage in just a bit. Note that Fluent NHibernate contains a SessionSource class that can optionally be used to create and manage the ISessionFactory, rather than doing this manually.

The code for the creation of a session looks like this:

ISession session = SessionFactory.OpenSession();

Before we can move on to the code that uses the ISession, we must have a database. We’ve declared our connection string, and with the mapping, NHibernate knows the table structure. We can proceed to create our database schema manually, or we can get NHibernate to help us out. To have NHibernate create our schema, we can create an empty database named NHibernateSample (as declared by the connection string) inside SQL Server Express, and execute the code shown in listing 23.8.

Listing 23.8. NHibernate, which generates a database from mappings
using Infrastructure;
using NHibernate.Tool.hbm2ddl;
using NUnit.Framework;

namespace IntegrationTests
{
[TestFixture]
public class DatabaseTester
{
[Test, Explicit]
public void CreateDatabaseSchmea()
{
var export = new SchemaExport(
DataConfig.BuildConfiguration());
export.Execute(true, true, false);
}
}
}

We’re using an NUnit test fixture as an easy launching point for this code, which makes it trivial to run the code snippet. After running this test inside Visual Studio using the TestDriven.net add-in (http://testdriven.net/), we’ll see the output in the Output window. On our system, the Output window showed the text in listing 23.9.

Listing 23.9. Output from the schema export
------ Test started: Assembly: IntegrationTests.dll ------

if exists (select *
from dbo.sysobjects
where id = object_id(N'Visitors')
and OBJECTPROPERTY(id, N'IsUserTable') = 1)
drop table Visitors
create table Visitors (
Id UNIQUEIDENTIFIER not null,
PathAndQuerystring NVARCHAR(4000) not null,
LoginName NVARCHAR(255) not null,
Browser NVARCHAR(4000) not null,
VisitDate DATETIME not null,
IpAddress NVARCHAR(255) not null,
primary key (Id)
)

1 passed, 0 failed, 0 skipped, took 6.86 seconds.

The NUnit test lives in the IntegrationTests project, which also links in the hibernate.cfg.xml file to leverage the same configuration. Figure 23.6 shows the IntegrationTests project structure. We’ve kept it minimal for the sake of simplicity.

Figure 23.6. The IntegrationTests project contains tests for all the mappings and repositories.

Notice the VisitorRepositoryTester class. It contains the automated testing necessary to ensure that the repository implementation functions as expected. We can’t write unit tests for data access because data access, by its very nature, is an integration test concern. Not only are we integrating a third-party library, NHibernate, but we’re also expecting another process to be running on our network, server, or workstation. SQL Server must be up and running, and it also must contain the correct schema. If anything is wrong along the way, the tests will fail. Because of this arrangement, these integration tests are more complex than tests that don’t require persisted data. Even so, when you write data access tests, keep them as small as possible, and only test the data access.

Listing 23.10 shows the code for the VisitorRepositoryTester.

Listing 23.10. Integration tests

 

These tests are essential to ensure that every query generated by NHibernate is tested and retested with every build. Because configuration changes will change the queries that are generated, tests are important for the stability of the application.

When we run the tests in listing 23.10, we see that they pass, as shown in figure 23.7.

Figure 23.7. When the repository test passes, we know the mapping is correct. The test results are shown in the ReSharper test runner.

All NHibernate API usage should remain in the Infrastructure project. Remember that none of the other projects in the solution have a reference to Infrastructure, so the rest of the code isn’t coupled to this particular data access library. This decoupling is important, because data access methods change frequently. We don’t want to couple our application to infrastructural concerns when they’re likely to change frequently.

We now know the basics of persisting with NHibernate. We’ve covered both the Core and Infrastructure, so let’s see how this ties together in the UI.

23.5. UI is the presentation of the model

Now that the domain model and the NHibernate infrastructure are set up and functioning, we can turn our attention once again to the ASP.NET MVC project. We’ve left the project close to the default project template in an effort to keep it simple, as well as to clearly identify the additions necessary to save every visitor to the site. Figure 23.8 shows the structure of the UI project.

Figure 23.8. The additions to the project are shown in boxes. We’ve added several files to support the capture and display of visitors.

As you’ll recall (from figure 23.1), the bottom of each page on the site shows the most recent visitors to the site. To share this view on each page, we’ve wired up a partial view to the master page, Site.Master. We covered this capability in chapter 3, so we won’t cover it in depth again here.

At the highest level, we’ve added an action filter attribute to each controller. If the site contains many controllers, we’d consider introducing a custom ControllerActionInvoker for all controllers and adding the filter for all controllers. In this example, the project contains only the HomeController, which is shown in listing 23.11. Notice the action filters applied at the class level.

Listing 23.11. Action filters applied to controller to keep concerns separated

We’ve introduced two filters, VisitorAdditionFilter and VisitorRetrievalFilter . We’ve applied the optional Order parameter to ensure that they’re executed in the intended order. The order in which the attributes are applied to the class isn’t guaranteed to be the execution order.

We want to persist a new visitor and then retrieve the list of recent visitors and pass them to a view. Listing 23.12 shows both of the action filters.

Listing 23.12. Action filters interacting with domain model

Each of the filters is simple. Most of the code is just for managing the dependency of the IVisitorRepository and building the repository from the factory . The three lines that are interesting are in the OnResultExecuting method . We build the visitor and save it . Then, we get the recent visitors and push them into view data . The VisitorBuilder class isn’t shown, but it’s a simple one that constructs a Visitor and populates it with information from the HttpRequest.

The next interesting file is the Visitors.ascx partial view, located in /Views/ Shared/Visitors.ascx. Listing 23.13 shows this partial view.

Listing 23.13. Displays recent visitors
<%@ Control Language="C#"
Inherits="System.Web.Mvc.ViewUserControl<Visitor[]>" %>
<%@ Import Namespace="Core"%>
<div style="text-align:left">
<h3>Recent Visitors</h3>
<%foreach (var visitor in ViewData.Model){%>
<%=visitor.VisitDate%> -
<%=visitor.IpAddress%> -
<%=visitor.LoginName%> -
<%=visitor.PathAndQuerystring%><br />
<%=visitor.Browser%><hr />
<%}%>
</div>

This partial is added to the page via the master page. The array of visitors is expected to be in ViewData.Model so that the array can be rendered the default way. At the bottom of the master page, the following code passes just the visitor array to the partial:

<%Html.RenderPartial(Constants.Partials.VISITORS,
ViewData[Constants.ViewData.VISITORS]); %>

We use constants so that the views don’t contain duplicate string literals. Because logging and displaying visitor information are cross-cutting concerns for the application, we’ve taken steps to keep the logic factored out so that it can be shared across all controllers in the application.

Let’s review what we’ve done:

  • Kept the persistence logic behind an interface that doesn’t belong to the UI project
  • Leveraged action filters so that no single controller is responsible for knowing how to interact with IVisitorRepository
  • Created a partial view to own the layout of the recent visitors
  • Delegated to the partial view from the master page so that individual views don’t have to care about rendering visitor information

All the pieces are now in place to be pulled together.

23.6. Pulling it together

If you’ve been keeping a close eye on the code up to this point, you’ll have noticed that we don’t have a default way to create the NHibernate repository instance of IVisitorRepository that lives in the Infrastructure project. Our UI project doesn’t reference the Infrastructure project at all. This section will walk through the process of wiring up these decoupled pieces.

The first piece is in the Web.config file. Inside the httpModules node, we’ve registered an extra module:

<add name="StartupModule"
type="Infrastructure.NHibernateModule, Infrastructure, Version=1.0.0.0,
Culture=neutral"/>

This module kicks off the process of creating the session factory. It also handles the BeginRequest and EndRequest events and creates and destroys NHibernate sessions for each web request.

Listing 23.14 shows the code for NHibernateModule.cs, which lives in the Infrastructure project.

Listing 23.14. NHibernateModule, which kick-starts NHibernate

The DataConfig class (shown earlier in listing 23.7) is responsible for creating ISession instances and storing them in the SessionCache, which is shown in listing 23.15 (along with the relevant method from DataConfig).

Listing 23.15. Session cache that keeps session in HttpContext items

Now that we have a session factory and we have a session, our application can call NHibernate and communicate with the database.

Aside from the NHibernate initialization, we have the initialization of the VisitorRepositoryFactory. Many applications use IoC tools, which provide these factories automatically; but because this example doesn’t leverage an IoC container, we had to provide this startup logic explicitly. There are several ways to do that; for example, we could declare an interface for the factory and keep an implementation around. Use your judgment when choosing a technique. The important thing is that neither the Core project nor the UI project should reference the Infrastructure project or libraries that are purely infrastructural in nature. We’ve kept NHibernate completely off to the side so that the rest of the application doesn’t care how the data access is happening.

There’s one final missing piece required before we can run this application from Visual Studio using Ctrl-F5. The Web.config file refers to a class in the Infrastructure project, but because there’s no reference, the Infrastructure assembly won’t be in the bin folder of the website. We could copy it explicitly every time we compile, but that would get tiresome. The solution is to have Visual Studio copy it every time it’s compiled by adding the lines in listing 23.16 to the Infrastructure.csproj file as a postbuild event.

Listing 23.16. A postbuild event that copies assemblies and config files
xcopy /y  ".*.dll" "......UIin"
xcopy /y ".*.dll" "......IntegrationTestsin$(ConfigurationName)"
xcopy /y ".log4net.config" "......UI"
xcopy /y ".hibernate.cfg.xml" "......UIin"

By setting up the four commands shown in listing 23.16, we’ve configured the Infrastructure project to copy two important configuration files as well as the necessary binaries to the UI project’s bin folder and the test folder. Not only will the Infrastructure assembly be copied, but the NHibernate assemblies will be copied as well. This ensures that when the UI project is run from Visual Studio, we’ll be greeted with a running application that’s saving and showing visitors, as shown in figure 23.9.

Figure 23.9. The application works as expected after being wired together.

Because of this postbuild step, the application has all the required assemblies and configuration files. This reduces the pain of copying these files manually, and it’s just one type of automation required when we truly commit to decoupling our applications.

23.7. Summary

In this chapter, we’ve seen how to structure a solution, configure NHibernate, use the DDD repository pattern, and wire up loosely coupled code at runtime. This chapter presents a vastly simplified example, but the decoupling patterns contained within it are appropriate in medium to large applications as well.

Configuring and using NHibernate is easy. It’s also easy to couple to it and get in trouble. Whether it’s NHibernate or any other data access library, make an explicit architectural decision whether or not to couple to it. Make sure you understand the trade-offs for your decision. Most of the time, we prefer to keep the core clean and the UI separated, with all data access behind abstractions and tested separately. For more advanced usage of NHibernate with ASP.NET MVC, you can download the CodeCampServer open source project from http://codecampserver.org.

Now that we understand all the concepts in ASP.NET MVC as well as how to tie it together into a full application with a database, it’s time to move on to part 4, which will dive into more cross-cutting topics, such as route debugging, customizing Visual Studio, and overall testing practices.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.45.5