The Salty Economist

Things I Should Have Learned in High School
posts - 56, comments - 0, trackbacks - 0

F# - Fluent NHibernate RTM

Well, I finally got the courage to download the RTM version of Fluent NHibernate.

I wrote about using Fluent NHibernate in an earlier post (June 28, 2009), so a lot of this is repetitive and some is new.

I have highlighted in yellow the important stuff.

First, I create a simple class

------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace StockPrices {

    public class COMPANY {

        public virtual int Id { get; set; }
        public virtual string COMPANY_NAME { get; set; }
        public virtual string COMPANY_TICKER { get; set; }
        public virtual int ASSET_TYPE_ID { get; set; }
        public virtual int IS_ACTIVE { get; set; }

    }

}
------------------------------------------------------------------

The COMPANY class has only five properties: Id, COMPANY_NAME and COMPANY_TICKER, ASSET_TYPE_ID and IS_ACTIVE

I next create a SQL Server table that is iso-morphic to my class.

CREATE TABLE [COMPANY] (
 [Id] [int] IDENTITY (1, 1) NOT NULL ,
 [COMPANY_NAME] [varchar] (100) NOT NULL ,
 [COMPANY_TICKER] [varchar] (50) NOT NULL ,
 [ASSET_TYPE_ID] [int] NOT NULL ,
 [IS_ACTIVE] [int] NOT NULL ,
 CONSTRAINT [PK_COMPANY] PRIMARY KEY  CLUSTERED
 (
  [Id]
 )  ON [PRIMARY]
) ON [PRIMARY]
GO

OK, now I can start to work in F#

I now a new project.

I first add a reference to my StockPrices class and also an open statement:

#light
open System
open System.Collections.Generic
open System.IO
open StockPrices

I now need to add references to FluentNHibernate and NHibernate (I do believe that you need both)

I then add two open statements into my code for FluentNHibernate

------------------------------------------------------------------
open FluentNHibernate

//Old
//open FluentNHibernate.AutoMap

//New RTM
open FluentNHibernate.Automapping

------------------------------------------------------------------

Note the change in the namespace "AutoMap" has been changed to "AutoMapping".

The next thing I do is declare a dictionary object to hold a set of configuration properties.  I then add a set of attributes.

let properties = new Dictionary<string, string>()

properties.Add("connection.provider", "NHibernate.Connection.DriverConnectionProvider")
properties.Add("dialect", "NHibernate.Dialect.MsSql2000Dialect")
properties.Add("connection.driver_class", "NHibernate.Driver.SqlClientDriver")
properties.Add("show_sql", "true")

let connString = "server='BIG_ROCK\LOGGERSEDGE';Initial Catalog=SMDATA;User ID=sa;Password=XXXXXX"
properties.Add("connection.connection_string", connString)

Not much to say here, other than I am using SQL Server 2000, so I have to tell nHibernate to use the MsSql2000Dialect.  I set my connection string and add it to my set of properties.

In the RTM version, I also had to add the following property:

properties.Add("proxyfactory.factory_class", "NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle")

I did not need this before, but you need it now.  Otherwise you get this big ugly:

The ProxyFactoryFactory was not configured.
Initialize 'proxyfactory.factory_class' property of the session-factory configuration section with one of the available NHibernate.ByteCode providers.
Example:
<property name='proxyfactory.factory_class'>NHibernate.ByteCode.LinFu.ProxyFactoryFactory, NHibernate.ByteCode.LinFu</property>
Example:
<property name='proxyfactory.factory_class'>NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle</property>

To be honest, I have not explored the guts of NHibernate to figure this all out.  I just know I could not get my older version to run without the proxyfactory.factory_class property.

Next, come the autoMappings.  This has also changed.

//-> The Old
//let autoMappings = (AutoPersistenceModel.MapEntitiesFromAssemblyOf<StockPrices.COMPANY>()).Where(fun t -> (t.Namespace = "StockPrices"))

//-> The New
let autoMappings = (FluentNHibernate.Automapping.AutoMap.AssemblyOf<StockPrices.COMPANY>()).Where(fun t -> (t.Namespace="StockPrices"))

Not too much difference here -- basically the method "MapEntitiesFromAssemblyOf" has been replaced with "AutoMap.AssemblyOf".  It just took a while to figure it all out.

The point of this statement is that I want FluentNHibernate to map my Company class object and my database table automatically, obviating the need for a mapping interface xml file.

This part:

let autoMappings = FluentNHibernate.Automapping.AutoMap.AssemblyOf<StockPrices.COMPANY>()

says map my object StockPrices.COMPANY to the database.  I then need to add a where clause that tells FluentNHibernate  what namespace to use.  You should check out the FluentNHiernate site to get a more technical understanding of what is going on in the background.

I also discovered I need to wrap this:

FluentNHibernate.Automapping.AutoMap.AssemblyOf<StockPrices.COMPANY>()

in parentheses like this:

(FluentNHibernate.Automapping.AutoMap.AssemblyOf<StockPrices.COMPANY>())

in order to get intellisense to work.  Also, it generated this convoluted error:

"Successive arguments should be separated by spaces or tupled, and arguments involving function or method applications should be parenthesized"

It took me a while to figure out what this meant.  I guess I must be stupid.  Anyway, the where clause:

.Where(fun t -> t.Namespace = "StockPrices")

is an anonymous or lambda function.  The function returns true if the namespace of an entity equals the name "StockPrices" where my COMPANY class resides.

F# provides a way to define a nameless function using the keyword fun. This type of function receives just one input value and returns just one output value.  Generally, if a function is to be passed as an argument to another function (as in the case here), then often you don’t need to give it a name of its own. These functions are referred to as anonymous functions and sometimes called lambda functions or even just lambdas.

The guts of the function above:

t.Namespace = "StockPrices"

 could have just as easily have been written:

if t.Namespace = "StockPrices" then true else false

but that would not have been as cool.

My next line:

let aConfig = (new NHibernate.Cfg.Configuration()).AddProperties(properties).AddAutoMappings(autoMappings)

does quite a bit of work: It

(1)  Instantiates an NHibernate Configuration;
(2)  Add my dictionary lit of properties to the Configuration; and most importantly
(3)  Adds my autoMappings

Again, according to Gregory, AddAutoMappings substitutes for AddAssembly (used in regular NHibernate). This allows us to stop NHibernate from looking for hbm.xml files, and use our auto mapped entities instead.

The next block of code opens a NHibernate session and looks up AT&T in my database.  It then prints the company name and ticker to the console, giving us the gratification of knowing that something works. 

let sessionFactory = aConfig.BuildSessionFactory()

let aSession = sessionFactory.OpenSession()

aSession.BeginTransaction()

//AT&T is Id 100
let coID = 100

let someObj = aSession.Load(typeof<StockPrices.COMPANY>, coID) :?> StockPrices.COMPANY

printfn "Company Name: %s,  Ticker: %s" someObj.COMPANY_NAME someObj.COMPANY_TICKER

aSession.Close()

let userresp = Console.ReadLine()

The one weird piece of code is this:

let someObj = aSession.Load(typeof<StockPrices.COMPANY>, coID) :?> StockPrices.COMPANY

should return a type of  StockPrices.COMPANY, but it does not. -- it returns a generic Object.  I have not figured out why.  It certainly does in VB.

So, I have to cast a generic Object into StockPrices.COMPANY so I could actually use it.  In F#,  there is an operator ":?>"  known as on downcast operator the uses the syntax:

x :?> T

According to our buddies at MSFT:

"The :?> operator performs a dynamic cast, which means that the success of the cast is determined at run time. A cast that uses the :?> operator is not checked at compile time; but at run time, an attempt is made to cast to the specified type. If the object is compatible with the target type, the cast succeeds. If the object is not compatible with the target type, the runtime raises an InvalidCastException."

Anyhow, this operator allows me to cast the result into a type StockPrices.COMPANY.

I then print the output to the console.  Note:  I also get my sql query generated by NHibernate.  This is the result of setting the show_sql property to true: properties.Add("show_sql", "true").

Next Task:  Use an F# Class instead of a C# Class

Here is the full F# code:

-----------------------------------------------------------------

#light
open System
open System.Collections.Generic
open System.IO
open StockPrices

open FluentNHibernate.Automapping
open FluentNHibernate

let properties = new Dictionary<string, string>()

properties.Add("connection.provider", "NHibernate.Connection.DriverConnectionProvider")
properties.Add("dialect", "NHibernate.Dialect.MsSql2000Dialect")
properties.Add("connection.driver_class", "NHibernate.Driver.SqlClientDriver")
properties.Add("show_sql", "true")

properties.Add("proxyfactory.factory_class", "NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle")

let connString = "server='BIG_ROCK\LOGGERSEDGE';Initial Catalog=SMDATA;User ID=sa;Password=XXXXXX"
properties.Add("connection.connection_string", connString)

//-> The Old
//let autoMappings = (AutoPersistenceModel.MapEntitiesFromAssemblyOf<StockPrices.COMPANY>()).Where(fun t -> (t.Namespace = "StockPrices"))

//-> The New
let autoMappings = (FluentNHibernate.Automapping.AutoMap.AssemblyOf<StockPrices.COMPANY>()).Where(fun t -> (t.Namespace="StockPrices"))

let aConfig = (new NHibernate.Cfg.Configuration()).AddProperties(properties).AddAutoMappings(autoMappings)

let sessionFactory = aConfig.BuildSessionFactory()

let aSession = sessionFactory.OpenSession()

aSession.BeginTransaction()

//AT&T is Id 100
let coID = 100

let someObj = aSession.Load(typeof<StockPrices.COMPANY>, coID) :?> StockPrices.COMPANY

printfn "Company Name: %s,  Ticker: %s" someObj.COMPANY_NAME someObj.COMPANY_TICKER

aSession.Close()

let userresp = Console.ReadLine()


-----------------------------------------------------------------

 

Print | posted on Tuesday, September 22, 2009 1:34 PM |

Powered by:
Powered By Subtext Powered By ASP.NET