Logo




Subscribe:
RSS 2.0 | Atom 1.0
Categories:

Sign In


[Giagnocavo]Michael::Write()

# Wednesday, April 01, 2009
Using Fluent NHibernate with F#

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.Not.LazyLoad()

    base.Id ~@@ <@ fun x -> x.Id @> |> ignore

    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.

Code | FSharp
Wednesday, April 01, 2009 8:01:29 AM UTC  #    Comments [0]  |  Trackback

OpenID
Please login with either your OpenID above, or your details below.
Name
E-mail
Home page

Comment (HTML not allowed)  

Enter the code shown (prevents robots):

Live Comment Preview