Announcing EFCore.FSharp
Today marks the first alpha release of F# support for Entity Framework Core, allowing for direct usage of EF Core from your F# projects with support for EF Core 5.0.3
It contains support for migrations and reverse-engineering, allowing you to map tables to F# records and take advantage of F# idioms such as option
to represent nullable columns. At the moment though, this does not work for primary or foreign keys, just plain columns (GitHub issue).
To take it for a test drive, grab the NuGet package from https://www.nuget.org/packages/EntityFrameworkCore.FSharp
Bear in mind that it is an alpha release so please log any issues on the GitHub repo.
Currently it has support for relatively simple schemas, it does not support many-types-per-table relationships yet but that is on the to-do list.
Although it does support option
columns it does not yet support types such as discriminated unions.
Installation
All the usual ways of grabbing a NuGet package...
Install-Package EntityFramworkCore.FSharp
dotnet add package EntityFramworkCore.FSharp
paket install EntityFramworkCore.FSharp
Usage
When it is installed, you will need to register it for dotnet ef
to pick it up. Simply add the following code to your project. It will be detected at compile time, no need to call it from a Startup
class
module DesignTimeServices
open Microsoft.Extensions.DependencyInjection
open Microsoft.EntityFrameworkCore.Design
open EntityFrameworkCore.FSharp
type DesignTimeServices() =
interface IDesignTimeServices with
member __.ConfigureDesignTimeServices(serviceCollection: IServiceCollection) =
let fSharpServices = EFCoreFSharpServices.Default
fSharpServices.ConfigureDesignTimeServices serviceCollection
()
Migrations
Code first databases are supported, a working example of creating a basic MVC application in F# with user authentication is available at https://github.com/simon-reynolds/EFCore.FSharp.MvcAuth
One thing to remember is that record types have to have the [<CliMutable>]
attribute specified so that they can be created correctly by Entity Framework.
After running dotnet ef migration add
you will need to add the created files to your your project. The migration files are generated sequentially so you can add a single glob reference to your fsproj file to add them all at once
<Compile Include="Migrations/*.fs" />
There is an issue to track this to try find an automated solution. As always, any help or suggestions are welcome.
Scaffolding
When scaffolding a model from an existing database, we can specify how we want the generated code to be created in our DesignTimeServices
type above.
We can create types as either record types or classes similar to how they behave in C#
For instance, given a blog post type with an Id, Title and Content, it can generated as either example below.
// Record type
type BlogPost = {
Id : int
Title: string
Content: string
}
// Class type
type BlogPost() =
[<DefaultValue>] val mutable private _Id : int
member this.Id with get() = this._Id and set v = this._Id <- v
[<DefaultValue>] val mutable private _Title : string
member this.Title with get() = this._Title and set v = this._Title <- v
[<DefaultValue>] val mutable private _Content : string
member this.Content with get() = this._Content and set v = this._Content <- v
Similarly, optional columns can either be rendered as Nullable<'a>
or as 'a option
. The default configuration will create record types with nullable columns specified as option
types.
These are done by defining our scaffold options and passing them into the DesignTimeServices
, e.g.
module DesignTimeServices =
open Microsoft.Extensions.DependencyInjection
open Microsoft.EntityFrameworkCore.Design
open EntityFrameworkCore.FSharp
type DesignTimeServices() =
interface IDesignTimeServices with
member __.ConfigureDesignTimeServices(serviceCollection: IServiceCollection) =
// The default behaviour can be specified by calling
let fSharpServices = EFCoreFSharpServices.Default
// Or we can define a ScaffoldOptions use that instead
let scaffoldOptions =
ScaffoldOptions (
ScaffoldTypesAs = ScaffoldTypesAs.ClassType,
ScaffoldNullableColumnsAs = ScaffoldNullableColumnsAs.NullableTypes)
let fSharpServices = EFCoreFSharpServices.WithScaffoldOptions scaffoldOptions
fSharpServices.ConfigureDesignTimeServices serviceCollection
()
More details can be found in our project documentation here.
Option types
It has basic support for option
types, including an OptionConverter
for mapping nullable columns to options. When building a code first database simply include the following at the end of OnModelCreating
type MyContext (options) =
inherit DbContext (options)
override this.OnModelCreating mb =
(* Define entities here *)
modelBuilder.RegisterOptionTypes()
Other issues
I'm sure there are. Please report any issues you discover and help us improve F# support for Entity Framework Core!