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!):
1 Try forcing an argument type
builder
.HasOne(fun i -> i.ProductionReport)
.WithMany(fun (pr: ProductionReport) -> pr.CostItems)
Result
2 Use the alternative Function syntax
builder
.HasOne(<# fun i -> i.ProductionReport #>)
.WithMany(<# fun pr -> pr.CostItems #>)
Result
3 Use the <# notation with specific type
builder
.HasOne(<# Func<ProductionReport,_> fun i -> i.ProductionReport #>)
.WithMany(<# Func<CostItem,_> fun pr -> pr.CostItems #>)
Result
4 Factorize the Expression solution from Nathan
static member toExpr (f:'a -> 'b) =
<# Func<_,_> (f) #>
|> LeafExpressionConverter.QuotationToExpression
|> unbox<Expression<Func<'a, 'b>>>
Factorization class
Result
5 Factorize the Expression with type notation suggested by Nathan
static member toExpr<'a, 'b> (f:'a -> 'b) =
<# Func<_,_> (f) #>
|> LeafExpressionConverter.QuotationToExpression
|> unbox<Expression<Func<'a, 'b>>>
Result
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)
Related
I have a map like :
val programming = Map(("functional", 1) -> "scala", ("functional", 2) -> "perl", ("orientedObject", 1) -> "java", ("orientedObject", 2) -> "C++")
with the same first element of key appearing multiple times.
How to regroup all the values corresponding to the same first element of key ? Which would turn this map into :
Map("functional" -> List("scala","perl"), "orientedObject" -> List("java","C++"))
UPDATE: This answer is based upon your original question. If you need the more complex Map definition, using a tuple as the key, then the other answers will address your requirements. You may still find this approach simpler.
As has been pointed out, you can't actually have multiple keys with the same value in a map. In the REPL, you'll note that your declaration becomes:
scala> val programming = Map("functional" -> "scala", "functional" -> "perl", "orientedObject" -> "java", "orientedObject" -> "C++")
programming: scala.collection.immutable.Map[String,String] = Map(functional -> perl, orientedObject -> C++)
So you end up missing some values. If you make this a List instead, you can get what you want as follows:
scala> val programming = List("functional" -> "scala", "functional" -> "perl", "orientedObject" -> "java", "orientedObject" -> "C++")
programming: List[(String, String)] = List((functional,scala), (functional,perl), (orientedObject,java), (orientedObject,C++))
scala> programming.groupBy(_._1).map(p => p._1 -> p._2.map(_._2)).toMap
res0: scala.collection.immutable.Map[String,List[String]] = Map(functional -> List(scala, perl), orientedObject -> List(java, C++))
Based on your edit, you have a data structure that looks something like this
val programming = Map(("functional", 1) -> "scala", ("functional", 2) -> "perl",
("orientedObject", 1) -> "java", ("orientedObject", 2) -> "C++")
and you want to scrap the numerical indices and group by the string key. Fortunately, Scala provides a built-in that gets you close.
programming groupBy { case ((k, _), _) => k }
This will return a new map which contains submaps of the original, grouped by the key that we return from the "partial" function. But we want a map of lists, so let's ignore the keys in the submaps.
programming groupBy { case ((k, _), _) => k } mapValues { _.values }
This gets us a map of... some kind of Iterable. But we really want lists, so let's take the final step and convert to a list.
programming groupBy { case ((k, _), _) => k } mapValues { _.values.toList }
You should try the .groupBy method
programming.groupBy(_._1._1)
and you will get
scala> programming.groupBy(_._1._1)
res1: scala.collection.immutable.Map[String,scala.collection.immutable.Map[(String, Int),String]] = Map(functional -> Map((functional,1) -> scala, (functional,2) -> perl), orientedObject -> Map((orientedObject,1) -> java, (orientedObject,2) -> C++))
you can now "clean" by doing something like:
scala> res1.mapValues(m => m.values.toList)
res3: scala.collection.immutable.Map[String,List[String]] = Map(functional -> List(scala, perl), orientedObject -> List(java, C++))
Read the csv file and create a map that contains key and list of values.
val fileStream = getClass.getResourceAsStream("/keyvaluepair.csv")
val lines = Source.fromInputStream(fileStream).getLines
var mp = Seq[List[(String, String)]]();
var codeMap=List[(String, String)]();
var res = Map[String,List[String]]();
for(line <- lines )
{
val cols=line.split(",").map(_.trim())
codeMap ++= Map(cols(0)->cols(1))
}
res = codeMap.groupBy(_._1).map(p => p._1 -> p._2.map(_._2)).toMap
Since no one has put in the specific ordering he asked for:
programming.groupBy(_._1._1)
.mapValues(_.toSeq.map { case ((t, i), l) => (i, l) }.sortBy(_._1).map(_._2))
I need help for passing HashMap to queryParam as shown below, as I have so many request with same queryParams and just last queryParam changes with each request.
val asset_sort_by_displays = exec(http("Sort by Displays")
.get("/data/analytics/reports/")
.queryParamMap(GatlingConfig.asset_query_string_map)
.queryParam("""sort""", """displays""")
Where in my config file I have a object GatlingConfig{} where I've defined asset_query_string_map as.
val asset_query_string_map = Map("""report_type""" -> """performance""",
"""start_date""" -> "2014-07-07",
"""end_date""" -> "2014-07-10",
"""metrics""" -> """plays""",
"""metrics""" -> """displays""",
"""metrics""" -> """video_starts""")
But, it throws " value asset_query_string_map is not a member of io.gatling.http.request.builder.HttpRequestBuilder " error.
Please guide me , how can I pass Map value to queryParams?
Where in my config file I have created
I don't get it. This is to be defined in some Scala code and imported/placed in scope.
Then, queryParam is for unique values. You'd have to use queryParamMap instead.
With current snapshot, you'd have:
val asset_query_string_map = Map("""report_type""" -> """performance""",
"""start_date""" -> "2014-07-07",
"""end_date""" -> "2014-07-10",
"""metrics""" -> """plays""",
"""metrics""" -> """displays""",
"""metrics""" -> """video_starts""")
val asset_sort_by_displays = exec(http("Sort by Displays")
.get("/data/analytics/reports/")
.queryParamMap(asset_query_string_map)
.queryParam("""sort""", """displays"""))
So for a very small formula interpretter someone asked me to code-golf, I wanted to do something like
val ops = Map("plus" -> + , "minus"-> -, "times"-> *, "div" -> / )
to allow quick conversion between keywords and the functions they describe. Not only did this syntax not parse, but none of the other shorthand syntaxes I tried ( _ + _, _:Int.+_)parsed. Is there a way of doing this as a function shorthand, or am I doomed to writing out the lambdas fully, ruining my golf score.
Edit: The problem in question was integers only. I understand that overloading would make this vastly more difficult otherwise.
Not sure how your score is impacted by defining an additional function. You could lift the operators, using something like this:
def lift(f:(Int,Int)=>Int) = f
val ops = Map("plus" -> lift(_+_), "minus" -> lift(_-_), "times"-> lift(_*_), "div" -> lift(_/_))
And if you want to shave some chars:
def ↑(f:(Int,Int)=>Int) = f
val ops = Map("plus" -> ↑(_+_), "minus" -> ↑(_-_), "times"-> ↑(_*_), "div" -> ↑(_/_))
And as om-nom-nom comments, this explicit lifting can be done by the compiler as well by providing the type signature to coerse the lifting:
val ops: Map[String, (Int,Int) => Int] = Map("plus" -> (_+_), "minus" -> (_-_), "times"->(_*_), "div" -> (_/_))
I am trying to define a key for a model type that has two key properties and is defined like this:
type Model () =
member val IdOne = 0 with get, set
member val IdTwo = 0 with get, set
member val OtherProperty = "" with get, set
When I try to use this model in Entity Framework 5, I get the error that "Model has no key defined. Define the key for this EntityType". The model types are given, I cannot change them and add the [<Key>] attribute. So I tried the Fluent API.
In C#, you would do something like this:
modelBuilder.Entity<Model>().HasKey(m => new { m.IdOne, m.IdTwo });
It uses an anonymous type. But for the life of me I cannot figure out how to pull this off in F#. I tried Tuples, Records, even a regular type that has the properties IdOne and IdTwo:
// Regular type with properties IdOne & IdTwo.
type ModelKey (idOne, idTwo) =
member this.IdOne = idOne
member this.IdTwo = idTwo
modelBuilder.Entity<Model>().HasKey(fun (m : Model) -> ModelKey (m.IdOne, m.IdTwo))
// ArgumentNullException: Value cannot be null. Parameter name: source
// Regular type with default constructor and properties IdOne & IdTwo.
type ModelKey2 () =
member val IdOne = 0 with get, set
member val IdTwo = 0 with get, set
modelBuilder.Entity<Model>().HasKey(fun (m : Model) -> ModelKey2 ())
// ArgumentNullException: Value cannot be null. Parameter name: source
// Record type.
type ModelKeyRecord = { IdOne : Int32; IdTwo : Int32 }
modelBuilder.Entity<Model>().HasKey(fun (m : Model) -> { IdOne = m.IdOne; IdTwo = m.IdTwo })
// ArgumentNullException: Value cannot be null. Parameter name: source
// Tuple.
modelBuilder.Entity<Model>().HasKey(fun (m : Model) -> (m.IdOne, m.IdTwo))
// ArgumentNullException: Value cannot be null. Parameter name: source
None of these approaches work, I get an ArgumentNullException every time. I'm out of ideas...
EDIT
With the code Tomas provided below (that causes the same ArgumentNullException btw.), I did some snooping around. This is what I found:
I used the function below to analyze the Expression Tree C# is building:
static void Analyze<T>(Expression<Func<Model, T>> function)
{
}
// Call it like this:
Analyze(m => new { m.IdOne, m.IdTwo });
Then I looked at the generated lambda in the debugger. This is what C# generates:
{m => new <>f__AnonymousType0'2(IdOne = m.IdOne, IdTwo = m.IdTwo)}
Doing the same thing on the F# side using the getFuncTree function from Tomas using <# fun (m : Model) -> ModelKey(m.IdOne, m.IdTwo) #> yields:
{m => new ModelKey(m.IdOne, m.IdTwo)}
As you can see, the explicit naming of the - what is this anyway, looks like properties - arguments is missing in the F# code. I recreated the whole expression tree by hand in F# so that it would look like the C# version:
let modelKeyExpression =
Expression.Lambda<Func<Model, ModelKey>> (
body = Expression.New (
``constructor`` = typeof<ModelKey>.GetConstructor [| typeof<Int32>; typeof<Int32> |],
arguments = seq {
yield Expression.MakeMemberAccess (
expression = Expression.Parameter (
``type`` = typeof<Model>,
name = "m"
),
``member`` = typeof<Model>.GetProperty "IdOne"
) :> Expression;
yield Expression.MakeMemberAccess (
expression = Expression.Parameter (
``type`` = typeof<Model>,
name = "m"
),
``member`` = typeof<Model>.GetProperty "IdTwo"
) :> Expression
},
members = seq {
yield (typeof<ModelKey>.GetProperty "IdOne") :> MemberInfo
yield (typeof<ModelKey>.GetProperty "IdTwo") :> MemberInfo
}
),
parameters = [
Expression.Parameter (
``type`` = typeof<Model>,
name = "m"
)
]
)
The part that was missing in the F# representation is the members sequence.
When I move the mouse over this expression, this representation appears:
{m => new ModelKey(IdOne = m.IdOne, IdTwo = m.IdTwo)}
As you can see, apart from the class, it looks the same. But when I try to use this expression in the HasKey method, I get the following InvalidOperationException:
The properties expression 'm => new ModelKey(IdOne = m.IdOne, IdTwo= m.IdTwo)'
is not valid. The expression should represent a property: C#: 't =>
t.MyProperty' VB.Net: 'Function(t) t.MyProperty'. When specifying multiple
properties use an anonymous type: C#: 't => new { t.MyProperty1,
t.MyProperty2 }' VB.Net: 'Function(t) New With { t.MyProperty1,
t.MyProperty2 }'.
So it seems to me that this anonymous class syntax does something special...
In F# 4.6, this has worked for me after much struggle.
Apperantly all you need is a tuple. Makes sense since an anon object without explicit member names is basically, a tuple.
I can't believe nobody has put this on the MS documentation tho.
modelBuilder.Entity<Entity>()
.HasKey(fun e -> (e.Key1, e.Key2) :> obj)
|> ignore
EDIT: The technique below would be needed in F# 2.0, but it should not be needed in the newer versions. There must be some other issue with the F#-generated expression tree...
I think the problem is that Entity Framework wants you to specify a lambda expression as an expression tree:
modelBuilder.Entity<Model>().HasKey(fun (m : Model) -> ModelKey (m.IdOne, m.IdTwo))
This should just work in F# 3.1 in Visual Studio 2013, but it is not supported in F# 3.0. You can still do this, but you'll have to use F# quotations and write a bit of code that converts a quotation to LINQ expression tree - there is a helper that does most of the work:
open System
open System.Linq.Expressions
open Microsoft.FSharp.Quotations
open Microsoft.FSharp.Linq.RuntimeHelpers
let getFuncTree (quotation:Expr<'a -> 'b>) =
let e = LeafExpressionConverter.QuotationToExpression quotation
let call = e :?> MethodCallExpression
let lam = call.Arguments.[0] :?> LambdaExpression
Expression.Lambda<Func<'a, 'b>>(lam.Body, lam.Parameters)
getFuncTree <# fun x -> x + 1 #>
Using this, you should be able to call:
modelBuilder.Entity<Model>().HasKey(getFuncTree <# fun (m : Model) ->
ModelKey (m.IdOne, m.IdTwo) #>)
You could define an extension method like HasKeyQuot that does this behind the cover, to make the code a bit nicer.
It looks like the issue is twofold:
F#'s compiler never sets the Members collection of a LINQ NewExpression during quotation translation, but this is used to mark the construction of an anonymous type, which EF expects.
EF is really picky: even in C#, doing m => new { A = m.IdOne, B = m.IdTwo } won't work - the anonymous type's property names must match the model's property names.
One way to work around the issue (which is probably overkill, but works) is to create a new type dynamically at runtime which has fields with the right names and then just use a tuple in the F# code:
open Quotations.Patterns
open Quotations.ExprShape
open System.Reflection
open System.Linq.Expressions
module AnonymousTypeFixer =
let private mb =
let ab = System.AppDomain.CurrentDomain.DefineDynamicAssembly(AssemblyName("codeGen"), Emit.AssemblyBuilderAccess.ReflectionOnly)
ab.DefineDynamicModule("codeGen")
let transform (Lambda(v, (NewTuple exprs)) : Quotations.Expr<'a -> 'b>) =
let newV = Expression.Variable(v.Type, v.Name)
let cvtExpr (PropertyGet(Some(Var v'), p, [])) =
assert (v = v')
Expression.Property(newV, p) :> Expression, p
let ty = mb.DefineType(v.Type.Name)
let ctor = ty.DefineConstructor(MethodAttributes.Public (*||| MethodAttributes.RTSpecialName ||| MethodAttributes.SpecialName*), CallingConventions.HasThis, exprs |> List.map (fun e -> e.Type) |> List.toArray)
ctor.GetILGenerator().Emit(Emit.OpCodes.Ret)
let fields =
[for (_, p) in exprs |> List.map cvtExpr ->
ty.DefineField(p.Name, p.PropertyType, FieldAttributes.Public) :> MemberInfo]
ty.CreateType()
let newE = Expression.New(ctor, exprs |> Seq.map (cvtExpr >> fst), fields)
Expression.Lambda<System.Func<'a, obj>>(newE, newV)
let mb = System.Data.Entity.DbModelBuilder()
mb.Entity<Model>().HasKey(AnonymousTypeFixer.transform <# fun (m:Model) -> m.IdOne, m.IdTwo #>)
// F# 3.0
open Microsoft.FSharp.Linq.RuntimeHelpers.LeafExpressionConverter
// Regular type with properties IdOne & IdTwo.
type ModelKey (idOne, idTwo) =
member this.IdOne = idOne
member this.IdTwo = idTwo
modelBuilder.Entity<Model>()
.HasKey(QuotationToLambdaExpression(
<# Func<_,_>(fun m -> NewAnonymousObjectHelper<_>(ModelKey(m.IdOne, m.IdTwo))) #>
)
)
I'm at the moment working on a Entity Framework Code First Wrapper for F#, and I've been wondering whether I should merge all my modules into just one.
Take a look at this:
module ManyNavProperty =
let withMany (cfg:ManyNavPropertyInfo<'a,'b>) = cfg.WithMany()
let withSeq expr (cfg:ManyNavPropertyInfo<'a,'b>) = cfg.WithSeq expr
let withList expr (cfg:ManyNavPropertyInfo<'a,'b>) = cfg.WithList expr
let withArray expr (cfg:ManyNavPropertyInfo<'a,'b>) = cfg.WithArray expr
let withOptional (cfg:ManyNavPropertyInfo<'a,'b>) = cfg.WithOptional()
let withOptionalProperty expr (cfg:ManyNavPropertyInfo<'a,'b>) = cfg.WithOptional expr
let withRequired (cfg:ManyNavPropertyInfo<'a,'b>) = cfg.WithRequired()
let withRequiredProperty expr (cfg:ManyNavPropertyInfo<'a,'b>) = cfg.WithRequiredProperty expr
module DependentNavProperty =
let hasForeignKey expr (cfg:DependentNavPropertyInfo<'a>) = cfg.HasForeignKey expr
module CascadableNavProperty =
let willCascadeOnDelete b (cfg:CascadableNavPropertyInfo) = cfg.WillCascadeOnDelete b
module EF =
let entity<'a when 'a : not struct> (cfg:DbModelBuilder) = EntityInfo<'a>(cfg.Entity<'a>())
let hasKey expr (cfg:EntityInfo<'a>) = cfg.HasKey expr
let hasSeq expr (cfg:EntityInfo<'a>) = cfg.HasSeq expr
let hasList expr (cfg:EntityInfo<'a>) = cfg.HasList expr
let hasArray expr (cfg:EntityInfo<'a>) = cfg.HasArray expr
let hasOptional expr (cfg:EntityInfo<'a>) = cfg.HasOptional expr
let hasRequired expr (cfg:EntityInfo<'a>) = cfg.HasRequried expr
let toTable s (cfg:EntityInfo<'a>) = cfg.ToTable s
that would require me to call the code like:
override x.OnModelCreating (mb:DbModelBuilder) =
let finished = ignore
let entity = EF.entity<Author> mb
entity
|> EF.hasSeq <# fun z -> z.Books #>
|> ManyNavProperty.withRequiredProperty <# fun z -> z.Author #>
|> DependentNavProperty.hasForeignKey <# fun z -> z.AuthorId #>
|> CascadableNavProperty.willCascadeOnDelete true
|> finished
Is the use of so many modules confusing for the user? - Should I place them all into one module, or will that destroy the overview for the user?
An example where all functions are placed in same Module:
override x.OnModelCreating (mb:DbModelBuilder) =
let finished = ignore
let entity = EF.entity<Author> mb
entity
|> EF.hasSeq <# fun z -> z.Books #>
|> EF.withRequiredProperty <# fun z -> z.Author #>
|> EF.hasForeignKey <# fun z -> z.AuthorId #>
|> EF.willCascadeOnDelete true
|> finished
This is a tricky design question.
Generally, F# libraries tend to group functions that work with a type (e.g. list<T> or seq<T>) in a separate module with the name same as the type (e.g. List.xyz and Seq.xyz). This works well for common types, because the users learn how to find the functions.
However, for less common types (e.g. your own library), it may be a bit difficult to discover these modules (especially if the type is not created explicitly (but is obtained as a result of some call). Having one module name and then use . to explore it is quite powerful.
I would probably suggest using nested modules. Something like:
module EF =
// common functions
module Cascadable =
// CascadableNavProperty functions
module Many =
// etc.
The users could then find everything by starting with EF. They'd write something like:
entity
|> EF.hasSeq <# fun z -> z.Books #>
|> EF.Many.withRequiredProperty <# fun z -> z.Author #>
|> EF.Dependent.hasForeignKey <# fun z -> z.AuthorId #>
|> EF.Cascadable.willCascadeOnDelete true
|> finished