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 DateTime
. DateTime
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)