Question

During slow time at work I wanted to translate a little DB access framework I coded from C# to F#. This framework uses dynamics and ExpandoObjects extensively. In particular, there's this bit which is giving me headaches:

public static List<dynamic> ToDynamic(this IDataReader reader)
{
    dynamic result = new List<dynamic>();

    while (reader.Read())
    {
        var item = new ExpandoObject();
        var dc = item as IDictionary<String, object>;
        for (int i = 0; i < reader.FieldCount; i++)
        {
            dc.Add(reader.GetName(i), DBNull.Value.Equals(reader[i]) ? null : reader[i]);
        }

        result.Add(item);
    }

    return result;
}

How do you translate this into F#? This is all I could come up with:

let (~~) (x:obj) = 
    match x with
    | :? 't as t -> t 
    | _ -> null

let rec mapper (reader : SqlDataReader) : list<'Value> =
    match reader.Read() with
    | false -> []
    | true ->
        let dc = new ExpandoObject()
        let dictionary = ~~dc : Dictionary<string, obj>
        [for i in [0 .. reader.FieldCount - 1] do
            dictionary.Add(reader.GetName(i), reader.GetValue(i))] @ mapper reader

Except, of course, dictionary comes up null in the cycle. The ~~ was supposed to replace the as operator from C# but I guess things are not that simple. It might just be a case of Friday, but I really can't see through this.

Was it helpful?

Solution

Because there isn't language support for dynamic, this is slightly more involved in F#. (I recommend reading the docs on DynamicAttribute.) Here's a translation:

open System.Collections.Generic
open System.Data
open System.Dynamic
open System.Runtime.CompilerServices

[<CompiledName("ToDynamic")>]
let toDynamic (reader: IDataReader) : [<return: Dynamic([|false;true|])>] ResizeArray<obj> = 
  let results = ResizeArray<obj>()
  let rec loop() =
    if reader.Read() then
      let obj = ExpandoObject() :> IDictionary<_,_>
      for i = 0 to reader.FieldCount - 1 do
        obj.Add(reader.GetName(i), if reader.IsDBNull(i) then null else reader.[i])
      results.Add(obj)
      loop()
    else results
  loop()

If you just want dynamic access to the values in an IDataReader, a more elegant solution in F# would be to overload the dynamic operator:

let (?) (reader: IDataReader) (name: string) = 
  match reader.[name] with
  | :? DBNull -> Unchecked.defaultof<_>
  | value -> unbox value

//Usage: reader?Id
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top