Skip to content

Instantly share code, notes, and snippets.

@jsnape
Last active November 21, 2018 14:52
Show Gist options
  • Save jsnape/56f1fb4876974de94238 to your computer and use it in GitHub Desktop.
Save jsnape/56f1fb4876974de94238 to your computer and use it in GitHub Desktop.
F# IEnumerable<'T> -> IDataReader
open System
open System.Reflection
open System.Collections.Generic
open System.Data
open System.Linq
open System.Linq.Expressions
type private accessor<'T> = { Index: int; Name: string; Getter: 'T -> obj }
/// IEnumerable to IDataReader wrapper class
type EnumerableReader<'T>(data: IEnumerable<'T>) =
/// Constant used to return nulls
let nullValue = Operators.Unchecked.defaultof<obj>
/// Internal iterator
let mutable enumerator = Some (data.GetEnumerator())
let properties =
typeof<'T>.GetProperties(BindingFlags.Instance ||| BindingFlags.Public)
|> Seq.filter (fun p -> p.CanRead)
|> Seq.mapi (fun i p -> { Index = i; Name = p.Name; Getter = EnumerableReader<'T>.createPropertyAccessor(p) })
|> Seq.toArray
/// Raw getters indexed by ordinal
let getters =
properties |> Seq.map (fun p -> p.Getter) |> Seq.toArray
/// Ordinal map since most <c>SqlBulkCopy</c> calls come by ordinal
let ordinals =
properties |> Seq.map (fun p -> p.Name, p.Index) |> Map.ofSeq
/// Creates the property accessor.
static member private createPropertyAccessor property =
// Define the parameter that will be passed - will be the current object
let parameter = Expression.Parameter(typeof<'T>, "input")
// Define an expression to get the value from the property
let propertyAccess = Expression.Property(parameter, property.GetGetMethod())
// Make sure the result of the get method is cast as an object
let castAsObject = Expression.TypeAs(propertyAccess, typeof<obj>)
// Create a lambda expression for the property access and compile it
let lambda = Expression.Lambda<Func<'T, obj>>(castAsObject, parameter);
let func = lambda.Compile()
// Return an F# wrapper function
fun input -> func.Invoke(input)
interface IDisposable with
/// Performs application-defined tasks associated with freeing,
/// releasing, or resetting unmanaged resources.
member this.Dispose() =
match enumerator with
| Some e -> e.Dispose()
| None -> ()
enumerator <- None
interface IDataReader with
/// Gets the number of columns in the current row.
member this.FieldCount with get() = getters.Length
/// Gets a value indicating whether the data reader is closed.
member this.IsClosed with get() = enumerator.IsNone
/// Gets a value indicating the depth of nesting for the current row.
member this.Depth with get() = 0
/// Gets the number of rows changed, inserted, or deleted by execution of the SQL statement.
member this.RecordsAffected with get() = -1
/// Gets the column with the specified ordinal
member this.Item
with get(ordinal) = (this :> IDataReader).GetValue(ordinal)
/// Gets the column with the specified name
member this.Item
with get(name) =
let ordinal = (this :> IDataReader).GetOrdinal(name)
(this :> IDataReader).GetValue(ordinal)
/// Advances the <see cref="T:System.Data.IDataReader" /> to the next record
member this.Read() =
match enumerator with
| Some e -> e.MoveNext()
| None -> raise (new ObjectDisposedException("EnumerableReader"))
/// Return the index of the named field.
member this.GetOrdinal(name) =
match ordinals.TryFind(name) with
| Some ordinal -> ordinal
| None -> raise (new InvalidOperationException("Unknown parameter name " + name))
/// Gets the name for the field to find.
member this.GetName(ordinal) =
ordinals |> Map.findKey (fun k v -> v = ordinal)
/// Return the value of the specified field.
member this.GetValue(ordinal) =
let value =
match enumerator with
| Some e -> getters.[ordinal](e.Current)
| None -> raise (new ObjectDisposedException("EnumerableReader"))
match value with
| :? Option<bool> as x -> if x.IsNone then nullValue else x.Value :> obj
| :? Option<byte> as x -> if x.IsNone then nullValue else x.Value :> obj
| :? Option<char> as x -> if x.IsNone then nullValue else x.Value :> obj
| :? Option<DateTime> as x -> if x.IsNone then nullValue else x.Value :> obj
| :? Option<decimal> as x -> if x.IsNone then nullValue else x.Value :> obj
| :? Option<double> as x -> if x.IsNone then nullValue else x.Value :> obj
| :? Option<Guid> as x -> if x.IsNone then nullValue else x.Value :> obj
| :? Option<Int16> as x -> if x.IsNone then nullValue else x.Value :> obj
| :? Option<Int32> as x -> if x.IsNone then nullValue else x.Value :> obj
| :? Option<Int64> as x -> if x.IsNone then nullValue else x.Value :> obj
| :? Option<string> as x -> if x.IsNone then nullValue else x.Value :> obj
| _ -> value
/// Return whether the specified field is set to null.
member this.IsDBNull(ordinal) =
match (this :> IDataReader).GetValue(ordinal) with
| null -> true
| :? DBNull -> true
| _ -> false
/// Closes the <see cref="T:System.Data.IDataReader" /> Object
member this.Close() =
(this :> IDisposable).Dispose()
/// Gets the <see cref="T:System.Type" /> information corresponding to the type
/// of <see cref="T:System.Object" /> that would be returned
member this.GetFieldType(ordinal) =
(this :> IDataReader).GetValue(ordinal).GetType()
/// Advances the data reader to the next result, when reading the results of batch SQL statements.
member this.NextResult() = false
/// Gets the value of the specified column as a Boolean
member this.GetBoolean(ordinal) =
unbox ((this :> IDataReader).GetValue(ordinal))
/// Gets the 8-bit unsigned integer value of the specified column
member this.GetByte(ordinal) =
unbox ((this :> IDataReader).GetValue(ordinal))
/// Gets the character value of the specified column
member this.GetChar(ordinal) =
unbox ((this :> IDataReader).GetValue(ordinal))
/// Gets the date and time data value of the specified field
member this.GetDateTime(ordinal) =
unbox ((this :> IDataReader).GetValue(ordinal))
/// Gets the fixed-position numeric value of the specified field
member this.GetDecimal(ordinal) =
unbox ((this :> IDataReader).GetValue(ordinal))
/// Gets the double-precision floating point number of the specified field
member this.GetDouble(ordinal) =
unbox ((this :> IDataReader).GetValue(ordinal))
/// Gets the single-precision floating point number of the specified field
member this.GetFloat(ordinal) =
unbox ((this :> IDataReader).GetValue(ordinal))
/// Returns the GUID value of the specified field
member this.GetGuid(ordinal) =
unbox ((this :> IDataReader).GetValue(ordinal))
/// Gets the 32-bit signed integer value of the specified field
member this.GetInt16(ordinal) =
unbox ((this :> IDataReader).GetValue(ordinal))
/// Gets the 32-bit signed integer value of the specified field
member this.GetInt32(ordinal) =
unbox ((this :> IDataReader).GetValue(ordinal))
/// Gets the 64-bit signed integer value of the specified field
member this.GetInt64(ordinal) =
unbox ((this :> IDataReader).GetValue(ordinal))
/// Gets the string value of the specified field
member this.GetString(ordinal) =
unbox ((this :> IDataReader).GetValue(ordinal))
/// Returns an <see cref="T:System.Data.IDataReader" /> for the specified column ordinal
member this.GetData(ordinal) =
raise (new NotImplementedException())
/// Gets the data type information for the specified field
member this.GetDataTypeName(ordinal) =
raise (new NotImplementedException())
/// Populates an array of objects with the column values of the current record
member this.GetValues(ordinal) =
raise (new NotImplementedException())
/// Returns a <see cref="T:System.Data.DataTable" /> that describes the
/// column meta data of the <see cref="T:System.Data.IDataReader"
member this.GetSchemaTable()
= raise (new NotImplementedException())
/// Reads a stream of bytes from the specified column offset into the buffer as an array, starting at the given buffer offset
member this.GetBytes(i, fieldOffset, buffer, bufferOffset, length) =
raise (new NotImplementedException())
/// Reads a stream of characters from the specified column offset into the buffer as an array, starting at the given buffer offset
member this.GetChars(i, fieldOffset, buffer, bufferOffset, length) =
raise (new NotImplementedException())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment