Asbestos Supply

2011-08-07 F# and Entity Framework, not there yet

Based on this article on MSDN I decided to code the data layer for an app in F#.  After a few weeks of hobby time coding, I don't believe that EF via F# is a viable solution yet.  Here's why:

NOTE: Windows Live Writer is crashing constantly on me.  I've got 2 reasons so far and may update this post with more when Live Writer starts behaving (going to restart at some point this week I suppose) or I find a more stable editor.

1. No protected keyword

Because the Entity Framework's proxy classes inherit from their base classes (and because they do not support setting private properties of their ancestors), the protected keyword becomes especially important.  Protected becomes a way of creating properties that can be set only by the class itself and by Entity Framework's proxy classes.  I'm especially fond of this in constructors.  Take the following code snippet:

public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public void Person(string firstName, string lastName)
    {
      FirstName = firstName;
        LastName = lastName;
    }
    protected void Person() {}
}

This requires client code to specify a first and last name for a Person while still allowing Entity Framework to construct a new Person object via its default constructor (a requirement) before setting the properties.  This is also really useful for lists and such – by making them protected you can ensure that they are never null and wrap list inserts inside a method like so:

...
protected List<string> people {get; set;}
void AddPerson(string name)
{
    // business logic around the name
    people.Add(name);
}
...

Since F# doesn't support the protected keyword, there's no alternative to generating a public default constructor and exposing your lists getter and setter for all.

2. Awkward null support

When writing functional code, the lack of implicit support for null values is very useful.  However, Entity Framework is used to interact with a database – somewhere where nulls are commonplace and very useful.  I wrote a blog post about one issue with this (F#, System.Linq extensions and the case of the missing null) but there are others including lack of support for Nullable (in case you think you're unfamiliar with Nullable, you're probably not. You may never have written Nullable in your code -- the ? in C# is syntactic sugar around it.  So if you've ever written int? in C#, you've used Nullable).  F# has its own way of achieving something similar to Nullable called Option but it's based on F# Discriminated Unions and is not supported by Entity Framework. 

The biggest use case for Nullable in Entity Framework in my opinion is with DateTimeDateTime is a value type and cannot be null.  But null datetime values in the database are a frequent occurrence usually meant to signify that something has never taken place.  Take the following code:

public class Email
{
    ...
    public DateTime? DateSent {get; set;}
    ...
}

In this case, the Email class defines a DateSent property that presumably will be null until the email is actually sent. This makes a lot of sense in the database and even in C# but doesn't work out so well in F#.  First of all there's the syntactic sugar in C#.  Consider the same code written in F#:

open System
type Email =
    ...
    let mutable m_dateSent = new Nullable<DateTime>()
    member this.DateSent with get () = m_dateSent 
                            and set v = m_dateSent <- v
    ...

Aside from the verbosity, check out this snippet that doesn't compile (if you're not familiar with the syntax, I'm using the F# PowerPack Linq extensions:

let unsentEmails = query <@ seq { for e in context.Emails do if e.EmailSent = null then yield e } @> 

Can you guess why that won't compile?  It checks EmailSent, which is a Nullable against null which is not permitted a null value.  You can get around this by changing that line to

if e.EmailSent.HasValue = false then

However, the outputted SQL (using the default, MSSQL connector) from this line is far worse than the equivalent in C# (using the "= null").  In C#, the expected SQL is outputted – including a where clause that checks for the column against null which allows the use of an index in MSSQL (although not for mySQL which is another conversation altogether!).  The F# code, checking the HasValue property of the Nullable,  generates some awful SQL in the where clause using CASE and CAST seemingly just to annoy by preventing the use of indexes:

WHERE 0 = 
    (CASE 
        WHEN ([Extent1].[EmailSent] IS NOT NULL) THEN cast(1 as bit)
        WHEN ([Extent1].[EmailSent] IS NULL) THEN cast(0 as bit)
    END)