Fork me on GitHub

Code Snippet:

 1 #if INTERACTIVE
 2 #r "System.Data.Services.Client"
 3 #r "FSharp.Data.TypeProviders"
 4 #endif
 5 
 6 open System
 7 open System.Net
 8 open System.Data.Services.Client
 9 open Microsoft.FSharp.Data
10 
11 [<Literal>]
12 let url = @"https://demo.crm4.dynamics.com/XRMServices/2011/OrganizationData.svc/"
13 
14 [<Literal>] // "%TMP%/odata/OrganizationData.csdl"
15 let csdl = __SOURCE_DIRECTORY__  + @"/odata/OrganizationData.csdl" 
16 
17 type Xrm = 
18     TypeProviders.ODataService<
19         ServiceUri = url,
20         LocalSchemaFile = csdl,
21         ForceUpdate = false>
22 
23 let ctx = Xrm.GetDataContext()
24 
25 // To be used when writing JavaScript with OData
26 ctx.DataContext.SendingRequest.Add (
27     fun eventArgs -> printfn "-Url: %A" eventArgs.Request.RequestUri)
28 ctx.DataContext.SendingRequest.Add (
29     fun eventArgs -> printfn "-Query: %s" eventArgs.Request.RequestUri.Query)
30 
31 // Remember to "pipe" to a Sequence, in order to evaluate the Linq Query:
32 query { for a in ctx.AccountSet do
33         where (a.AccountNumber = "42")
34         select (a.AccountNumber, a.AccountId)
35         skip 5
36         take 1} 
37 |> Seq.map id

Code output:

val url : string =
  "https:demo.crm4.dynamics.com/XRMServices/2011/OrganizationData.svc/"
val csdl : string =
  "C:\Users\rsm\AppData\Local\Temp\odata\OrganizationData.csdl"
type Xrm =
  class
    static member GetDataContext : unit -> Xrm.ServiceTypes.SimpleDataContextTypes.demoContext
     + 1 overload
    nested type ServiceTypes
  end
val ctx : Xrm.ServiceTypes.SimpleDataContextTypes.demoContext
val it : unit = ()
-Uri: https:demo.crm4.dynamics.com/XRMServices/2011/OrganizationData.svc/AccountSet()?$filter=AccountNumber eq '42'&$skip=5&$top=1&$select=AccountNumber,AccountId
-Query: $filter=AccountNumber eq '42'&$skip=5&$top=1&$select=AccountNumber,AccountId
val it : seq<string * Guid> = seq []

Remark:

The reason LocalSchemaFile = csdl and ForceUpdate = false are set to static values are because Microsoft still doesn’t allow us to use OData from a server side context (we still need to login to CRM Online with a browser that supports JavaScript). If anybody have a hint on how to access the CSDL service from a .NET application, please write a comment below.

The point is that even though there is no data returned in the .NET application from CRM Online, it doesn’t matter as we just want to use the queries (nicely written in Linq with intellisense and type-safety) outputted from fun eventArgs -> printfn "-Query: %s" eventArgs.Request.RequestUri.Query for our JavaScript code in combination with the official SDK.REST.js library:

1 SDK.REST.retrieveMultipleRecords(
2   "Account",
3   "$filter=AccountNumber eq '42'&$skip=5&$top=1&$select=AccountNumber,AccountId", // query
4   function (results) {
5     // Do stuff
6   },
7   errorHandler,
8   onCompleteHandler
9 );

The current way it’s done (and built on @deprecated technologies) is just not very handy (and has never been) as it requires to expand your current CRM tenant with a 3rd party managed solution:

CRM 2011 OData Query Designer

References:

Code Snippet:

 1 let NOT = function | 1 -> 0 | _ -> 1
 2 let AND x y = match x,y with | (1,1) -> 1 | _ -> 0
 3 let OR  x y = match x,y with | (0,0) -> 0 | _ -> 1
 4 let XOR x y = match x,y with | (1,1) | (0,0) -> 0 | _ -> 1
 5 let NAND x y = (x,y) ||> AND |> NOT
 6 let NOR  x y = (x,y) ||> OR  |> NOT
 7 let XNOR x y = (x,y) ||> XOR |> NOT
 8 
 9 let HALFADDER x y = (x,y) ||> AND,(x,y) ||> XOR
10 let FULLADDER x y z =
11   let c,s   = (x,y) ||> HALFADDER
12   let c',s' = (z,s) ||> HALFADDER
13   (c,c') ||> OR, s'
14 
15 let itb n =
16   System.Convert.ToString(0+n, 2).PadLeft(32,'0')
17   |> Seq.map string |> Seq.map int |> Seq.toList
18 
19 let bti (ls:int list) =
20   ls |> List.map string |> List.reduce (+)
21      |> fun x -> System.Convert.ToInt32(x,2)
22 
23 let add x y =
24   (x |> itb , y |> itb)
25   ||> List.zip
26   |> List.rev
27   |> List.fold(
28     fun (c,zs) (x,y) -> (x,y,c) |||> FULLADDER |> fun (c',z) -> c',z::zs) (0,[])
29   |> fun (x,ys) -> ys |> bti
30 
31 // Eight cases:
32 (0,0,0) |||> FULLADDER;;
33 (1,0,0) |||> FULLADDER;;
34 (0,1,0) |||> FULLADDER;;
35 (1,1,0) |||> FULLADDER;;
36 (0,0,1) |||> FULLADDER;;
37 (1,0,1) |||> FULLADDER;;
38 (0,1,1) |||> FULLADDER;;
39 (1,1,1) |||> FULLADDER;;
40 
41 // Examples taken from 'Domino Addition - Numberphile'
42 (42,17) ||> add;;
43 (55,27) ||> add;

Code output:

val NOT : _arg1:int -> int
val AND : x:int -> y:int -> int
val OR : x:int -> y:int -> int
val XOR : x:int -> y:int -> int
val NAND : x:int -> y:int -> int
val NOR : x:int -> y:int -> int
val XNOR : x:int -> y:int -> int
val HALFADDER : x:int -> y:int -> int * int
val FULLADDER : x:int -> y:int -> z:int -> int * int
val itb : n:int -> int list
val bti : ls:int list -> int
val add : x:int -> y:int -> int

> val it : int * int = (0, 0)
> val it : int * int = (0, 1)
> val it : int * int = (0, 1)
> val it : int * int = (1, 0)
> val it : int * int = (0, 1)
> val it : int * int = (1, 0)
> val it : int * int = (1, 0)
> val it : int * int = (1, 1)

> val it : int = 59
> val it : int = 82

References:

Code Snippet:

 1 type SeqMonad() =
 2   member t.Bind(m,f) = Seq.concat(Seq.map f m)
 3   member t.Return v = seq{ yield v }
 4 let seqMonad = SeqMonad()
 5 
 6 let permutations ls = 
 7   let rec insertions x = function
 8     | []             -> [[x]]
 9     | (y :: ys) as l -> (x::l)::(List.map (fun x -> y::x) (insertions x ys))
10   let rec permutations' = function
11     | []      -> seq [ [] ]
12     | x :: xs -> Seq.concat (Seq.map (insertions x) (permutations' xs))
13   ls |> permutations'
14 
15 let md5 s =
16   System.BitConverter
17     .ToString(
18       System.Security.Cryptography.MD5
19         .Create()
20         .ComputeHash(buffer = System.Text.Encoding.UTF8.GetBytes(s = s)))
21     .Replace("-", System.String.Empty)
22     .ToLower()
23 
24 let factorial n = 
25   let rec fact acc = function | 0 -> acc | i -> fact (acc * i) (i - 1)
26   (1,n) ||> fact
27 
28 let unitTestPermutations () = 
29   "FooBar" 
30   |> Seq.toList
31   |> fun xs -> xs |> permutations |> Seq.length,
32                xs |> Seq.length   |> factorial
33   |> fun (x,y) -> x = y
34 
35 let unitTestMD5 () =  
36   // [ mon@mbai7 tmp ] md5 -s "FooBar"
37   // MD5 ("FooBar") = f32a26e2a3a8aa338cd77b6e1263c535
38   "FooBar" |> md5 |> fun x -> x = "f32a26e2a3a8aa338cd77b6e1263c535"
39 
40 (unitTestPermutations() && unitTestMD5()) |> function 
41   | true -> () 
42   | false -> failwith "Must be n! permuations per string"
43 
44 let cache file =
45   use reader = System.IO.File.OpenText(file)
46   let rec cache' acc = function
47     | true -> acc
48     | false -> cache' (acc |> Set.add(reader.ReadLine())) reader.EndOfStream
49   cache' Set.empty reader.EndOfStream
50 
51 let root     = __SOURCE_DIRECTORY__
52 let wordList = System.IO.Path.Combine(root,"wordlist.txt")
53 let anagram  = @"poultry outwits ants"
54 let hash     = @"4624d200580677270a54ccff86b9610e"
55 let words    = anagram.Split(char " ")
56 let cached   = wordList |> cache
57 
58 words |> Array.map(fun x  -> x  |> Seq.toList |> permutations)
59       |> Array.map(fun xs -> xs |> Seq.map(fun ys -> ys |> List.map string))
60       |> Array.map(fun xs -> xs |> Seq.map(fun ys -> ys |> List.reduce(+)))
61       |> Array.map(fun xs -> xs |> Seq.filter(fun x -> (x,cached) ||> Set.contains))
62       |> fun xs -> xs.[0],xs.[1],xs.[2]
63       |> fun (xs,ys,zs) -> seqMonad{let! x = xs
64                                     let! y = ys
65                                     let! z = zs
66                                     return (x,y,z)}
67       |> Seq.map(fun (x,y,z) -> x + " " + y + " " + z)
68       |> Seq.map(fun x -> x |> md5, x)
69       |> Seq.filter(fun (x,y) -> x = hash)
70       |> Seq.map(fun (x,y) -> y)
71       |> Seq.truncate 1
72       |> fun x -> x |> printfn "%A"

Code output:

type SeqMonad =
  class
    new : unit -> SeqMonad
    member Bind : m:seq<'b> * f:('b -> #seq<'d>) -> seq<'d>
    member Return : v:'a -> seq<'a>
  end
val seqMonad : SeqMonad
val permutations : ls:'a list -> seq<'a list>
val md5 : s:string -> string
val factorial : n:int -> int
val unitTestPermutations : unit -> bool
val unitTestMD5 : unit -> bool
val cache : file:string -> Set<string>
val root : string = "/Users/mon/tmp"
val wordList : string = "/Users/mon/tmp/wordlist.txt"
val anagram : string = "poultry outwits ants"
val hash : string = "4624d200580677270a54ccff86b9610e"
val words : string [] = [|"poultry"; "outwits"; "ants"|]
val cached : Set<string> =
  set
    ["a"; "a's"; "ab's"; "abaci"; "aback"; "abacus"; "abacus's"; "abacuses";
     "abaft"; ...]

Code result:

> seq []
> val it : unit = ()

References: