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
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.Entry(currentEntry).CurrentValues.SetValues(entity) 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!