Using Entity Framework Core with F#

So this was fun… again, learned a lot… again! I blogged before about building a simple crud app that used a type provider and that worked pretty good, until I wanted to configure my continuous integration in VSTS. This didn’t work as smoothly as I wanted, because the type provider needs to connect to the database during the build. I read that I had to cache the definition or have the database setup before the build task, but still it felt uncomfortable. I thought, well why not take a look closer to home and use Entity Framework. Latest bits of course, because why not. Example code is on GitHub.

UPDATE 8/15/2018: GitHub example is updated because of some issues and there might be some other issues not fixed right now.

The types

Yes, this went pretty well. I thought I had to create mutable types to work with EF, but just adding the [<CLIMutable>] was enough. This makes the type just mutable for the compiler, not for us mortal developers. I defined a separate type for record ids because I can! Love this. Then I got all kung fu by using the and keyword, known as mutual recursive types, so I could have a Serie with a list of Episodes and have those Episodes have a navigation property to Serie. I made the latter an Option type and made sure it wasn’t mapped by using [<NotMapped>]. That way I can just create an instance of an Epsiode and later add it to a Serie.

type [<CLIMutable>] Serie =
  { Id: SerieId
    Name: string
    Description: string
    Episodes: List<Episode>
    Status: SerieStatus
and [<CLIMutable>] Episode = 
  { Id: EpisodeId
    Number: int
    Season : int
    Name: string
    Description: string
    Status: EpisodeStatus
    [<NotMapped>]Serie: Option<Serie>

The context

Then the DbContext, we’ll add DbSets for the types here, this is pretty straight forward. I also tried a 2.1.0 preview 1 feature here, namely the ValueConverter. With this you can map a type before is added or retrieved from the database. I just it here to map Enum as strings to the database, instead of integer. (It might be more useful to put enums in a separate table, but that’s not the point here)

let esconvert = ValueConverter<EpisodeStatus, string>((fun v -> v.ToString()), (fun v -> Enum.Parse(typedefof<EpisodeStatus>, v) :?> EpisodeStatus))
modelBuilder.Entity<Episode>().Property(fun e -> e.Status).HasConversion(esconvert) |> ignore

The repository

Time for some abstraction over the EF bits by using a repository. I don’t if this is all functional, so suggestions are welcome.

I started with the getSerie function by trying to work with the FirstOrDefault method. Stackoverflow convinced me to be more FSharpy and use the Seq.find which flow more nicely I think. The add functionality was simple, I struggled with the update a little more because I was used to Attach entities back to context or set it Modified.

let updateSerie (context: SerieContext) (entity: Serie) = 
    let currentEntry = context.Series.Find(entity.Id)
    context.SaveChanges |> ignore

It might look if this code will do an extra call to the database, but this is only if the entity isn’t loaded before. Otherwise the entity is retrieved from the context (memory).

I wanted to know how to do some Linq-like queries use I created a getSeriesWithAiredEpisodes function to try that out.

let getSeriesWithAiredEpisodes (context: SerieContext) = 
    query {
        for serie in context.Series do
            where (serie.Episodes.Exists (fun e -> e.Status = EpisodeStatus.Aired))
            select serie 

Had some light bulb moments trying Async, this isn’t my strong point in C# either by the way.

let addSerieAsync (context: SerieContext) (entity: Serie) =
    async {
        context.Series.AddAsync(entity) |> Async.AwaitTask |> ignore
        let! _ = context.SaveChangesAsync true |> Async.AwaitTask
        return entity

The configuration

Last but not least, applying the configuration. Functional applying that is! I created a CompositionRoot module so that the context is applied to the repository functions. Here you might set the connection strings too.

let configureSqlServerContext = 
    (fun () ->
        let optionsBuilder = new DbContextOptionsBuilder<SerieContext>();
        optionsBuilder.UseSqlServer(@"Server=.\SQLExpress;Database=Series;Integrated Security=SSPI;") |> ignore
        new SerieContext(optionsBuilder.Options)

I used the (func () -> …) construction (don’t know how this is called) so that if you need the create a separate context, just call this function. As you can see in the code on GitHub, this sample works with SQLite and SQL Server, just change the getContext function.

So that’s it, Have fun with it! Comments, critique or suggestions = awesome & welcome!