EFCore.FSharp 10.0.0

Edit this page

Use RecordHelpers

EFCore.FSharp also includes a number of helper methods for allowing a more F#-like experience while interacting with a DbContext.

They can be found in EntityFrameworkCore.FSharp.DbContextHelpers

Example Usages

Given a context with a Blog defined as such

[<CLIMutable>]
type Blog = {
    [<Key>]
    Id : Guid
    Title : string
    Content : string
}

type MyContext () =
    inherit DbContext()

    [<DefaultValue>]
    val mutable private _blogs : DbSet<Blog>
    member this.Blogs with get() = this._blogs and set v = this._blogs <- v

We can use the helper methods like so

let interactWithContext (ctx:MyContext) =

    let originalBlogPost = {
        Id = (Guid.NewGuid())
        Title = "My Title"
        Content = "My original content"
    }

    originalBlogPost |> addEntity ctx |> ignore
    saveChanges ctx |> ignore

    let blogPostReadFromDb =
        match tryFindEntity<Blog> ctx originalBlogPost.Id with
        | Some b -> b
        | None -> failwithf "Could not entity of type %A with identifier %A" typeof<Blog> originalBlogPost.Id

    let modifiedBlogPost = { blogPostReadFromDb with Content = "My updated content" }

    // This method is needed as we are "updating" the database with what is technically a new object
    // We specify the key, allowing us to accommodate composite keys 
    updateEntity ctx (fun b -> b.Id :> obj) modifiedBlogPost |> ignore

    saveChanges ctx |> ignore

    let updatedBlogFromDb =
        match tryFindEntity<Blog> ctx originalBlogPost.Id with
        | Some b -> b
        | None -> failwithf "Could not find entity of type %A with identifier %A" typeof<Blog> originalBlogPost.Id

    updatedBlogFromDb.Content = "My updated content" // This will be true

An advantage of the generalised updateEntity function is that it allows us to use partial application to easily create methods for specific entities. For example:

let ctx = new MyContext()

// Partially applied function, all that it needs now is the Blog entity
let updateBlog = updateEntity ctx (fun (b:Blog) -> b.Id :> obj)

let myBlog = ctx.Blogs |> Seq.head

// No need to specify context or key, that's already been done
updateBlog { myBlog with Content = "Updated content" }

We also have methods and functions for DbSet/IQueryable to replace usage of FirstOrDefault and FirstOrDefaultAsync:

let queryingContext (ctx:MyContext) =
    let allPosts = toListAsync ctx.Blogs |> Async.RunSynchronously

    let firstBlog = 
        match tryFirst ctx.Blogs with
        | Some b -> b
        | None -> failwithf "no blogs"

    let filteredBlog = 
        let maybeBlog = tryFilterFirst <@ fun x -> x.Title = "some title" @> ctx.Blogs
        match maybeBlog with
        | Some b -> b
        | None -> failwithf "no blogs founded with the title"

    let firstBlogLinq = 
        match ctx.Blogs.TryFirst() with
        | Some b -> b
        | None -> failwithf "no blogs"

    let filteredBlogLinq = 
        match ctx.Blogs.TryFirst(fun x -> x.Title = "some title") with
        | Some b -> b
        | None -> failwithf "no blogs founded with the title"

    let advancedQuery =
        query {
            for b in ctx.Blogs do
            where (b.Title = "My title" && b.Content = "Some content")
            select b.Id
        }
        |> tryFirstAsync
        |> Async.RunSynchronously

    ()

Async methods

All methods in EntityFrameworkCore.FSharp.DbContextHelpers also have Async variants with support for async { ... } expressions

Multiple items
type CLIMutableAttribute = inherit Attribute new: unit -> CLIMutableAttribute

--------------------
new: unit -> CLIMutableAttribute
type Blog = { Id: Guid Title: string Content: string }
Multiple items
type KeyAttribute = inherit Attribute new: unit -> unit
<summary>Denotes one or more properties that uniquely identify an entity.</summary>

--------------------
KeyAttribute() : KeyAttribute
Blog.Id: Guid
Multiple items
[<Struct>] type Guid = new: b: byte array -> unit + 6 overloads member CompareTo: value: Guid -> int + 1 overload member Equals: g: Guid -> bool + 1 overload member GetHashCode: unit -> int member ToByteArray: unit -> byte array + 1 overload member ToString: unit -> string + 2 overloads member TryFormat: utf8Destination: Span<byte> * bytesWritten: byref<int> * ?format: ReadOnlySpan<char> -> bool + 1 overload member TryWriteBytes: destination: Span<byte> -> bool + 1 overload static member (<) : left: Guid * right: Guid -> bool static member (<=) : left: Guid * right: Guid -> bool ...
<summary>Represents a globally unique identifier (GUID).</summary>

--------------------
Guid ()
Guid(b: byte array) : Guid
Guid(b: ReadOnlySpan<byte>) : Guid
Guid(g: string) : Guid
Guid(b: ReadOnlySpan<byte>, bigEndian: bool) : Guid
Guid(a: int, b: int16, c: int16, d: byte array) : Guid
Guid(a: int, b: int16, c: int16, d: byte, e: byte, f: byte, g: byte, h: byte, i: byte, j: byte, k: byte) : Guid
Guid(a: uint32, b: uint16, c: uint16, d: byte, e: byte, f: byte, g: byte, h: byte, i: byte, j: byte, k: byte) : Guid
Blog.Title: string
Multiple items
val string: value: 'T -> string

--------------------
type string = String
Blog.Content: string
Multiple items
type MyContext = new: unit -> MyContext member Blogs: IQueryable<Blog>

--------------------
new: unit -> MyContext
Multiple items
type DefaultValueAttribute = inherit Attribute new: unit -> DefaultValueAttribute + 1 overload member Check: bool

--------------------
new: unit -> DefaultValueAttribute
new: check: bool -> DefaultValueAttribute
MyContext._blogs: obj
val set: elements: seq<'T> -> Set<'T> (requires comparison)
val interactWithContext: ctx: MyContext -> bool
val ctx: MyContext
val originalBlogPost: Blog
Guid.NewGuid() : Guid
val ignore: value: 'T -> unit
val blogPostReadFromDb: Blog
union case Option.Some: Value: 'T -> Option<'T>
val b: Blog
union case Option.None: Option<'T>
val failwithf: format: Printf.StringFormat<'T,'Result> -> 'T
val typeof<'T> : Type
val modifiedBlogPost: Blog
type obj = Object
val updatedBlogFromDb: Blog
val updateBlog: (Blog -> obj)
val myBlog: Blog
property MyContext.Blogs: Linq.IQueryable<Blog> with get, set
module Seq from Microsoft.FSharp.Collections
val head: source: seq<'T> -> 'T
val queryingContext: ctx: MyContext -> unit
val allPosts: obj
Multiple items
type Async = static member AsBeginEnd: computation: ('Arg -> Async<'T>) -> ('Arg * AsyncCallback * obj -> IAsyncResult) * (IAsyncResult -> 'T) * (IAsyncResult -> unit) static member AwaitEvent: event: IEvent<'Del,'T> * ?cancelAction: (unit -> unit) -> Async<'T> (requires delegate and 'Del :> Delegate) static member AwaitIAsyncResult: iar: IAsyncResult * ?millisecondsTimeout: int -> Async<bool> static member AwaitTask: task: Task<'T> -> Async<'T> + 1 overload static member AwaitWaitHandle: waitHandle: WaitHandle * ?millisecondsTimeout: int -> Async<bool> static member CancelDefaultToken: unit -> unit static member Catch: computation: Async<'T> -> Async<Choice<'T,exn>> static member Choice: computations: seq<Async<'T option>> -> Async<'T option> static member FromBeginEnd: beginAction: (AsyncCallback * obj -> IAsyncResult) * endAction: (IAsyncResult -> 'T) * ?cancelAction: (unit -> unit) -> Async<'T> + 3 overloads static member FromContinuations: callback: (('T -> unit) * (exn -> unit) * (OperationCanceledException -> unit) -> unit) -> Async<'T> ...

--------------------
type Async<'T>
static member Async.RunSynchronously: computation: Async<'T> * ?timeout: int * ?cancellationToken: Threading.CancellationToken -> 'T
val firstBlog: obj
val b: obj
val filteredBlog: obj
val maybeBlog: obj option
val firstBlogLinq: obj
val filteredBlogLinq: obj
val advancedQuery: obj
val query: Linq.QueryBuilder
custom operation: where (bool) Calls Linq.QueryBuilder.Where
custom operation: select ('Result) Calls Linq.QueryBuilder.Select