Ok. so now we a program that can retrieve stock prices and print them out. But, what I really want to do is save them to a database. So, let's see how to make F# work with NHibernate. Let's start with something super simple.
First, I create a simple class (in VB 'cause I know it better)
-------------------------------------------------------------------------
Option Explicit On
Namespace StockPrices
Public Class COMPANY
Private _ID As Integer
Private _CompanyName As String
Private _CompanyTicker As String
Public Overridable Property Id() As Integer
Get
Id = _ID
End Get
Set(ByVal value As Integer)
_ID = value
End Set
End Property
Public Overridable Property COMPANY_NAME() As String
Get
COMPANY_NAME = _CompanyName
End Get
Set(ByVal value As String)
_CompanyName = value
End Set
End Property
Public Overridable Property COMPANY_TICKER() As String
Get
COMPANY_TICKER = _CompanyTicker
End Get
Set(ByVal value As String)
_CompanyTicker = value
End Set
End Property
End Class
End Namespace
-------------------------------------------------------------------------
The class has only three properties: Id, COMPANY_NAME and COMPANY_TICKER.
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 ,
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.AutoMap
open FluentNHibernate
Now begins the work.
The first 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.
My next statement took forever to figure out:
let autoMappings = (AutoPersistenceModel.MapEntitiesFromAssemblyOf<StockPrices.COMPANY>()).Where(fun t -> (t.Namespace = "StockPrices.StockPrices"))
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 = AutoPersistenceModel.MapEntitiesFromAssemblyOf<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.
My first hang up:
I discovered I need to wrap this:
AutoPersistenceModel.MapEntitiesFromAssemblyOf<StockPrices.COMPANY>()
in parentheses like this:
(AutoPersistenceModel.MapEntitiesFromAssemblyOf<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.StockPrices")
is an anonymous or lambda function. The function returns true if the Namespace of an Entity equals the fully qualified name "StockPrices.StockPrices" where my class resides. )Note: I use the name StockPrices for both my assembly name and my Namespace -- you need to use both.)
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.StockPrices"
could have just as easily have been written:
if t.Namespace = "StockPrices.StockPrices" then true else false
but that would not have been as cool.
According the the FleuntNHibernate authority, James Gregory:
"...so I’ll introduce one final method: Where(Func<Type, bool>).
The Where method takes a lambda expression which is used to limit types based on your own criteria. The most common usage is limiting based on a namespace, but you could also look at the type name, or anything else exposed on the Type object."
Note that "Namespace" is a property of the Type object.
This is all a very long winded explanation as to why you need the Where clause in order for FluentNHibernate to just map those entities that you want mapped. Also, my app crashed and burned without it.
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.
The first part:
let someObj = aSession.Load(typeof<StockPrices.COMPANY>, coID)
Anyway, I had to find a way to cast a generic Object into StockPrices.COMPANY so I could actually use it. Browsing around the Internet I found this little puppy: ":?>" What the devil is this?
It is know 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").