【问题标题】:Difficulty rewriting code from c# to f# related to Entity Framework与实体框架相关的从 c# 到 f# 的代码难以重写
【发布时间】:2019-03-28 17:54:07
【问题描述】:

我想用 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的返回类型,如何确保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());

成本项目

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

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

  • 1 尝试强制使用参数类型
builder
    .HasOne(fun i -> i.ProductionReport) 
    .WithMany(fun (pr: ProductionReport) -> pr.CostItems)

Result

  • 2 使用替代函数语法
builder
    .HasOne(<@ fun i -> i.ProductionReport @>) 
    .WithMany(<@ fun pr -> pr.CostItems @>)

Result

  • 3 对特定类型使用
builder
    .HasOne(<@ Func<ProductionReport,_> fun i -> i.ProductionReport @>) 
    .WithMany(<@ Func<CostItem,_> fun pr -> pr.CostItems @>)

Result

  • 4 分解来自 Nathan 的表达式解决方案
static member toExpr (f:'a -> 'b) = 
    <@ Func<_,_> (f) @> 
    |> LeafExpressionConverter.QuotationToExpression 
    |> unbox<Expression<Func<'a, 'b>>>

Factorization class

Result

  • 5 使用 Nathan 建议的类型符号分解表达式
    static member toExpr<'a, 'b> (f:'a -> 'b) = 
        <@ Func<_,_> (f) @> 
        |> LeafExpressionConverter.QuotationToExpression 
        |> unbox<Expression<Func<'a, 'b>>>

Result

【问题讨论】:

  • 您能否提供一个更完整的示例,以便我们查看构建器的类型以及此处涉及的 DbContext / 实体?
  • 您可以在 lambda 表达式内的值上添加类型注释:.WithMany(fun (pr : ProductionReport) -&gt; pr.CostItems)。我觉得有必要这样做有点奇怪,但我几乎没有使用 EF 的经验,所以谁知道....
  • 我假设您正在使用 HasOneWithMany 的重载,它们将 Expression 作为输入。在 C# 中,both 表达式和 Func/Action 使用“胖箭头”(=&gt;) 语法。在 F# 中,表达式使用不同的语法单独处理。我没有使用过这些 EF 方法,但您可以尝试 F# 表达式语法:.HasOne(&lt;@ fun i -&gt; i.ProductionReport @&gt;).WithMany(&lt;@ fun pr -&gt; pr.CostItems @&gt;) 看看是否可行。
  • 啊,好吧,我想我们可能会有所进展。它说Expr&lt;'b -&gt; 'c&gt;Expression&lt;Func&lt;_, _&gt;&gt; 不兼容。在许多情况下,F# 函数 ('b -&gt; 'c) 会自动转换为 C# 函数 (Func&lt;'b, 'c&gt;),但并非总是如此。在这种情况下,您可能必须自己进行转换。我会尝试.HasOne(&lt;@ Func&lt;ProductionReport,_&gt;(fun i -&gt; i.ProductionReport) @&gt;).WithMany(&lt;@ Func&lt;CostItem,_&gt;(fun pr -&gt; pr.CostItems) @&gt;) 不确定是不是这样,但这至少应该使函数类型兼容。
  • @abatishchev 在这种情况下可能是正确的,要么将 EF 内容保留在 C# 中并从 F# 项目中引用它,要么围绕此 EF 代码进行某种包装可能会很好,因为一些使用这些表达式的 F# 语法变得有点乏味......

标签: entity-framework .net-core f# c#-to-f#


【解决方案1】:

我想我明白了,但需要一些挖掘才能弄清楚如何使用这些表达式。我参考了this 帖子的历史 来了解如何构建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 看起来是相互引用的(请参阅下面的 cmets 中的讨论)。这意味着它们需要在同一个文件中定义并使用and 关键字(参见this 示例)

【讨论】:

  • 我成功了。我的代码现在很大,我想知道:有没有办法将它包装到一个函数中并将类型和乐趣作为参数?例如:toExpr(CostItem, ProductionReport, (fun (i:CostItem) -> i.ProductionReport))
  • 我打赌你可以这样做:``` let toExpr (f:'a -> 'b) = (f) @> |> LeafExpressionConverter.QuotationToExpression |> 拆箱>> ```
  • 但是您应该可以使用任何常规 F# 函数调用它
  • 我把代码放在我的库里试了一下。如果我只使用 Has One,它会起作用,但是当我跟进 WithMany 时它会失败。我用结果更新了我的问题。
  • 我明白了。您可以在 F# 中执行循环依赖,但两者必须在同一个文件中。第二种类型使用and 关键字代替 type 关键字声明,然后两者可以相互引用。你是对的,虽然不同文件之间的循环依赖是不允许的
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-08-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多