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

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
[<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

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
30: 
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:

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
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:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
30: 
31: 
32: 
33: 
34: 
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 -> KeyAttribute

--------------------
KeyAttribute() : KeyAttribute
Blog.Id: Guid
Multiple items
type Guid =
  struct
    new : b:byte[] -> Guid + 5 overloads
    member CompareTo : value:obj -> int + 1 overload
    member Equals : o:obj -> bool + 1 overload
    member GetHashCode : unit -> int
    member ToByteArray : unit -> byte[]
    member ToString : unit -> string + 2 overloads
    member TryFormat : destination:Span<char> * charsWritten:int * ?format:ReadOnlySpan<char> -> bool
    member TryWriteBytes : destination:Span<byte> -> bool
    static val Empty : Guid
    static member NewGuid : unit -> Guid
    ...
  end

--------------------
Guid ()
Guid(b: byte []) : Guid
Guid(b: ReadOnlySpan<byte>) : Guid
Guid(g: string) : Guid
Guid(a: int, b: int16, c: int16, d: 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
Guid(a: int, b: int16, c: int16, 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 =
  inherit DbContext
  new : unit -> MyContext
  val mutable private _blogs: DbSet<Blog>
  member Blogs : DbSet<Blog>
  member Blogs : DbSet<Blog> with set

--------------------
new : unit -> MyContext
Multiple items
type DbContext =
  new : options:DbContextOptions -> DbContext
  member Add<'TEntity> : entity:'TEntity -> EntityEntry<'TEntity> + 1 overload
  member AddAsync<'TEntity> : entity:'TEntity * ?cancellationToken:CancellationToken -> ValueTask<EntityEntry<'TEntity>> + 1 overload
  member AddRange : [<ParamArray>] entities:obj[] -> unit + 1 overload
  member AddRangeAsync : [<ParamArray>] entities:obj[] -> Task + 1 overload
  member Attach<'TEntity> : entity:'TEntity -> EntityEntry<'TEntity> + 1 overload
  member AttachRange : [<ParamArray>] entities:obj[] -> unit + 1 overload
  member ChangeTracker : ChangeTracker
  member ContextId : DbContextId
  member Database : DatabaseFacade
  ...

--------------------
DbContext() : DbContext
DbContext(options: DbContextOptions) : DbContext
Multiple items
type DefaultValueAttribute =
  inherit Attribute
  new : unit -> DefaultValueAttribute
  new : check:bool -> DefaultValueAttribute
  member Check : bool

--------------------
new : unit -> DefaultValueAttribute
new : check:bool -> DefaultValueAttribute
MyContext._blogs: DbSet<Blog>
type DbSet<'TEntity (requires reference type)> =
  member Add : entity:'TEntity -> EntityEntry<'TEntity>
  member AddAsync : entity:'TEntity * ?cancellationToken:CancellationToken -> ValueTask<EntityEntry<'TEntity>>
  member AddRange : [<ParamArray>] entities:'TEntity[] -> unit + 1 overload
  member AddRangeAsync : [<ParamArray>] entities:'TEntity[] -> Task + 1 overload
  member AsAsyncEnumerable : unit -> IAsyncEnumerable<'TEntity>
  member AsQueryable : unit -> IQueryable<'TEntity>
  member Attach : entity:'TEntity -> EntityEntry<'TEntity>
  member AttachRange : [<ParamArray>] entities:'TEntity[] -> unit + 1 overload
  member EntityType : IEntityType
  member Equals : obj:obj -> bool
  ...
val this : MyContext
val set : elements:seq<'T> -> Set<'T> (requires comparison)
val v : DbSet<Blog>
val interactWithContext : ctx:MyContext -> bool
val ctx : MyContext
val originalBlogPost : Blog
Guid.NewGuid() : Guid
val addEntity : ctx:DbContext -> entity:'a -> unit (requires reference type)
val ignore : value:'T -> unit
val saveChanges : ctx:DbContext -> unit
val blogPostReadFromDb : Blog
val tryFindEntity : ctx:DbContext -> key:obj -> 'a option (requires reference type)
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
val updateEntity : ctx:DbContext -> key:('a -> 'b) -> entity:'a -> 'a (requires reference type)
type obj = Object
val updatedBlogFromDb : Blog
val updateBlog : (Blog -> Blog)
val myBlog : Blog
property MyContext.Blogs: DbSet<Blog> with get, set
module Seq

from Microsoft.FSharp.Collections
val head : source:seq<'T> -> 'T
val queryingContext : ctx:MyContext -> unit
val allPosts : Blog list
val toListAsync : dbset:#Linq.IQueryable<'b> -> Async<'b list>
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 -> Async<unit>
  static member AwaitTask : task:Task<'T> -> Async<'T>
  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>
  ...

--------------------
type Async<'T> =
static member Async.RunSynchronously : computation:Async<'T> * ?timeout:int * ?cancellationToken:Threading.CancellationToken -> 'T
val firstBlog : Blog
val tryFirst : dbset:#Linq.IQueryable<'b> -> 'b option
val filteredBlog : Blog
val maybeBlog : Blog option
val tryFilterFirst : predicate:Quotations.Expr<('a -> bool)> -> dbSet:#Linq.IQueryable<'a> -> 'a option
val x : Blog
val firstBlogLinq : Blog
member Linq.IQueryable.TryFirst : unit -> 'T option
member Linq.IQueryable.TryFirst : expr:Linq.Expressions.Expression<Func<'T,bool>> -> 'T option
val filteredBlogLinq : Blog
val advancedQuery : Guid option
val query : Linq.QueryBuilder
custom operation: where (bool)

Calls Linq.QueryBuilder.Where
custom operation: select ('Result)

Calls Linq.QueryBuilder.Select
val tryFirstAsync : dbset:#Linq.IQueryable<'b> -> Async<'b option>