Philippe Adib
Philippe Adib

Reputation: 43

Difficulty rewriting code from c# to f# related to Entity Framework

I have a block of code that I want to write in F#, but the examples I have are in C#. I would like some help to write this in the F# language, and help understanding how it works.

Here is the c# code I have to mimic:

builder.HasMany(r => r.Options).WithOne(o => o.Root).HasForeignKey(o => o.RootId).OnDelete(DeleteBehavior.Cascade);

In F#, I am trying to do this:

builder
    .HasOne(fun i -> i.ProductionReport) 
    .WithMany(fun pr -> pr.CostItems)
    .HasForeignKey(fun pr -> pr.ProductionReportId).OnDelete(DeleteBehavior.Cascade) |> ignore

And the issue, per visual studio, is that pr is of type obj. How do I make sure f# knows that pr is of type ProductionReport, according to the return type of builder.HasOne.

Here is the complete sample requested:

BackendDemoDbContext

namespace BackendDemo.BackendDemoContext

open Microsoft.EntityFrameworkCore

type BackendDemoContext(options: DbContextOptions<BackendDemoContext>) =
    inherit DbContext(options)


    override __.OnModelCreating modelbuilder =         
        //Todo:
        //modelbuilder.ApplyConfiguration(new CostItemEntityTypeConfiguration());        
        //modelbuilder.ApplyConfiguration(new ProductionReportEntityTypeConfiguration());

CostItem

namespace BackendDemo.Data.Models

type CostItem() = 
    member val CostItemId = null with get, set
    member val Paper1 = null with get, set    
    member val Paper2 = null with get, set
    member val Cases = null with get, set
    member val Boxes = null with get, set
    member val Paste = null with get, set
    member val Bundling = null with get, set
    member val Ink = null with get, set
    member val Cardboard = null with get, set
    member val Wrapping = null with get, set
    member val Labour = null with get, set
    member val Fringe = null with get, set
    member val Pallet = null with get, set

    member val ProductionReportId =null with get,set
    member val ProductionReport = null with get, set

ProductionReport

namespace BackendDemo.Data.Models

open System.Collections
open BackendDemo.Data.Models

type ProductionReport() = 
    //val keyword necessary for AutoProperties
    member val ProductionReportId : int = 2
    //Todo:
    //abstract member CostItems : ICollection<CostItem> with get, set

CostItemEntityTypeConfiguration

namespace BackendDemo.Data.EntityConfigurations

open Microsoft.EntityFrameworkCore
open Microsoft.EntityFrameworkCore.Metadata.Builders
open BackendDemo.Data.Models

type CostItemEntityTypeConfiguration =
    interface IEntityTypeConfiguration<CostItem> with

        override this.Configure(builder: EntityTypeBuilder<CostItem>) =
            builder.ToTable("CostItem") |> ignore
            builder.HasKey(fun i -> i.CostItemId) |> ignore
            builder.Property(fun i -> i.Paper1).IsRequired() |> ignore
            builder.Property(fun i -> i.Paper2).IsRequired() |> ignore
            builder.Property(fun i -> i.Cases).IsRequired() |> ignore
            builder.Property(fun i -> i.Boxes).IsRequired() |> ignore
            builder.Property(fun i -> i.Paste).IsRequired() |> ignore
            builder.Property(fun i -> i.Bundling).IsRequired() |> ignore
            builder.Property(fun i -> i.Ink).IsRequired() |> ignore
            builder.Property(fun i -> i.Cardboard).IsRequired() |> ignore
            builder.Property(fun i -> i.Wrapping).IsRequired() |> ignore
            builder.Property(fun i -> i.Labour).IsRequired() |> ignore
            builder.Property(fun i -> i.Fringe).IsRequired() |> ignore
            builder.Property(fun i -> i.Pallet).IsRequired() |> ignore

            builder
                .HasOne(fun i -> i.ProductionReport) 
                .WithMany(fun pr -> pr.CostItems)
                .HasForeignKey(fun pr -> pr.ProductionReportId).OnDelete(DeleteBehavior.Cascade) |> ignore

ProductionReportEntityTypeConfiguration

namespace BackendDemo.Data.EntityConfigurations

open Microsoft.EntityFrameworkCore
open Microsoft.EntityFrameworkCore.Metadata.Builders
open BackendDemo.Data.Models

type ProductionReportEntityTypeConfiguration =
    interface IEntityTypeConfiguration<ProductionReport> with

        override this.Configure(builder: EntityTypeBuilder<ProductionReport>) =
            builder.ToTable("ProductionReport") |> ignore
            //Todo
            ///builder.HasKey(fun r -> r.ProductionReportId) |> ignore

Here are the results of the suggestions below (thanks by the way!):

builder
    .HasOne(fun i -> i.ProductionReport) 
    .WithMany(fun (pr: ProductionReport) -> pr.CostItems)

Result

builder
    .HasOne(<@ fun i -> i.ProductionReport @>) 
    .WithMany(<@ fun pr -> pr.CostItems @>)

Result

builder
    .HasOne(<@ Func<ProductionReport,_> fun i -> i.ProductionReport @>) 
    .WithMany(<@ Func<CostItem,_> fun pr -> pr.CostItems @>)

Result

static member toExpr (f:'a -> 'b) = 
    <@ Func<_,_> (f) @> 
    |> LeafExpressionConverter.QuotationToExpression 
    |> unbox<Expression<Func<'a, 'b>>>

Factorization class

Result

    static member toExpr<'a, 'b> (f:'a -> 'b) = 
        <@ Func<_,_> (f) @> 
        |> LeafExpressionConverter.QuotationToExpression 
        |> unbox<Expression<Func<'a, 'b>>>

Result

Upvotes: 4

Views: 748

Answers (1)

Nathan Wilson
Nathan Wilson

Reputation: 639

I think I got it, but it took some digging to figure out how to work with the expressions. I referenced this post's history to see how to build a System.Linq.Expressions.Expression. Here's what I have:

open System.Linq.Expressions
open Microsoft.FSharp.Linq.RuntimeHelpers

...

let toProdRptExpr : Expression<Func<CostItem, ProductionReport>> =
  <@ Func<_, _> (fun (i:CostItem) -> i.ProductionReport) @>
  |> LeafExpressionConverter.QuotationToExpression 
  |> unbox<Expression<Func<CostItem, ProductionReport>>>

let toCostItemsExpr : Expression<Func<ProductionReport, seq<CostItem>>> = 
  <@ Func<_,_> (fun (pr:ProductionReport) -> pr.CostItems) @>
  |> LeafExpressionConverter.QuotationToExpression 
  |> unbox<Expression<Func<ProductionReport, seq<CostItem>>>>

let a = builder.HasOne(toProdRptExpr)
let b = a.WithMany(toCostItemsExpr)

that's a lot more verbose than it needs to be, but it helped me figure out how the types fit together.

EDIT

For brevity, you can create a function like

let toExpr (f:'a -> 'b) = 
  <@ Func<_,_> (f) @>
  |> LeafExpressionConverter.QuotationToExpression 
  |> unbox<Expression<Func<'a, 'b>>>

and then use it like

builder
  .HasOne(toExpr(fun (i:CostItem) -> i.ProductionReport))
  .WithMany(toExpr(fun (pr:ProductionReport) -> pr.CostItems))

But you have to be careful because it looks like CostItem and ProductionReport are mutually referential (see the discussion in comments below). That means they need to be defined in the same file and use the and keyword (see this example)

Upvotes: 1

Related Questions