Configuration options

During code examples so far, we have seen various configuration options or properties such as dialect, driver, connection provider, and so on being set. In this section, we will try to know more about what these options mean, what are the different possible values that these options take, and what do we get out of these options. NHibernate has several such options available. In this section, we are going to look at some of the important and commonly used ones.

Connection release mode

One of the things we learned in the previous chapters about session is that it opens an ADO.NET connection to the database when it needs to connect to database for the first time. But when is this connection closed? That is what connection release mode defines. There are two connection release modes, described as follows:

  • OnClose: This is a legacy of the first version of NHibernate where an ADO.NET connection acquired by a session was closed when the session was closed or disposed
  • AfterTransaction: Connection is released when the NHibernate transaction associated with that connection is finished (either committed or rolled back)

In the coming chapters, we will talk more about NHibernate transactions where it would be clearer for you to understand where exactly the second connection release mode fits in the grand scheme of things. For now, remember that there are two supported connection release modes.

Connection release mode is configured on session factory. There are three configuration options available, two corresponding to the two modes discussed previously and a third and default option of auto that uses AfterTransaction as the connection release mode. It is recommended to use the default setting.

Note

There are two ways that session gets hold of an ADO.NET connection. A connection can be acquired via configured IConnectionProvider. This is transparent to our code and session object handles this internally for us. On the other hand, we can build our own ADO.NET connection and pass it to session object. Connection release mode is only relevant for the former types of connections.

Dialect

Dialect refers to the type of database, for example, SQL Server 2008, Oracle, MySQL, and so on. NHibernate needs to know which type of database your application interacts with. Declaring dialect serves two purposes. One, NHibernate knows which platform dependent features to enable or disable based on underlying database technology. Second, NHibernate is able to generate correct set of SQL statements for a given database technology. Though SQL is a language understood by most databases, there are subtle differences between implementations of SQL by each database. NHibernate offers you one way to interact with database no matter which database you are connecting to. NHibernate is able to do this because it knows how to generate correct SQL based on dialect information you provided during configuration. Every dialect that NHibernate supports is implemented as a class that inherits from NHibenate.Dialect.Dialect. For instance, dialect for SQL Server 2012 is implemented in the class NHibernate.Dialect.Sql2012Dialect.

Dialect is configured as one of the properties by calling the SetProperty method on configuration object as can be seen in the following code snippet:

var config=newConfiguration();
config.SetProperty(Environment.Dialect,
typeof(Sql2012Dialect).AssemblyQualifiedName)

FNH has made dialect declaration slightly easier by aggregating related database technologies under one helper class. This helper class has static properties on it for specific databases. For instance, all MS SQL Server versions are available under helper class FluentNHibernate.Cfg.Db.MsSqlConfiguration. This class has static properties to then choose versions 2000, 2005, 2008 R2, and so on. of MS SQL Server. To declare dialect, you can use method Database() that is available on the FluentConfiguration class. Following is how you can configure MS SQL Server 2012 database:

Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2012
.ConnectionString(c=> c.FromConnectionStringWithKey("EmployeeBenefits")))

For every supported database, NHibernate has a dialect defined. Following table lists the major supported dialects along with information about which database the dialect refers to, and any other important information you should know of:

Dialect class

FNH equivalent

Database technology

DB2Dialect

DB2Configuration.Standard

DB2

FirebirdDialect

FirebirdConfiguration

Firebird

InformixDialect

 

Informix database. Works only with OdbcDriver.

Ingres9Dialect

IngressConfiguration.Standard

(Only supportsIngressDialect)

Ingres SQL. IngresDialect is available for older versions.

MsSql2012Dialect

MsSqlConfiguration.MsSql7

MsSqlConfiguration.MsSql2000

MsSqlConfiguration.MsSql2005

MsSqlConfiguration.MsSql2008

MsSqlConfiguration.MsSql2012

MS SQL Server 2012. Following dialects are available for other versions – MsSql2008Dialect, MsSql2005Dialect, MsSql2000Dialect, MsSql7Dialect.

MsSqlCeDialect

MsSqlCeConfiguration.Standard

MsSqlCeConfiguration.MsSqlCe40

SQL CE.

MySQLDialect

MySQLConfiguration.Standard

MySQL

Oracle10gDialect

OracleConfiguration.Oracle8

OracleConfiguration.Oracle9

OracleConfiguration.Oracle10

Oracle 10g. Following dialects are available for other versions – Oracle9iDialect, Oracle8iDialect.

PostgreSQLDialect

PostgreSQLConfiguration.Standard

PostgreSQLConfiguration.PostgreSQL81

PostgreSQLConfiguration.PostgreSQL82

PostgresSQL.

Other available dialects are - PostgreSQL81Dialect, PostgreSQL82Dialect.

SQLiteDialect

SQLiteConfiguration.Standard

SQLiteConfiguration.InMemory

SQLiteConfiguration.UsingFile

SQLiteConfiguration.UsingFileWithPassword

SQLite.

SybaseSQLAnywhere12Dialect

 

Sybase SQL Anywhere 12. Following dialects are available for other versions - SybaseSQLAnywhere11Dialect,

SybaseSQLAnywhere10Dialect,

SybaseASE15Dialect, SybaseASA9Dialect.

MsSqlAzure2008Dialect

 

SQL Azure.

OracleLite

 

Oracle Lite.

Driver

Every database technology is different and so are the libraries that allow you to connect to and interact with databases. A database driver is client library that lets you connect to database, send SQL commands, and receive results of executing the SQL commands on database server. While most databases have one preferred driver library available, there are cases where a particular database technology has more than one driver library in use. Moreover, some database technologies support more than one protocol of interacting with database server, for instance, SQL Server supports a native protocol along with OLE DB and ODBC protocols. So, you need to tell NHibernate which database driver you want to use in order to connect to database. Similar to dialect, NHibernate implements a class for each driver that it supports. A class implementing a driver support inherits from NHibernate.Driver.DriverBase.

Driver configuration is exactly the same as dialect configuration. SQL Server 2012 uses SQL client driver whose implementation is in class SqlClientDriver. Following is how the driver configuration will be done:

varconfig=newNHibernate.Cfg.Configuration()
.SetProperty(Environment.ConnectionDriver,
        typeof(SqlClientDriver).AssemblyQualifiedName)

If dialect is known then it is easier to make an assumption about the default driver that can be used. NHibernate does exactly that. For every supported dialect, NHibernate sets the default driver so that you do not have to worry about setting it up yourself. You only use the preceding code when you know that you want to use a specific driver. In case of any MS SQL Server version, NHibernate uses SqlClientDriver. Finding out default driver that FNH assumes for different database technologies is something I will leave to curious readers to explore.

Mappings

We covered mappings in Chapter 3, Let's tell NHibernate about our database, and we know why mappings exist. Mappings would be of no use if NHibernate does not know about mappings that exist in our application. Hence, mappings are an important configuration option. As with other options, you can configure mappings through XML, programmatically, or fluently. But even within each method, you have got multiple ways of configuring mappings.

If you remember from Chapter 3, Let's Tell NHibernate About Our Database, there are three types of mappings, namely XML mappings, mappings by code, and fluent mappings. XML configuration can only map XML mappings. Programmatic configuration is able to map both mapping by code and XML mapping. Fluent configuration is capable of mapping all three types of mappings. Moreover, there are multiple ways of configuring a particular mapping configuration. For instance, you can configure individual mapping files/classes one by one or you can ask configuration system to scan an assembly and configure all the mapping files/classes found in that assembly. In this chapter we are only going to look at a representative examples but do explore other options as well.

Programmatic configuration

With programmatic configuration, it is possible to configure both XML mapping files and mapping by code classes. In this section, we will quickly walk through how to do both.

XML mapping

NHibernate supports several different ways of configuring XML mapping files. You can declare the mapping in XML files have NHibernate load XML files directly or you could embed them as resources and load the mapping files from the assembly. You could even load the XML files yourselves into XmlDocument and pass it to NHibernate. We will look at how to configure individual XML files and mapping files embedded resources. To configure individual XML mapping files, you can call the AddFile method available on configuration object, as shown next:

config.AddFile("Mappings/Xml/Community.hbm.xml");

There are several versions of the AddXXX method available on configuration object. You can configure mappings by specifying directory in which XML files can be found, by the FileInfo object pointing to XML mapping file, by stream for XML mapping file, or even by URL on which XML mapping file can be found. It is out of scope for this book to discuss each of these methods so readers are encouraged to explore these on their own.

If you want to add all XML mapping files embedded as a resource into the assembly then you can use the AddAssembly method. In our case, all XML mapping files are in the Persistence assembly so you can call the AddAssembly method as follows:

config.AddAssembly("Persistence");

NHibernate will scan the Persistence assembly to look for embedded resources whose names end with .hbm.xml and load the mappings from those resources.

Mapping by code

Classes containing mappings by code need to be added to an instance of class NHibernate.Mapping.ByCode.ModelMapper before they can be passed to configuration object. ModelMapper compiles the mapping classes into HbmMapping which is a special type that holds the mapping information. Compiled HbmMappings are then added to configuration. Following piece of code shows how to compile mapping classes to HbmMappings and add them to configuration object:

varmodelMapper=newModelMapper();
modelMapper.AddMapping<EmployeeMappings>();
modelMapper.AddMapping<AddressMappings>();
modelMapper.AddMapping<BenefitMappings>();
modelMapper.AddMapping<LeaveMappings>();
modelMapper.AddMapping<SkillsEnhancementAllowanceMappings>();
modelMapper.AddMapping<SeasonTicketLoanMappings>();
modelMapper.AddMapping<CommunityMappings>();
config.AddMapping(modelMapper.CompileMappingForAllExplicitlyAddedEntities());

If you want to add all mapping classes from an assembly then you will need to use the AddMappings method on the ModelMapper class, as shown in the following code snippet:

var modelMapper=new ModelMapper();
modelMapper.AddMappings(typeof(EmployeeMappings).Assembly.GetTypes());
config.AddMapping(modelMapper.CompileMappingForAllExplicitlyAddedEntities());

Note the use of the EmployeeMappings class to get to the assembly containing all the mappings. Call to GetTypes() on Assembly would return all publically defined types inside that assembly. ModelMapper would then internally filter anything that is a "mapping by code" class (a class that inherits from ClassMapping<T>) and add it to configuration.

Fluent configuration

FNH is capable of configuring hbm mappings along with fluent mappings. But if you are using FNH then it is highly unlikely that you would be using mapping by code. So, we will skip configuration of hbm mappings and jump directly to fluent mappings.

The FluentConfiguration object of FNH has a method named Mappings which is used for configuring mappings. This method accepts Action<MappingConfiguration> as its parameter. MappingConfiguration objects offers methods that can be used to configure individual fluent mapping classes or scan an assembly to add all fluent mappings from the assembly. In the following code snippet, all fluent mappings from our Persistence assembly are added individually:

var config = Fluently.Configure()
.Mappings(mapper=>
{
  mapper.FluentMappings.Add<EmployeeMappings>();
  mapper.FluentMappings.Add<CommunityMappings>();
  mapper.FluentMappings.Add<BenefitMappings>();
  mapper.FluentMappings.Add<LeaveMappings>();
  mapper.FluentMappings.Add<SkillsEnhancementAllowanceMappings>();
  mapper.FluentMappings.Add<SeasonTicketLoanMappings>();
  mapper.FluentMappings.Add<AddressMappings>();
})

Call to Fluently.Configure() gives us an instance of the FluentConfiguration object. On this object, we have called the Mapping method to which a parameter named mapper of type MappingConfiguration is passed. MappingConfiguration has a property FluentMappings to which we can add classes containing fluent mappings. For Hbm mappings, this class has another property named HbmMappings to which you can add all your mappings by code.

If you want to add all mapping classes from an assembly, then the FluentMappings property has another method named AddFromAssemblyOf<T> where T is type of any one fluent mapping class.

var config = Fluently.Configure()
.Mappings(mapper=>
{
  mapper.FluentMappings.AddFromAssemblyOf<EmployeeMappings>();
});

Connection string

Think of the connection string as address of your database instance. NHibernate needs to know connection string in order to connect to the database successfully. Connection strings are written in different formats for different database technologies but in a nutshell, it is just a string. Similar to dialect and driver, connection string is configured as a property. We have already looked at how connection string is configured so I will try to keep myself brief here by not repeating code examples.

If you have declared connection string in application configuration file of your application under the connectionStrings node, then you can also use the name of the connection string to configure NHibernate programmatically, as shown next:

var config = Fluently.Configure()
.Database(MsSqlConfiguration
.MsSql2012
.ConnectionString(c => c.FromConnectionStringWithKey("EmployeeBenefits")));

If you are not sure how to write connection string for your database, then check out www.connectionstrings.com. This website has collection of all possible connection string formats for all databases that exist.

Caching

NHibernate can cache the entities it loaded from database into memory or in a second level cache, such as ASP.NET cache. There are three types of caches available–first level cache, second level cache, and query cache.

First level cache is held within session object and managed by session for you. This is turned on by default and you cannot turn it off. All entities that a session loads can be kept in cache till the session is open. If application requests the same entity again, NHibernate returns it from first level cache.

Second level cache has a wider scope than one session. Entities added to second level cache can be accessed by any session. Use of second level cache needs a thorough thinking and design because of the complex nature of this caching strategy. Though caching enhances performance, use of second level cache should be considered a last resort after you have investigated every other area of your data access layer to improve performance. Moreover, it is only master or lookup data that should be cached because it changes less frequently.

If your application runs a query with same inputs multiple times then you can turn on query caching for that particular query. For the first time, results for the query will be fetched from database but subsequent executions of that query would return from cache. If any entity in the result set of that query changes, then that result set in invalidated and next time the query hits the database.

There is more to caching than this. NHibernate lets you declare different read/write strategies for entities that can be cached. This complicates the system at runtime but knowledge of this is very useful when you are trying to optimize the performance of data access layer using caching. We would cover caching in more detail in Chapter 11, A Whirlwind Tour of Other NHibernate Features where some of the occasionally used features are explained.

Session context

Every time you need to interact with a database, you need to get hold of a session object. Technically speaking, you can use a single session object for all your database interactions. However, that would lead to subtle defects since multiple threads would be accessing same session object which is not built to be thread safe. You might also have potential data inconsistency issues. Such usage of session is strongly discouraged. On the other hand, you can open a new session every time you need to interact with database. However, this would be too chatty and would have negative impact on performance. Moreover, you would not be able to use database transaction properly to maintain the integrity of data. What you need is something in the middle of these two options. NHibernate offers session context which can be used for precise control over when new sessions are opened and existing ones are closed. Depending on the runtime characteristics of your application, you can choose the right session context implementation. Following session contexts are available:

  • ThreadLocalSessionContext
  • CallSessionContext
  • ThreadStaticSessionContext
  • WcfOperationSessionContext
  • WebSessionContext

We are not going to cover these in detail right now. In one of the upcoming chapters, we will see how the preceding session context implementations manage session for you and which one to use in which situation.

Batch size

As we will see in Chapter 5, Let's Store Some Data into the Database, NHibernate sends SQL statements to database server in batches to reduce the number of roundtrips to database. NHibernate will use its own heuristics to determine the right batch size. You can override that setting if needed, by setting the batch_size configuration option. Following is how batch size can be set using loquacious configuration:

var config = new Configuration();
config.DataBaseIntegration(db =>
{
  db.BatchSize = 20;
})

Command timeout

Command timeout setting is used to define time after which a SQL command execution should time out. Execution of a SQL command blocks an application thread. If SQL command execution takes a long time then the application thread keeps blocked thus starving other requests. This results in long wait queue of requests to be served leading to performance issues. An optimal value of command timeout helps alleviate such issues. Following code snippet shows how to override default command timeout setting using loquacious configuration:

var config = new Configuration();
config.DataBaseIntegration(db =>
{
  db.Timeout = 5;
})

Note that the timeout value is in seconds.

Show SQL

If you want to log to console those SQL statements that NHibernate sends to database, you can turn on the show_sql setting by setting its value to true. We have used this setting in Chapter 3, Let's Tell NHibernate About Our Database. I usually leave it on in the configuration used for unit tests. This makes it easy to investigate failing tests. While show_sql is helpful in seeing what SQL is actually generated behind the scenes, the formatting of the SQL is not great. If you want properly formatted SQL then another configuration option named format_sql can be used in conjunction. format_sql takes Boolean value and when set to true would format the generated SQL by inserting tab spaces and new lines to make it more readable. Following code snippet shows how to set both these properties using loquacious configuration:

var config = new Configuration();
config.DataBaseIntegration(db =>
{
  db.LogSqlInConsole = true;
  db.LogFormattedSql = true;
})

Tip

These options are simple mechanisms to see the SQL generated under the hood. If you want more detailed information about what is happening within NHibernate, then you could configure log4net which is a famous logging library for .NET. NHibernate has very good support for log4net and you can find an article describing configuration of log4net in the following How to article from official NHibernate website at http://nhibernate.info/doc/howto/various/configure-log4net-for-use-with-nhibernate.html.

We have covered most important and most commonly used configuration options. There are some more options available that can be useful in rare situations. Feel free to explore those on your own. Before we close this section, let's have a look at both programmatic and fluent configuration with all the previous options being configured.

Following is how programmatic configuration would look when all the previously discussed configuration options are specified. You should be familiar with most of this code by now:

var config = new NHibernate.Cfg.Configuration()
         .SetProperty(Environment.ReleaseConnections, "on_close")
         .SetProperty(Environment.Dialect, typeof(SQLiteDialect).AssemblyQualifiedName)
         .SetProperty(Environment.ConnectionDriver, typeof(SQLite20Driver).AssemblyQualifiedName)
         .SetProperty(Environment.ConnectionString, "data source=:memory:")
         .SetProperty(Environment.ShowSql, "true")
         .SetProperty(Environment.UseQueryCache, "true")
         .SetProperty(Environment.CurrentSessionContextClass, typeof(ThreadLocalSessionContext).AssemblyQualifiedName)
         .SetProperty(Environment.BatchSize, "20")
         .SetProperty(Environment.CommandTimeout,"30");
var modelMapper = new ModelMapper();
modelMapper.AddMappings(typeof(EmployeeMappings).Assembly.GetTypes());
config.AddMapping(modelMapper.CompileMappingForAllExplicitlyAddedEntities());
var sessionFactory = config.BuildSessionFactory();
session = sessionFactory.OpenSession();
new SchemaExport(config).Execute(true,true,false,session.Connection,Console.Out);

And following is how the same configuration would look when done fluently:

var config = Fluently.Configure()
             .Database(MsSqlConfiguration.MsSql2012
                      .ConnectionString(c=> c.FromConnectionStringWithKey("EmployeeBenefits"))
                      .ShowSql()
             .AdoNetBatchSize(50))
             .CurrentSessionContext<ThreadLocalSessionContext>()
      .Mappings(mapper=>
                 {
                   mapper.FluentMappings.AddFromAssemblyOf<EmployeeMappings>();
                 })
             .Cache(cacheBuilder=>
                {
                  cacheBuilder.UseQueryCache();
                  cacheBuilder.UseSecondLevelCache();
                  cacheBuilder.ProviderClass<HashtableCacheProvider>();
                })
             .ExposeConfiguration(cfg=>
               {
                 configuration = cfg;
                 cfg.SetProperty(Environment.CommandTimeout, "30");
               });
var sessionFactory = config.BuildSessionFactory();
session = sessionFactory.OpenSession();
new SchemaExport(configuration)
         .Execute(true,true,false,session.Connection,Console.Out);

Note that I have configured to use HashTableCacheProvider as cache provider. This is a default in-memory cache provider that NHibernate offers out of the box. This is not recommended for production use or for situations where you are putting huge amount of data in cache. The most common use of this cache provider is during unit tests. Also note, the use of ThreadLocalSessionContext a session context provider. This is again provided as an example but right session context must be chosen based on runtime characteristics of your application, for example, Web versus non-web, WCF, and so on.

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

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