Last active
November 21, 2018 14:52
-
-
Save jsnape/56f1fb4876974de94238 to your computer and use it in GitHub Desktop.
F# IEnumerable<'T> -> IDataReader
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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