I'm trying to query a list of events each containg a list of attendees.
I'm using a query expression on an EF db context.
I've managed to do it like this:
let events =
query {
for event in db.Events do
leftOuterJoin ea in db.EventAttendances
on (event.Id = ea.EventId) into result
for ea in result do
select ({|Id = event.Id
Description = event.Description
DateTime = event.DateTime
|}, {| FirstName = ea.Member.FirstName
LastName = ea.Member.LastName
IsAttending = ea.IsAttending |})
}
|> Seq.toList
|> List.groupBy (fun (e, _) -> e)
|> List.map (fun (e, attendees) ->
{ Id = e.Id
Description = e.Description
DateTime = e.DateTime
Attendees = attendees
|> Seq.map(fun (_, a) ->
{ FirstName = a.FirstName
LastName = a.LastName
IsAttending = ea.IsAttending })
|> Seq.toList
} )
I'm used to doing this with EF, so the whole groupBy thing seems a little cumbersome to me.
Is there a smarter way to do this?
What you're doing is pretty much exactly what groupJoin does.
let events =
query {
for event in db.Events do
groupJoin ea in db.EventAttendances on (event.Id = ea.EventId) into result
select (event, query { for ea in result do
select { FirstName = ea.Member.FirstName
LastName = ea.Member.LastName
IsAttending = ea.IsAttending } })
}
|> Seq.map (fun (e, attendees) ->
{ Id = e.Id
Description = e.Description
DateTime = e.DateTime
Attendees = attendees |> Seq.toList })
|> Seq.toList
Related
Can someone tell me how I can update a subitem in a nested record?
I want to set isSelected to true for the Item with value = "B"
type MyItem = {isSelected:bool; value:string}
type MyModel = {list:MyItem list}
let a = {isSelected = false; value = "A"}
let b = {isSelected = false; value = "B"}
let c = {isSelected = false; value = "C"}
let m = {list = [a;b;c]}
let m2 = { m with list = { m.list with ??? = { ??? }}}
I will not use mutable data structures.
Immutability is great but when dealing with nested immutable structures it can get a bit hairy. Especially if it's deeply nested.
One way to deal with this is so called Lenses.
So I increased the nesting level of the example a bit so that the value of lenses are more visible.
module Lenses =
// This lens is a pair of function, a getter that get's inner value of an object
// and a setter that sets the inner value of an object
// The cool thing is that a lens is composable meaning we can create a lens
// that allows us to get and set a deeply nested property succinctly
type Lens<'O, 'I> = L of ('O -> 'I)*('I -> 'O -> 'O)
let lens (g : 'O -> 'I) (s : 'I -> 'O -> 'O) = L (g, s)
// Gets an inner value
let get (L (g, _)) o = g o
// Sets an inner value
let set (L (_, s)) i o = s i o
// Updates an inner value given an updater function that sees the
// inner value and returns a new value
let over (L (g, s)) u o = s (u (g o)) o
// Compose two lenses into one, allows for navigation into deeply nested structures
let compose (L (og, os)) (L (ig, is)) =
let g o = ig (og o)
let s i o = os (is i (og o)) o
L (g, s)
type Lens<'O, 'I> with
static member (-->) (o, i) = compose o i
open Lenses
// I made the model a bit more complex to show benefit of lenses
type MySelection =
{
isSelected: bool
}
// Define a lens that updates the property, this code can potentially be generated
// Scala does this with macros, in F# there are other possibilities
static member isSelectedL : Lens<MySelection, bool> = lens (fun o -> o.isSelected) (fun i o -> { o with isSelected = i })
type MyValue =
{
value: string
}
static member valueL : Lens<MyValue, string> = lens (fun o -> o.value) (fun i o -> { o with value = i })
type MyItem =
{
selection : MySelection
value : MyValue
}
static member selectionL : Lens<MyItem, MySelection> = lens (fun o -> o.selection) (fun i o -> { o with selection = i })
static member valueL : Lens<MyItem, MyValue> = lens (fun o -> o.value ) (fun i o -> { o with value = i })
type MyModel =
{
list: MyItem list
}
static member listL : Lens<MyModel, MyItem list> = lens (fun o -> o.list) (fun i o -> { o with list = i })
[<EntryPoint>]
let main argv =
// Define example model
let a = {selection = {isSelected = false}; value = {value = "A"}}
let b = {selection = {isSelected = false}; value = {value = "B"}}
let c = {selection = {isSelected = false}; value = {value = "C"}}
let m = {list = [a;b;c]}
// Print it
printfn "%A" m
// Update the model
let m2 =
let mapper (v : MyItem) =
// Grabs the nest value using lens composition
let nestedValue = v |> get (MyItem.valueL --> MyValue.valueL)
let isSelected = nestedValue = "B"
// Set the nested isSelected using lens composition
v |> set (MyItem.selectionL --> MySelection.isSelectedL) isSelected
// Maps nested list property
m |> over MyModel.listL (List.map mapper)
printfn "%A" m2
0
Use List.map:
let m2 =
{ m with list =
List.map (fun item ->
if item.value = "B" then
{ item with isSelected = true }
else
item)
m.list
}
This will create a new list where every item is the same as before, except the one we want to "update" because we replace that with a new item where isSelected is true.
How do I get items from an RSS feed using .Net Core?
The following code doesn't appear to work:
open Microsoft.SyndicationFeed
open Microsoft.SyndicationFeed.Rss
[<Test>]
let ``Get links from iTunes RSS Feed`` () =
let url = "http://www.pwop.com/feed.aspx?show=dotnetrocks&filetype=master&tags=F%23"
use reader = XmlReader.Create(url)
let feedReader = RssFeedReader(reader)
let mutable linkTemplate = {
Title= ""
Url= ""
}
let links =
async {
let links = Collections.Generic.List<Link>()
while feedReader.Read() |> Async.AwaitTask |> Async.RunSynchronously do
match feedReader.ElementType with
| SyndicationElementType.Link ->
let item = feedReader.ReadLink() |> Async.AwaitTask |> Async.RunSynchronously
let link = { linkTemplate with Title= item.Title; Url= item.Uri.AbsolutePath }
links.Add(link)
| _ -> ()
return links
} |> Async.RunSynchronously
reader.Close()
System.Diagnostics.Debug.WriteLine(links.[0].Title)
links.[0].Title |> should not' (equal "")
Specifically, items are read but there's no actual data after the read.
I used the XElement class as recommended:
[<Test>]
let ``Get links from iTunes RSS Feed`` () =
let toLink (item:XElement) = {
Id = -1
ProfileId = "to be derived..."
Title= item.Element(XName.Get("title")) |> string
Url= item.Element(XName.Get("link")) |> string
Description = item.Element(XName.Get("description")) |> string
ContentType= Podcast |> contentTypeToString
Topics = []
IsFeatured= false
}
let baseAddress = "http://www.pwop.com/"
let url = "feed.aspx?show=dotnetrocks&filetype=master&tags=F%23"
use client = httpClient baseAddress
let response = client.GetAsync(url) |> Async.AwaitTask
|> Async.RunSynchronously
let links =
if response.IsSuccessStatusCode
then let text = response.Content.ReadAsStringAsync() |> Async.AwaitTask |> Async.RunSynchronously
XElement.Parse(text).Descendants(XName.Get("item")) |> Seq.toList |> List.map toLink
else []
links |> List.isEmpty |> should equal false
I want to sort items of a class and collect them in Collection-Classes that beside a List-Member also contain further information that are necessary for the sorting process.
The following example is a a very simplified example for my problem. Although it doesn't make sense, I hope it still can help to understand my Question.
type ItemType = Odd|Even //realworld: more than two types possible
type Item(number) =
member this.number = number
member this.Type = if (this.number % 2) = 0 then Even else Odd
type NumberTypeCollection(numberType:ItemType , ?items:List<Item>) =
member this.ItemType = numberType
member val items:List<Item> = defaultArg items List.empty<Item> with get,set
member this.append(item:Item) = this.items <- item::this.items
let addToCollection (collections:List<NumberTypeCollection>) (item:Item) =
let possibleItem =
collections
|> Seq.where (fun c -> c.ItemType = item.Type) //in my realworld code, several groups may be returned
|> Seq.tryFind(fun _ -> true)
match possibleItem with
|Some(f) -> f.append item
collections
|None -> NumberTypeCollection(item.Type, [item]) :: collections
let rec findTypes (collections:List<NumberTypeCollection>) (items:List<Item>) =
match items with
| [] -> collections
| h::t -> let newCollections = ( h|> addToCollection collections)
findTypes newCollections t
let items = [Item(1);Item(2);Item(3);Item(4)]
let finalCollections = findTypes List.empty<NumberTypeCollection> items
I'm unsatisfied with the addToCollection method, since it requires the items in NumberTypeCollection to be mutual. Maybe there are further issues.
What can be a proper functional solution to solve this issue?
Edit: I'm sorry. May code was too simplified. Here is a little more complex example that should hopefully illustrate why I chose the mutual class-member (although this could still be the wrong decision):
open System
type Origin = Afrika|Asia|Australia|Europa|NorthAmerika|SouthAmerica
type Person(income, taxrate, origin:Origin) =
member this.income = income
member this.taxrate = taxrate
member this.origin = origin
type PersonGroup(origin:Origin , ?persons:List<Person>) =
member this.origin = origin
member val persons:List<Person> = defaultArg persons List.empty<Person> with get,set
member this.append(person:Person) = this.persons <- person::this.persons
//just some calculations to group people into some subgroups
let isInGroup (person:Person) (personGroup:PersonGroup) =
let avgIncome =
personGroup.persons
|> Seq.map (fun p -> float(p.income * p.taxrate) / 100.0)
|> Seq.average
Math.Abs ( (avgIncome / float person.income) - 1.0 ) < 0.5
let addToGroup (personGroups:List<PersonGroup>) (person:Person) =
let possibleItem =
personGroups
|> Seq.where (fun p -> p.origin = person.origin)
|> Seq.where (isInGroup person)
|> Seq.tryFind(fun _ -> true)
match possibleItem with
|Some(f) -> f.append person
personGroups
|None -> PersonGroup(person.origin, [person]) :: personGroups
let rec findPersonGroups (persons:List<Person>) (personGroups:List<PersonGroup>) =
match persons with
| [] -> personGroups
| h::t -> let newGroup = ( h|> addToGroup personGroups)
findPersonGroups t newGroup
let persons = [Person(1000,20, Afrika);Person(1300,22,Afrika);Person(500,21,Afrika);Person(400,20,Afrika)]
let c = findPersonGroups persons List.empty<PersonGroup>
What I may need to emphasize: There can be several different groups with the same origin.
Tomas' solution using groupby is the optimal approach if you want to generate your collections only once, it's a simple and concise.
If you want to be able to add/remove items in a functional, referentially transparent style for this type of problem, I suggest you move away from seq and start using Map.
You have a setup which is fundamentally dictionary-like. You have a unique key and a value. The functional F# equivalent to a dictionary is a Map, it is an immutable data structure based on an AVL tree. You can insert, remove and search in O(log n) time. When you append/remove from the Map, the old Map is maintained and you receive a new Map.
Here is your code expressed in this style
type ItemType =
|Odd
|Even
type Item (number) =
member this.Number = number
member this.Type = if (this.Number % 2) = 0 then Even else Odd
type NumTypeCollection = {Items : Map<ItemType, Item list>}
/// Functions on NumTypeCollection
module NumberTypeCollection =
/// Create empty collection
let empty = {Items = Map.empty}
/// Append one item to the collection
let append (item : Item) numTypeCollection =
let key = item.Type
match Map.containsKey key numTypeCollection.Items with
|true ->
let value = numTypeCollection.Items |> Map.find key
let newItems =
numTypeCollection.Items
|> Map.remove key
|> Map.add key (item :: value) // append item
{Items = newItems }
|false -> {Items = numTypeCollection.Items |> Map.add key [item]}
/// Append a list of items to the collections
let appendList (item : Item list) numTypeCollection =
item |> List.fold (fun acc it -> append it acc) numTypeCollection
Then call it using:
let items = [Item(1);Item(2);Item(3);Item(4)]
let finalCollections = NumberTypeCollection.appendList items (NumberTypeCollection.empty)
If I understand your problem correctly, you're trying to group the items by their type. The easiest way to do that is to use the standard library function Seq.groupBy. The following should implement the same logic as your code:
items
|> Seq.groupBy (fun item -> item.Type)
|> Seq.map (fun (key, values) ->
NumberTypeCollection(key, List.ofSeq values))
Maybe there are further issues.
Probably. It's difficult to tell, since it's hard to detect the purpose of the OP code... still:
Why do you even need an Item class? Instead, you could simply have a itemType function:
let itemType i = if i % 2 = 0 then Even else Odd
This function is referentially transparent, which means that you can replace it with its value if you wish. That makes it as good as a property getter method, but now you've already saved yourself from introducing a new type.
Why define a NumberTypeCollection class? Why not a simple record?
type NumberTypeList = { ItemType : ItemType; Numbers : int list }
You can implement addToCollection like something like this:
let addToCollection collections i =
let candidate =
collections
|> Seq.filter (fun c -> c.ItemType = (itemType i))
|> Seq.tryHead
match candidate with
| Some x ->
let x' = { x with Numbers = i :: x.Numbers }
collections |> Seq.filter ((<>) x) |> Seq.append [x']
| None ->
collections |> Seq.append [{ ItemType = (itemType i); Numbers = [i] }]
Being immutable, it doesn't mutate the input collections, but instead returns a new sequence of NumberTypeList.
Also notice the use of Seq.tryHead instead of Seq.tryFind(fun _ -> true).
Still, if you're attempting to group items, then Tomas' suggestion of using Seq.groupBy is more appropriate.
I am new to F#. I am trying to use List.fold to help me generate a list of categories and sub-categories based on their Id and ParentId fields. It seems I probably made this code more complex than need be, as I'm getting the stackoverflow error. What am I doing wrong or missing? All related feedback is appreciated.
// types
type CategoryStructure = {
Id: ValidString;
ParentId: ValidString;
Name: ValidString;
Abbreviation: ValidString;
Description: ValidString;
SapId: ValidString;
Section: ValidString;
SectionPosition: ValidString
}
type DynamicCategories = {
Category: CategoryStructure;
SubCategories: seq<DynamicCategories>
}
// this is the function that produces the stack overflow error
let rec private structureCategories (fullList: CategoryStructure list)
(list: CategoryStructure list) =
List.fold (fun acc elem ->
// get all categories and details
let categories = fullList
let mainAcc =
[
for row in categories do
if row = elem
then
let subs =
List.fold (fun acc' elem' ->
if row.Id = elem'.ParentStructureId
then
let foundSubCategory =
{
Category = elem';
SubCategories = structureCategories fullList list |> Seq.ofList
}
foundSubCategory :: acc'
else acc'
) List.empty<DynamicCategories> categories
|> Seq.ofList
yield{
Category = elem;
SubCategories = subs
}
]
mainAcc # acc
) List.empty<DynamicCategories> list
// this function gets the initial parent categories and calls the above function
let getStructuredCategories () =
let categories = allCategoriesAndDetails () |> List.ofSeq
[
for row in categories do
if row.ParentStructureId = NotValid
then yield row
] |> structureCategories categories |> Seq.ofList
You keep calling structureCategories with the same arguments - fullList and list. Since arguments are same, it proceeds to do exactly the same thing as on the previous pass, and ends up calling itself again, with the same arguments. And so on.
This is unbounded recursion ("unbounded" here means "doesn't know when to stop recurring"), and it is also not "tail recursion", so quite naturally, it causes stack overflow.
If you want to turn the flat list into a tree-like structure, you could do a bit simpler than this:
let getChildren fullList parentId = fullList |> List.filter (fun c -> c.ParentId = parentId)
let rec toTree fullList root =
{ Category = root;
SubCategories =
getChildren fullList root.Id
|> List.map (toTree fullList) }
With this, you'll be left with two problems, which I don't know how to solve without knowing more about your requirements:
This will still cause stack overflow if the original list happens to have cycles.
You need to decide who the root(s) of the tree is (or are). Intuitively, this would be indicated via "empty" ParentId, but it is unclear from your data structure what "empty" means.
And finally, this naive solution, while better than your original one, is still a bit slower than it needs to be. It iterates over the whole list once, and for every node does another pass to determine its children, resulting in overall complexity of O(N^2). This may be fine if you expect relatively small list, but not so fine for larger lists. In that case, I would first turn the list into a hashtable (keyed by ParentId) and then use that to find children instead of List.filter.
Thanks to Fyodor, I saw my mistake. He was dead on about calling the same arguments. I added this bit of code right before the foundSubCategory value:
let modifiedList = elem' :: List.empty<CategoryStructure>
and then called that value in the subsequent code:
let foundSubCategory =
{
Category = elem';
SubCategories = structureCategories fullList modifiedList |> Seq.ofList
}
This solved my issue, but now as Fyodor alluded to, I now have to refactor this into something more efficient.
UPDATE
With the insight that Fyodor pointed out this is the current state of my code, which replaces the original code:
let getStructuredCategories () =
let fullList = allCategoriesAndDetails ()
let parentList () =
allCategoriesAndDetails ()
|> Seq.filter (fun p -> p.ParentStructureId = NotValid)
let rec toTree (fullList': seq<CategoryStructure>) (parent: CategoryStructure) =
fullList'
|> Seq.filter (fun x -> x.ParentStructureId = parent.Id)
|> Seq.map (fun x ->
{
Category = x;
SubCategories =
toTree fullList' x
})
seq {
for row in parentList () do
yield {
Category = row;
SubCategories = toTree fullList row
}
}
I have a method which works fine:
member this.GetItems =
let db = dbSchema.GetDataContext()
let getQuery =
query {
for row in db.ItemsTable do
select row
}
getQuery
|> Seq.map (fun e -> new Item(e.ItemId, e.ItemName ))
|> Seq.toArray
It make the select query to database, transforms each record to object and returns an array of such objects.
I want to split this method to two. First one must be more general. It gets query and function to transform record to object.
Here is my code:
member private this.ExecuteSelectQuery(query, transform_function) =
let db = dbSchema.GetDataContext()
query
|> Seq.map transform_function
|> Seq.toArray
member this.GetItems =
let db = dbSchema.GetDataContext()
this.ExecuteSelectQuery
query {
for row in db.ItemsTable do
select row
}
(fun e -> new Item(e.ItemId, e.ItemName ))
But I get the errors:
In this expression required type 'a * ('b -> 'c) but there is a type QueryBuilder
This construction can be used only in the case of the method For
How can I fix it ?
Updated:
member private this.ExecuteSelectQuery query transform_function =
let db = dbSchema.GetDataContext()
query
|> Seq.map transform_function
|> Seq.toArray
member this.GetItems =
let db = dbSchema.GetDataContext()
this.ExecuteSelectQuery
query {
for row in db.ItemsTable do
select row
}
(fun e -> new Item(e.ItemId, e.ItemName ))
The member function ExecuteSelectQuery expects the arguments as a tuple, so you have to use parentheses.
member private this.ExecuteSelectQuery(query, transform_function) =
query
|> Seq.map transform_function
|> Seq.toArray
member this.GetItems =
use db = dbSchema.GetDataContext()
this.ExecuteSelectQuery (
query {
for row in db.ItemsTable do
select row
},
(fun e -> new Item(e.ItemId, e.ItemName )))
Please consider also the use keyword for binding of DataContext instance to correctly implement the disposable pattern. I've done it in my example.