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.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!
I love using SQL Provider and Iβm curious as to what you mean by caching a definition. How is this done?
Well that’s the road I didn’t take; but they talk about this in thread for example: https://stackoverflow.com/questions/40685423/connect-to-sql-database-without-the-sql-type-provider
Nice post by Ody Mbegbu about this: https://medium.com/real-world-fsharp/continuous-integration-with-type-providers-6ddf40ee31b3
Thanks for sharing this, it is good to know I am the only one trying. The main reason for me to use EF with F# are migrarions, so I do not have to write any sql scripts manually. Keep it up!
Thanks! Support for migrations in F# should be available in the near future; https://github.com/bricelam/EFCore.FSharp/tree/migration-support
Very cool! Thanks for sharing.
Hi I was wondering is there a good way to do these kimds of things with Oracle database. Does F# have a provider that works with Oracle?
Thanks,
Mattq
Do you mean for EF Core? Then you might look into this: https://docs.microsoft.com/en-us/ef/core/providers/#oracle
Else there is a type provider for F#: https://fsprojects.github.io/SQLProvider/core/oracle.html
I have some problemns with your github example,
the function `getSerie ` in SeriesRepository dont filter in query the id, it brings all the series in database and then filter it in memory
to work as it should, i have to change how it filters
“`
let getSerie (context: SerieContext) id =
context
.Series
.Include(fun s -> s.Episodes)
.FirstOrDefault (fun f -> f.Id = id)
|> (fun x -> if box x = null then None else Some x)
“`
Other problem is, is not possible to navigate to Serie from Episode, this is awfull, i dont find anyway to treat it as a navigation property
After that, i find myself using SQLProvider, SQLClient and thi wrapper for Dapper (https://github.com/AlexTroshkin/fsharp-dapper) more convenient
tks for the contribution
I might revisit this blog post and make some changes because of these problems (and others I found) I used some of your example code btw π
let getSerie (context: SerieContext) id =
query {
for serie in context.Series do
where (serie.Id = id)
select serie
exactlyOne
} |> (fun x -> if box x = null then None else Some x)
Did you find a solution for the option navigation property?
No not yet and I think this might not be supported by EF in combination with F# right now. It looks like EF cannot deal with Option types like it deals with Nullable types in C#.
Nice,
And how you are dealing with navigation properties on queries?
Thank you very much for the GitHub project. This was very helpful in getting started using EF Core with F#.
This is a great tutorial, F# + EFCore resources are not easy to find, out there ! I just set up a very similar app using Suave for web access and a SQLite db, and I ran into a weird issue : performance are not very good, at all. A single insert takes at least 250ms, up to 450ms. This is very troubling because a raw insertion using ado.net would be more around 10ms IIRC. Have you had this kind of issues ? Do you know how to fix this ? Thanks ?