How do I walk the Odata entity model graph? - f#

#r "nuget: Microsoft.OData.Edm"
open Microsoft.OData.Edm
open System.Xml
let reader = XmlReader.Create("metadata.xml")
let success, edmModel, errors = Csdl.CsdlReader.TryParse(reader)
let container = edmModel.EntityContainer
let elements = edmModel.SchemaElements |> Seq.cast<IEdmSchemaElement> |> Array.ofSeq
for elem in elements do
printfn $"1 {elem.Name} : {elem.SchemaElementKind} : {elem.Location()}"
let entitySets = container.EntitySets() |> Seq.cast<IEdmEntitySet> |> Array.ofSeq
for elem in entitySets do
let cElems = elem.Container.Elements |> Seq.cast<IEdmEntityContainerElement> |> Array.ofSeq
printfn $" 2 {elem.Name} : {elem.ContainerElementKind}"
for celem in cElems do
printfn $" 3 {celem.Name} : {celem.ContainerElementKind}"
This is the extent to which I can walk an odata entity model graph with Microsoft.OData.Edm.
The results of the level 2 and 3 prints are the same. I want to access the keys and properties of the entity types.
I can always switch to walking the XML graph, but using a maintained odata library seems like the right thing to do.

I think the trick here is to downcast the elements to IEdmEntityType where possible:
let entTypes =
edmModel.SchemaElements
|> Seq.choose (function
| :? IEdmEntityType as entType -> Some entType
| _ -> None)
for entType in entTypes do
printfn "%s" entType.Name
for prop in entType.DeclaredProperties do
printfn " %s %s" prop.Name (prop.Type.ShortQualifiedName())
Output will look something like:
Product
ID Int32
Name String
Description String
ReleaseDate DateTimeOffset
DiscontinuedDate DateTimeOffset
Rating Int16
Price Double
Categories
Supplier ODataDemo.Supplier
ProductDetail ODataDemo.ProductDetail
FeaturedProduct
Advertisement ODataDemo.Advertisement
ProductDetail
ProductID Int32
Details String
Product ODataDemo.Product

Related

A better way to use Seq.fold in F#

I wrote a small console app that update a Type record without using any mutable variable. If that looks simple for seasoned functional programmers, it was quite a hard work for me.
It works, but there is one thing I am not happy with. But before that, let's start with the code:
open System
//------------------------------------------------------------------------------------
// Type, no data validation to keep it simple
//------------------------------------------------------------------------------------
[<StructuredFormatDisplay("{FirstName} {LastName} is a {Age} year old {Sex}")>]
type Student = {
FirstName: string
LastName : string
Sex : char
Age: int
}
//------------------------------------------------------------------------------------
// I/O functions
//------------------------------------------------------------------------------------
let getConsoleChar message =
printf "\n%s" message
Console.ReadKey().KeyChar
let getConsoleString message =
printf "\n%s" message
Console.ReadLine()
let getConsoleInt = getConsoleString >> Int32.Parse //no tryparse to keep it simple, I'm sure you can type an integer
let isValidCommand command = [ 'f'; 'l'; 's'; 'a'; 'x'] |> List.contains command
let isStopCommand = (=) 'x'
let processCommand student command =
match command with
| 'f' -> { student with FirstName = (getConsoleString "First Name: ")}
| 'l' -> { student with LastName = (getConsoleString "Last Name: ")}
| 's' -> { student with Sex = (getConsoleChar "Sex: ")}
| 'a' -> { student with Age = (getConsoleInt "Age: ")}
| 'x' -> student
| _ -> failwith "You've just broken the Internet, theorically you cannot be here"
//------------------------------------------------------------------------------------
// Program
//------------------------------------------------------------------------------------
let initialStudent = {
FirstName = String.Empty
LastName = String.Empty
Sex = Char.MinValue
Age = 0
}
let commands = seq {
while true do
yield getConsoleChar "Update [f]irst name, [l]ast name, [s]ex, [a]ge or e[x]it: " }
let finalStudent =
commands
|> Seq.filter isValidCommand
|> Seq.takeWhile (not << isStopCommand)
|> Seq.map (fun cmd -> (initialStudent, cmd))
|> Seq.fold (fun student studentAndCommand -> processCommand student (snd studentAndCommand)) initialStudent
printfn "\n<<<< %A >>>>\n" finalStudent
My problem is with
|> Seq.map (fun cmd -> (initialStudent, cmd))
|> Seq.fold (fun student studentAndCommand -> processCommand student (snd studentAndCommand)) initialStudent
It looks bizarre to transform a sequence of char into a Student*char to be able to plug it with aSeq.fold. Also, if using the initialStudent as a starting point for Seq.fold is logical, it feel weird to use it in the mapping transformation (I'm not sure anyone would understand the logic if this code was pushed in prod).
Is there a better way to treat the sequence of commands or is this code standard and acceptable in the functional world?
You can get rid of the map and simplify the fold considerably:
commands
|> Seq.filter isValidCommand
|> Seq.takeWhile (not << isStopCommand)
|> Seq.fold processCommand initialStudent
I'm not sure why you thought you had to map the seq<char> into a seq<Student * char> to begin with. Since you immediately use snd to extract the char from the tuple, undoing the map, the tuples' first elements are totally ignored. Much cleaner to simply avoid creating tuples in the first place

iterating JArray without for loop in F#

I don't want to use this for loop for iterating the JArray. Is there any other method which can replace this for loop?
let tablesInJson = jsonModel.["tables"] :?> JArray //Converting JOject into JArray
for table in tablesInJson do
let TableName = table.["name"] :?> JValue
let columns = table.["columns"] :?> JArray
for col in columns do
let name = col.["name"] :?> JValue
let types = col.["type"] :?> JValue
let length = col.["length"] :?> JValue
let Result_ = sqlTableInfos
|> List.tryFind (fun s -> s.TableName = TableName.ToString() && s.ColumnName = name.ToString())
if Result_ = Unchecked.defaultof<_> then
printfn "is null"
else
printfn "not null"
If you want to iterate over a collection and perform an imperative operation than using for loop is the idiomatic way of doing this in F# and you should just use that. After all, for is an F# language construct! There is a reason why it exists and the reason is that it lets you easily write code that iterates over a collection and does something for each element!
There are cases where for loop is not a good fit. For example, if you wanted to turn a collection of columns into a new collection with information about the tables. Then you could use Seq.map:
let tableInfos = columns |> Seq.map (fun col ->
let name = col.["name"] :?> JValue
let types = col.["type"] :?> JValue
let length = col.["length"] :?> JValue
let result = sqlTableInfos |> List.tryFind (fun s ->
s.TableName = TableName.ToString() && s.ColumnName = name.ToString())
if result = Unchecked.defaultof<_> then None
else Some result)
This looks like something you might be trying to do - but it is difficult to say. Your question does not say what is the problem that you are actually trying to solve.
Your example with printfn is probably misleading, because if you actually just want to print, then for loop is the best way of doing that.
You can use the Seq module to perform sequence-processing operations over the JArray. In your case, I think I would probably do this for the second for loop (over the columns), but not for the outer loop. The reason being, if you factor the code in the inner-loop out to a function, then you can use pipelining and partial application to clean up the code a bit:
open Newtonsoft.Json
open Newtonsoft.Json.Linq
type SqlTableInfo = {TableName: string; ColumnName: string}
let tablesInJson = JArray()
let sqlTableInfo = []
let tryFindColumn (tableName: JValue) (column: JToken) =
let columnName = column.["name"] |> unbox<JValue>
if sqlTableInfo |> List.exists (fun s -> s.TableName = tableName.ToString() && s.ColumnName = columnName.ToString())
then printfn "Table %A, Column %A Found" tableName columnName
else printfn "Table %A, Column %A Found" tableName columnName
for table in tablesInJson do
let tableName = table.["name"] |> unbox<JValue>
table.["columns"]
|> unbox<JArray>
|> Seq.iter (tryFindColumn tableName)

parse log files with f#

I'm trying to parse data from iis log files.
Each row has a date that I need like this:
u_ex15090503.log:3040:2015-09-05 03:57:45
And a name and email address I need in here:
&actor=%7B%22name%22%3A%5B%22James%2C%20Smith%22%5D%2C%22mbox%22%3A%5B%22mailto%3AJames.Smith%40student.colled.edu%22%5D%7D&
I start off by getting the correct column like this. This part works fine.
//get the correct column
let getCol =
let line = fileReader inputFile
line
|> Seq.filter (fun line -> not (line.StartsWith("#")))
|> Seq.map (fun line -> line.Split())
|> Seq.map (fun line -> line.[7],1)
|> Seq.toArray
getCol
Now I need to parse the above and get the date, name, and email, but I'm having a hard time figuring out how to do that.
So far I have this, which gives me 2 errors(below):
//split the above column at every "&"
let getDataInCol =
let line = getCol
line
|> Seq.map (fun line -> line.Split('&'))
|> Seq.map (fun line -> line.[5], 1)
|> Seq.toArray
getDataInCol
Seq.map (fun line -> line.Split('&'))
the field constructor 'Split' is not defined
The errors:
Seq.map (fun line -> line.[5], 1)
the operator 'expr.[idx]' has been used on an object of indeterminate type based on information prior to this program point.
Maybe I'm going about this all wrong. I'm very new to f# so I apologize for the sloppy code.
Something like this would get the name and email. You'll still need to parse the date.
#r "Newtonsoft.Json.dll"
open System
open System.Text.RegularExpressions
open Newtonsoft.Json.Linq
let (|Regex|_|) pattern input =
let m = Regex.Match(input, pattern)
if m.Success then Some(List.tail [ for g in m.Groups -> g.Value ])
else None
type ActorDetails =
{
Date: DateTime
Name: string
Email: string
}
let parseActorDetails queryString =
match queryString with
| Regex #"[\?|&]actor=([^&]+)" [json] ->
let jsonValue = JValue.Parse(Uri.UnescapeDataString(json))
{
Date = DateTime.UtcNow (* replace with parsed date *)
Name = jsonValue.Value<JArray>("name").[0].Value<string>()
Email = jsonValue.Value<JArray>("mbox").[0].Value<string>().[7..]
}
| _ -> invalidArg "queryString" "Invalid format"
parseActorDetails "&actor=%7B%22name%22%3A%5B%22James%2C%20Smith%22%5D%2C%22mbox%22%3A%5B%22mailto%3AJames.Smith%40student.colled.edu%22%5D%7D&"
val it : ActorDetails = {Date = 11/10/2015 9:14:25 PM;
Name = "James, Smith";
Email = "James.Smith#student.colled.edu";}

F# custom type provider: "Container type already set" error

Recently I attended a tutorial by Keith Battochi on type providers, in which he introduced a variant of the MiniCsv type provider in the MSDN tutorial. Unfortunately, my laptop wasn't available, so I had to write down the code by hand as well as I could. I believe I've recreated the type provider, but I'm getting
error FS3033: The type provider 'CsvFileTypeProvider+CsvFileTypeProvider' reported an error: container type for 'CsvFileProvider.Row' was already set to 'CsvFileProvider.CsvFile,Filename="events.csv"
When I look at the code, I can't see how I'm adding the Row type to the container twice (or to some other container). Removing selected lines of the code doesn't help.
Here's how I'm calling the code in fsi:
#r "CsvFileTypeProvider.dll"
open CsvFileProvider
let eventInfos = new CsvFile<"events.csv">() ;;
And here's the code itself:
module CsvFileTypeProvider
open Samples.FSharp.ProvidedTypes
open Microsoft.FSharp.Core.CompilerServices
let getType str =
if System.DateTime.TryParse(str, ref Unchecked.defaultof<_>) then
typeof<System.DateTime>, (fun (str:Quotations.Expr) -> <## System.DateTime.Parse(%%str) ##>)
elif System.Int32.TryParse(str, ref Unchecked.defaultof<_>) then
typeof<System.Int32>, (fun (str:Quotations.Expr) -> <## System.Int32.Parse(%%str) ##>)
elif System.Double.TryParse(str, ref Unchecked.defaultof<_>) then
typeof<System.Double>, (fun (str:Quotations.Expr) -> <## System.Double.Parse(%%str) ##>)
else
typeof<string>, (fun (str:Quotations.Expr) -> <## %%str ##>)
[<TypeProvider>]
type CsvFileTypeProvider() =
inherit TypeProviderForNamespaces()
let asm = typeof<CsvFileTypeProvider>.Assembly
let ns = "CsvFileProvider"
let csvFileProviderType = ProvidedTypeDefinition(asm, ns, "CsvFile", None)
let parameters = [ProvidedStaticParameter("Filename", typeof<string>)]
do csvFileProviderType.DefineStaticParameters(parameters, fun tyName [| :? string as filename |] ->
let rowType = ProvidedTypeDefinition(asm, ns, "Row", Some(typeof<string[]>))
let lines = System.IO.File.ReadLines(filename) |> Seq.map (fun line -> line.Split(','))
let columnNames = lines |> Seq.nth 0
let resultTypes = lines |> Seq.nth 1 |> Array.map getType
for idx in 0 .. (columnNames.Length - 1) do
let col = columnNames.[idx]
let ty, converter = resultTypes.[idx]
let prop = ProvidedProperty(col, ty)
prop.GetterCode <- fun [row] -> converter <## (%%row:string[]).[idx] ##>
rowType.AddMember(prop)
let wholeFileType = ProvidedTypeDefinition(asm, ns, tyName, Some(typedefof<seq<_>>.MakeGenericType(rowType)))
wholeFileType.AddMember(rowType)
let ctor = ProvidedConstructor(parameters = []) // the *type* is parameterized but the *constructor* gets no args
ctor.InvokeCode <- //given the inputs, what will we get as the outputs? Now we want to read the *data*, skip the header
fun [] -> <## System.IO.File.ReadLines(filename) |> Seq.skip 1 |> Seq.map (fun line -> line.Split(',')) ##>
wholeFileType.AddMember(ctor)
wholeFileType
)
do base.AddNamespace(ns, [csvFileProviderType])
[<TypeProviderAssembly>]
do()
Thanks for any help!
you need to use another constructor when defining 'Row' type. Existing ProvidedTypeDefinition type exposes two constructors:
(assembly, namespace, typename, base type) - defines top level type whose container is namespace.
(typename, basetype) - defines nested type that should be added to some another type.
Now Row type is defined using first constructor so it is treated as top level type. Exception is raised when this type is later added to wholeFileType as nested.

How to Get the F# Name of a Module, Function, etc. From Quoted Expression Match

I continue to work on a printer for F# quoted expressions, it doesn't have to be perfect, but I'd like to see what is possible. The active patterns in Microsoft.FSharp.Quotations.Patterns and Microsoft.FSharp.Quotations.DerivedPatterns used for decomposing quoted expressions will typically provide MemberInfo instances when appropriate, these can be used to obtain the name of a property, function, etc. and their "declaring" type, such as a module or static class. The problem is, I only know how to obtain the CompiledName from these instances but I'd like the F# name. For example,
> <# List.mapi (fun i j -> i+j) [1;2;3] #> |> (function Call(_,mi,_) -> mi.DeclaringType.Name, mi.Name);;
val it : string * string = ("ListModule", "MapIndexed")
How can this match be rewritten to return ("List", "mapi")? Is it possible?
FYI, here is my final polished solution from Stringer Bell and pblasucci's help:
let moduleSourceName (declaringType:Type) =
FSharpEntity.FromType(declaringType).DisplayName
let methodSourceName (mi:MemberInfo) =
mi.GetCustomAttributes(true)
|> Array.tryPick
(function
| :? CompilationSourceNameAttribute as csna -> Some(csna)
| _ -> None)
|> (function | Some(csna) -> csna.SourceName | None -> mi.Name)
//usage:
let sourceNames =
<# List.mapi (fun i j -> i+j) [1;2;3] #>
|> (function Call(_,mi,_) -> mi.DeclaringType |> moduleSourceName, mi |> methodSourceName);
You can use F# powerpack for that purpose:
open Microsoft.FSharp.Metadata
...
| Call(_, mi, _) ->
let ty = Microsoft.FSharp.Metadata.FSharpEntity.FromType(mi.DeclaringType)
let name = ty.DisplayName // name is List
However, I don't think if it's possible to retrieve function name with powerpack.
Edit:
As hinted by pblasucci, you can use CompilationSourceName attribute for retrieving source name:
let infos = mi.DeclaringType.GetMember(mi.Name)
let att = infos.[0].GetCustomAttributes(true)
let fName =
(att.[1] :?> CompilationSourceNameAttribute).SourceName // fName is mapi

Resources