难以将代码从 c# 重写为 f# 与 Entity Framework 相关

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

我有一段代码想用 F# 编写,但我的示例是用 C# 编写的。我需要一些帮助来用 F# 语言编写它,并帮助理解它是如何工作的。

这是我必须模仿的 c# 代码:

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

在 F# 中,我正在尝试这样做:

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

根据 visual studio,问题是 pr 是 obj 类型。根据 builder.HasOne.

的 return 类型,我如何确保 f# 知道 pr 是 ProductionReport 类型

这是请求的完整示例:

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

生产报告

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

以下是建议的结果(顺便感谢!):

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

我想我明白了,但是我花了一些时间来弄清楚如何使用这些表达式。我参考了 this post 的 历史 来了解如何构建 System.Linq.Expressions.Expression。这是我拥有的:

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)

这比需要的要冗长得多,但它帮助我弄清楚了这些类型是如何组合在一起的。

编辑

为简洁起见,您可以创建一个类似

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

然后像

一样使用它
builder
  .HasOne(toExpr(fun (i:CostItem) -> i.ProductionReport))
  .WithMany(toExpr(fun (pr:ProductionReport) -> pr.CostItems))

但是你必须要小心,因为它看起来像 CostItem 和 ProductionReport 相互引用(参见下面评论中的讨论)。这意味着它们需要在同一个文件中定义并使用 and 关键字(参见 this 示例)