Fluent NHibernate is a nice way to be able to use NHibernate without having to deal with all that unchecked XML. This morning I decided to find out how well it works with F#. Things went relatively smooth. I've converted some code samples from the Fluent NHibernate First Project. I suggest having that open to fill in any gaps. I've also included the full project code and DB script at the end of this article.
If you're not familiar at all with Fluent NHibernate, basically it takes advantage of lambda expressions as Expression<T> to provide a somewhat strongly typechecked reflection system. Thus, instead of having attributes with hard-coded strings, or XML files, you have expressions that target the properties of objects you wish to map. The Fluent NHibernate library then takes care of hooking it up to NHibernate, and away you go. Something like that anyways.
Classes
So, first, we define the "entities" like the C# project does. Here's the first little pain. F# doesn't really support C#'s idea of "automatic properties". You can have vals on a class, which act like fields (although, they are implemented as properties). Or, you can manually specify them, like you would in earlier versions of C# which didn't have the auto-gen-a-field-for-me.
F# doesn't encourage uninitialized values. If you have uninitialized fields (vals), you need to mark them with an attribute to say you know what you're doing. So, that adds a bit more code overhead. What I do here is to alias the DefaultValueAttribute to "DV". So the first bit of our classes looks like this:
#light
namespace FHib.Entities
open System.Collections.Generic
type DV = DefaultValueAttribute
type Employee() as this =
[<DV>] val mutable Id : int
[<DV>] val mutable FirstName : string
[<DV>] val mutable LastName : string
Now, NHibernate relies on having virtual properties so that it can dynamically create code to do nifty things like lazy loading. In F#, creating a virtual member means defining an abstract member and providing an implementation, in the same class. Since we don't have automatic properties, this means we'll have to define the backing field ourselfs. In all, the full code for the virtual property "Store" (virtual so NHibernate can lazy-load it) is:
abstract Store : Store with get,set
[<DV(false)>] val mutable _store : Store
override x.Store with get() = x._store and set(v) = x._store <- v
Not the pinnacle of short code, but not horrible all things considered. The rest of the entity mappings are rather straightforward so I'll skip them here. I stuck with vals for anything that didn't have to be virtual, to keep it more concise.
Mappings: Easy Start
OK, so now to the "real" work. Fluent NHiberate looks for classes that subclass ClassMap<T>. It then creates an instance of them, which allows you to call the mapping methods in the object's constructor. From the First Project:
public class EmployeeMap : ClassMap<Employee> { public EmployeeMap() { Id(x => x.Id); } }
OK, so how do we convert this to F#? It's easy... except for that lambda. The lambda in this case compiles to an Expression<Func<Employee, object>>. F#'s compiler does not support Expression<T>. So, we turn to experimental support. Referencing the FSharp.PowerPack.Linq assembly gives us the Microsoft.FSharp.Linq.QuotationEvaluation module. This extends the F# quotation type, Expr, with "ToLinqExpression", which returns an untyped LINQ Expression object.
To get this untyped Expression (LINQ) out of an Expr (F#) and into an Expression<T> (F#) suitable for Fluent NHibernate's consumption, I started off writing this tiny helper module:
module LinqHelper =
open Microsoft.FSharp.Quotations
open Microsoft.FSharp.Linq.QuotationEvaluation
open System.Linq.Expressions
let ToLinq (exp : Expr<'a -> 'b>) =
let linq = exp.ToLinqExpression()
let call = linq :?> MethodCallExpression
let lambda = call.Arguments.[0] :?> LambdaExpression
Expression.Lambda<Func<'a, 'b>>(lambda.Body, lambda.Parameters)
When an F# quotation of a lambda is turned into a LINQ expression, the root node is a useless MethodCallExpression. So, we unwrap that by taking it's argument and using it to generate a typed lambda Expression<T>.
This is a good start. But many of the Expressions that Fluent NHibernate looks for have a return type of object. Instead of having to write "box" or "upcast" all over, I added another function called "ToLinqObj". This takes an Expr<'a -> 'b>, but returns an Expression<Func<'a, obj>>.
Finally, writing "ToLinq" and "ToLinqObj" seemed too verbose, so I added some operators to the LinqHelper module:
let (~@) expr = ToLinq expr
let (~@@) expr = ToLinqObj expr
Now we can start mapping:
open LinqHelper
type EmployeeMap() = inherit ClassMap<Employee>() do
base.Not.LazyLoad()
base.Id ~@@ <@ fun x -> x.Id @> |> ignore
base.Map ~@@ <@ fun x -> x.FirstName @> |> ignore
base.Map ~@@ <@ fun x -> x.LastName @> |> ignore
(base.References ~@ <@ fun x -> x.Store @>).LazyLoad() |> ignore
We start off by disabling LazyLoad because most of the properties are not virtual, and NHibernate will fail to validate the mapping. Instead, we explicitly LazyLoad things, like the Store reference.
Mappings: Modifying the Expressions
Unfortunately, things didn't stay so simple. The Fluent NHibernate methods "HasMany" and "HasManyToMany", for instance, don't work with F#'s type inference. This is because they have several overloads, a couple of which take expressions. If we try this in the StoreMap:
base.HasMany ~@@ <@ fun x -> x.Staff @> |> ignore
We get an error because F# doesn't know what x is. (error FS0055: This lookup uses a deprecated feature, where a class type is inferred from the use of a class field label. Consider using a type annotation to make it clear which class the field comes from.) Using ~@ to keep it strongly typed fails as well; it can't figure out the overload. This is nothing surprising -- overloading is the enemy of type inference.
So, what do we do? Type annotations are not what I consider fun. So instead, we'll add non-overloaded ClassMap<'t> type extensions into the LinqHelper module:
type ClassMap<'t> with
member x.HasManyX expr = (x.HasMany : Expression<Func<'t, seq<_>>> -> _) (ToLinq expr)
member x.HasManyToManyX expr = (x.HasManyToMany : Expression<Func<'t, seq<_>>> -> _) (ToLinq expr)
By providing these extensions that are explicit once, we enable type inference for the rest of the time. We can now finish the StoreMap:
type StoreMap() = inherit ClassMap<Store>() do
base.Map ~@@ <@ fun x -> x.Name @> |> ignore
(base.HasManyX <@ fun x -> upcast x.Staff @>)
.LazyLoad()
.Inverse().Cascade.All() |> ignore
(base.HasManyToManyX <@ fun x -> upcast x.Products @>)
.Cascade.All()
.WithTableName("StoreProduct") |> ignore
The only type "annotation" we need here is the upcast for the HasManyX. This is because F# forces you to be explicit. Accessing "Staff" in the first quotation means type IList<Employee>, not seq<Employee>. The upcast will sort this out for us.
This compiles fine. But remember how I said using expressions was a "somewhat strongly typed" way of doing things? The expression trees are interpreted at runtime, which allows failures a more complex type system might prevent. This is one of the times. If we try to execute it as-is, we get this exception:
---> FluentNHibernate.Cfg.FluentConfigurationException: An invalid or incomplete configuration was used while creating a SessionFactory. Check PotentialReasons collection, and InnerException for more detail.
---> System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.ArgumentException: Not a member access Parameter name: member
Ouch. Fluent NHibernate does not appreciate our expression trees. Why? Using FSI, we can inspect what we're actually converting the F# quotations to:
type SomeClass() = member x.Stuff = [|1;2;3|];;let myExpr : Expr<SomeClass -> seq<int>> = <@ fun x -> upcast x.Stuff @>;;let myLinq = LinqHelper.ToLinq myExpr;;
> myLinq;;val it : Linq.Expressions.Expression<Func<SomeClass,seq<int>>>= x => (x.Stuff As IEnumerable`1) {Body = (x.Stuff As IEnumerable`1); NodeType = Lambda; Parameters = seq [x]; Type = System.Func`2[FSI_0022+SomeClass,System.Collections.Generic.IEnumerable`1[System.Int32]];}
> myLinq.Body;;val it : Linq.Expressions.Expression= (x.Stuff As IEnumerable`1) {IsLifted = false; IsLiftedToNull = false; Method = null; NodeType = TypeAs; Operand = x.Stuff; Type = System.Collections.Generic.IEnumerable`1[System.Int32];}
So, what's going on? The upcast is modifying the F# quotation (as it should), and the ToLinqExpression is sticking this in as a "TypeAs" node. Fluent NHibernate does not seem to like this. Not. One. Bit.
But, it IS ok if we have a Convert node in the Expression tree. I imagine this is because C# generates such nodes in its expression trees (say, accessing an int member in a Func<T, object> expression). So, our last hack in making F# work right here is adding a fixup function to our LinqHelper, and using it from ToLinq:
let fixup (lexpr:LambdaExpression) =
if lexpr.Body.NodeType <> ExpressionType.TypeAs then lexpr else
let typeAs = lexpr.Body :?> UnaryExpression
let newBody = Expression.Convert(typeAs.Operand, typeAs.Type)
Expression.Lambda(lexpr.Type, newBody, lexpr.Parameters)
Now it's happy with our expressions.
Finally
The rest of the First Project is pretty straightforward in F#. For instance, creating the Session Factory:
let createSessionFactory() =
Fluently.Configure()
.Database(
MsSqlConfiguration.MsSql2005.ConnectionString(fun csb ->
csb.Is("server=(local);database=fhib;Integrated Security=true") |> ignore))
.Mappings(fun m ->
m.FluentMappings.AddFromAssemblyOf<Mappings.EmployeeMap>() |> ignore)
.BuildSessionFactory()
Everything seems to execute as it should. Fluent interfaces in F# generate a bit more noise, because of all the ignores that have to be added. I'm thinking of creating a type extension to obj to add Ignore as a method, to make it look a bit more uniform.
Future
It's my hope that F# has now addressed most of these issues -- 1.9.6 is 7 months old. Expression<T> is growing in importance, outside of LINQ querying, so I cannot see F# not being able to handle them easily and generating similar output to C#. It'd be really neat if F# would auto-convert quotations to Expression<T> when the expr is a syntactic argument like it does with delegates now. I'd also be surprised if abstract/virtual members still lack the ability to define accessibility.
Code: fhib.zip (4.76 KB)I did not include Fluent NHibernate or any of its dependencies. The project expects them to be in the project's lib folder, so get them here if you don't have them and extract them to "lib".
I welcome comments on this and suggestions for making the code more concise.
Remember Me