Controlling output
1: 2: 3: 4: 5: 6: 7: 8: |
//Connection and query definition are shared for most of the examples below [<Literal>] let productsSql = " SELECT TOP (@top) Name AS ProductName, SellStartDate, Size FROM Production.Product WHERE SellStartDate > @SellStartDate " |
- Sequence of custom records is default result set type.
1: 2: 3: 4: 5: 6: 7: 8: 9: |
type QueryProductAsRecords = SqlCommandProvider<productsSql, connectionString> let queryProductAsRecords = new QueryProductAsRecords(connectionString) let records = queryProductAsRecords.AsyncExecute(top = 7L, SellStartDate = System.DateTime.Parse "2002-06-01") |> Async.RunSynchronously |> List.ofSeq records |> Seq.iter (printfn "%A") |
These records implement DynamicObject
for easy binding and JSON.NET serialization and Equals
for structural equality.
- Sync execution
- Seq of tuples as result set type
- Consider ResultType.Tuples to work around unique column name limitation for ResultType.Records.
1: 2: 3: 4: 5: 6: 7: 8: |
type QueryProductSync = SqlCommandProvider<productsSql, connectionString, ResultType = ResultType.Tuples> do use cmd = new QueryProductSync(connectionString) let tuples = cmd.Execute(top = 7L, SellStartDate = System.DateTime.Parse "2002-06-01") for productName, sellStartDate, size in tuples do printfn "Product name: %s. Sells start date %A, size: %A" productName sellStartDate size |
- Typed data table as result set
- DataTable result type is an enabler for data binding and update scenarios. Look at data modification for details.
1: 2: 3: 4: 5: |
do use cmd = new SqlCommandProvider<productsSql, connectionString, ResultType.DataTable>(connectionString) let table = cmd.Execute(top = 7L, SellStartDate = System.DateTime.Parse "2002-06-01") for row in table.Rows do printfn "Product name: %s. Sells start date %O, size: %A" row.ProductName row.SellStartDate row.Size |
- Single row hint. Must be provided explicitly. Cannot be inferred
- Nullable columns mapped to
Option<_>
type - Calling SQL Table-Valued Function
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: |
type QueryPersonInfoSingletoneAsRecords = SqlCommandProvider<"SELECT * FROM dbo.ufnGetContactInformation(@PersonId)" , connectionString , SingleRow = true> let singleton = new QueryPersonInfoSingletoneAsRecords(connectionString) let person = singleton.AsyncExecute(PersonId = 2) |> Async.RunSynchronously |> Option.get match person.FirstName, person.LastName with | Some first, Some last -> printfn "Person id: %i, name: %s %s" person.PersonID first last | _ -> printfn "What's your name %i?" person.PersonID |
- Same as previous but using tuples as result type
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: |
[<Literal>] let queryPersonInfoSingletoneQuery = "SELECT PersonID, FirstName, LastName FROM dbo.ufnGetContactInformation(@PersonId)" type QueryPersonInfoSingletoneTuples = SqlCommandProvider<queryPersonInfoSingletoneQuery, connectionString, ResultType.Tuples, SingleRow=true> QueryPersonInfoSingletoneTuples .Create(connectionString) .Execute(PersonId = 2).Value |> (function | id, Some first, Some last -> printfn "Person id: %i, name: %s %s" person.PersonID first last | id, _, _ -> printfn "What's your name %i?" person.PersonID ) |
- Same as previous but using typed DataTable as result type
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: |
type QueryPersonInfoSingletoneDataTable = SqlCommandProvider< "SELECT * FROM dbo.ufnGetContactInformation(@PersonId)", connectionString, ResultType = ResultType.DataTable> do use cmd = new QueryPersonInfoSingletoneDataTable(connectionString) let table = cmd .AsyncExecute(PersonId = 2) |> Async.RunSynchronously for row in table.Rows do printfn "Person info:Id - %i,FirstName - %O,LastName - %O" row.PersonID row.FirstName row.LastName // you can refer to the table type let table2 : QueryPersonInfoSingletoneDataTable.Table = let cmd = new QueryPersonInfoSingletoneDataTable(connectionString) cmd.Execute(PersonId = 2) // you can refer to the row type for row : QueryPersonInfoSingletoneDataTable.Table.Row in table2.Rows do printfn "Person info:Id - %i,FirstName - %O,LastName - %O" row.PersonID row.FirstName row.LastName |
- Same as previous but using
SqlProgrammabilityProvider<...>
- Worth noting that Stored Procedure/Function generated command instances have explicit ExecuteSingle/ AsyncExecuteSingle methods because there is no single place to specify SingleRow=true as for SqlCommandProvider.
1: 2: 3: 4: 5: 6: 7: |
type AdventureWorks2012 = SqlProgrammabilityProvider<connectionString> do use cmd = new AdventureWorks2012.dbo.ufnGetContactInformation(connectionString) cmd.ExecuteSingle(1) //opt-in for explicit call to |> Option.iter(fun x -> printfn "Person info:Id - %i,FirstName - %O,LastName - %O" x.PersonID x.FirstName x.LastName ) |
- One column only result set is inferred. Combined with
SingleRow
hint it gives single value as result AsyncExecute/Execute
are just regular F# methods, so args can be passed by name or by position
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: |
type QueryPersonInfoSingleValue = SqlCommandProvider< "SELECT FirstName + ' ' + LastName FROM dbo.ufnGetContactInformation(@PersonId)", connectionString, SingleRow=true> do let personId = 2 use cmd = new QueryPersonInfoSingleValue(connectionString) cmd.Execute( personId) |> Option.iter (fun name -> printf "Person with id %i has name %s" personId name.Value) |
- Single value
- Running the same command more than once with diff params
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: |
type GetServerTime = SqlCommandProvider< "IF @IsUtc = CAST(1 AS BIT) SELECT GETUTCDATE() ELSE SELECT GETDATE()", connectionString, SingleRow=true> let getSrvTime = new GetServerTime(connectionString) getSrvTime.AsyncExecute(IsUtc = true) |> Async.RunSynchronously |> printfn "%A" getSrvTime.Execute(IsUtc = false) |> printfn "%A" |
- Non-query
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: |
[<Literal>] let invokeSp = " EXEC HumanResources.uspUpdateEmployeePersonalInfo @BusinessEntityID, @NationalIDNumber, @BirthDate, @MaritalStatus, @Gender " type UpdateEmplInfoCommand = SqlCommandProvider<invokeSp, connectionString> let nonQuery = new UpdateEmplInfoCommand(connectionString) let rowsAffected = nonQuery.Execute( BusinessEntityID = 2, NationalIDNumber = "245797967", BirthDate = System.DateTime(1965, 09, 01), MaritalStatus = "S", Gender = "F") |
- Non-query with MS SQL HierarchyId using
SqlProgrammabilityProvider<...>
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: |
open System open System.Data open Microsoft.SqlServer.Types do use cmd = new AdventureWorks2012.HumanResources.uspUpdateEmployeeLogin(connectionString) let hierarchyId = SqlHierarchyId.Parse(SqlTypes.SqlString("/1/4/2/")) cmd.Execute( BusinessEntityID = 291, CurrentFlag = true, HireDate = DateTime(2013,1,1), JobTitle = "gatekeeper", LoginID = "adventure-works\gat0", OrganizationNode = hierarchyId ) |> printfn "Records afftected: %i" |
Result sequence is un-buffered by default
Although it implements standard seq<_>
(IEnumerable<_>
) interface it can be evaluated only once.
It is done mostly for memory efficiency. It behaves as forward-only cursor similar to underlying SqlDataReader.
If multiple passes over the sequence required use standard Seq.cache
combinator.
1: 2: 3: 4: |
type Get42 = SqlCommandProvider<"SELECT * FROM (VALUES (42), (43)) AS T(N)", connectionString> let xs = (new Get42(connectionString)).Execute() |> Seq.cache printfn "#1: %i " <| Seq.nth 0 xs printfn "#2: %i " <| Seq.nth 1 xs //see it fails here if result is not piped into Seq.cache |
Output result types summary:
- Records (default) .NET-style class with read-only properties. WebAPI/ASP.NET MVC/Json.NET/WPF, Data Binding
- Tuples - convenient option for F# combined with pattern matching
- DataTable with inferred data rows similar to Records. Update scenarios. WPF data binding
- DataReader - for rare cases when structure of output cannot be inferred
In later case, resulting SqlDataReader
can be wrapped into something like that:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: |
module SqlDataReader = open System.Data.SqlClient let toMaps (reader: SqlDataReader) = seq { use __ = reader while reader.Read() do yield [ for i = 0 to reader.FieldCount - 1 do if not( reader.IsDBNull(i)) then yield reader.GetName(i), reader.GetValue(i) ] |> Map.ofList } |
Note that combined with |> Map.tryFind(key)
this approach can be used to achieve Option
semantics
for each row, in other words, such function will return None
for NULL
values. Keep in mind though that
the incorrect column name will also return None
.
The same approach can be used to produce ExpandoObject
for dynamic scenarios.
type LiteralAttribute =
inherit Attribute
new : unit -> LiteralAttribute
Full name: Microsoft.FSharp.Core.LiteralAttribute
--------------------
new : unit -> LiteralAttribute
Full name: Output.productsSql
Full name: Output.QueryProductAsRecords
Full name: Output.connectionString
Full name: Output.queryProductAsRecords
Full name: Output.records
type DateTime =
struct
new : ticks:int64 -> DateTime + 10 overloads
member Add : value:TimeSpan -> DateTime
member AddDays : value:float -> DateTime
member AddHours : value:float -> DateTime
member AddMilliseconds : value:float -> DateTime
member AddMinutes : value:float -> DateTime
member AddMonths : months:int -> DateTime
member AddSeconds : value:float -> DateTime
member AddTicks : value:int64 -> DateTime
member AddYears : value:int -> DateTime
...
end
Full name: System.DateTime
--------------------
System.DateTime()
(+0 other overloads)
System.DateTime(ticks: int64) : unit
(+0 other overloads)
System.DateTime(ticks: int64, kind: System.DateTimeKind) : unit
(+0 other overloads)
System.DateTime(year: int, month: int, day: int) : unit
(+0 other overloads)
System.DateTime(year: int, month: int, day: int, calendar: System.Globalization.Calendar) : unit
(+0 other overloads)
System.DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int) : unit
(+0 other overloads)
System.DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, kind: System.DateTimeKind) : unit
(+0 other overloads)
System.DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, calendar: System.Globalization.Calendar) : unit
(+0 other overloads)
System.DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, millisecond: int) : unit
(+0 other overloads)
System.DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, millisecond: int, kind: System.DateTimeKind) : unit
(+0 other overloads)
System.DateTime.Parse(s: string, provider: System.IFormatProvider) : System.DateTime
System.DateTime.Parse(s: string, provider: System.IFormatProvider, styles: System.Globalization.DateTimeStyles) : System.DateTime
type Async
static member AsBeginEnd : computation:('Arg -> Async<'T>) -> ('Arg * AsyncCallback * obj -> IAsyncResult) * (IAsyncResult -> 'T) * (IAsyncResult -> unit)
static member AwaitEvent : event:IEvent<'Del,'T> * ?cancelAction:(unit -> unit) -> Async<'T> (requires delegate and 'Del :> Delegate)
static member AwaitIAsyncResult : iar:IAsyncResult * ?millisecondsTimeout:int -> Async<bool>
static member AwaitTask : task:Task<'T> -> Async<'T>
static member AwaitWaitHandle : waitHandle:WaitHandle * ?millisecondsTimeout:int -> Async<bool>
static member CancelDefaultToken : unit -> unit
static member Catch : computation:Async<'T> -> Async<Choice<'T,exn>>
static member FromBeginEnd : beginAction:(AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg:'Arg1 * beginAction:('Arg1 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg1:'Arg1 * arg2:'Arg2 * beginAction:('Arg1 * 'Arg2 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg1:'Arg1 * arg2:'Arg2 * arg3:'Arg3 * beginAction:('Arg1 * 'Arg2 * 'Arg3 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromContinuations : callback:(('T -> unit) * (exn -> unit) * (OperationCanceledException -> unit) -> unit) -> Async<'T>
static member Ignore : computation:Async<'T> -> Async<unit>
static member OnCancel : interruption:(unit -> unit) -> Async<IDisposable>
static member Parallel : computations:seq<Async<'T>> -> Async<'T []>
static member RunSynchronously : computation:Async<'T> * ?timeout:int * ?cancellationToken:CancellationToken -> 'T
static member Sleep : millisecondsDueTime:int -> Async<unit>
static member Start : computation:Async<unit> * ?cancellationToken:CancellationToken -> unit
static member StartAsTask : computation:Async<'T> * ?taskCreationOptions:TaskCreationOptions * ?cancellationToken:CancellationToken -> Task<'T>
static member StartChild : computation:Async<'T> * ?millisecondsTimeout:int -> Async<Async<'T>>
static member StartChildAsTask : computation:Async<'T> * ?taskCreationOptions:TaskCreationOptions -> Async<Task<'T>>
static member StartImmediate : computation:Async<unit> * ?cancellationToken:CancellationToken -> unit
static member StartWithContinuations : computation:Async<'T> * continuation:('T -> unit) * exceptionContinuation:(exn -> unit) * cancellationContinuation:(OperationCanceledException -> unit) * ?cancellationToken:CancellationToken -> unit
static member SwitchToContext : syncContext:SynchronizationContext -> Async<unit>
static member SwitchToNewThread : unit -> Async<unit>
static member SwitchToThreadPool : unit -> Async<unit>
static member TryCancelled : computation:Async<'T> * compensation:(OperationCanceledException -> unit) -> Async<'T>
static member CancellationToken : Async<CancellationToken>
static member DefaultCancellationToken : CancellationToken
Full name: Microsoft.FSharp.Control.Async
--------------------
type Async<'T>
Full name: Microsoft.FSharp.Control.Async<_>
module List
from Microsoft.FSharp.Collections
--------------------
type List<'T> =
| ( [] )
| ( :: ) of Head: 'T * Tail: 'T list
interface IEnumerable
interface IEnumerable<'T>
member Head : 'T
member IsEmpty : bool
member Item : index:int -> 'T with get
member Length : int
member Tail : 'T list
static member Cons : head:'T * tail:'T list -> 'T list
static member Empty : 'T list
Full name: Microsoft.FSharp.Collections.List<_>
Full name: Microsoft.FSharp.Collections.List.ofSeq
from Microsoft.FSharp.Collections
Full name: Microsoft.FSharp.Collections.Seq.iter
Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.printfn
Full name: Output.QueryProductSync
| Records = 0
| Tuples = 1
| DataTable = 2
| DataReader = 3
Full name: FSharp.Data.ResultType
Full name: Output.QueryPersonInfoSingletoneAsRecords
Full name: Output.singleton
Full name: Output.person
from Microsoft.FSharp.Core
Full name: Microsoft.FSharp.Core.Option.get
Full name: Output.queryPersonInfoSingletoneQuery
Full name: Output.QueryPersonInfoSingletoneTuples
Full name: Output.QueryPersonInfoSingletoneDataTable
Full name: Output.table2
Full name: Output.AdventureWorks2012
Full name: Microsoft.FSharp.Core.Option.iter
Full name: Output.QueryPersonInfoSingleValue
Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.printf
Full name: Output.GetServerTime
Full name: Output.getSrvTime
Full name: Output.invokeSp
Full name: Output.UpdateEmplInfoCommand
Full name: Output.nonQuery
Full name: Output.rowsAffected
struct
member CompareTo : obj:obj -> int + 1 overload
member Equals : obj:obj -> bool
member GetAncestor : n:int -> SqlHierarchyId
member GetDescendant : child1:SqlHierarchyId * child2:SqlHierarchyId -> SqlHierarchyId
member GetHashCode : unit -> int
member GetLevel : unit -> SqlInt16
member GetReparentedValue : oldRoot:SqlHierarchyId * newRoot:SqlHierarchyId -> SqlHierarchyId
member IsDescendantOf : parent:SqlHierarchyId -> SqlBoolean
member IsNull : bool
member Read : r:BinaryReader -> unit
...
end
Full name: Microsoft.SqlServer.Types.SqlHierarchyId
type SqlString =
struct
new : data:string -> SqlString + 6 overloads
member Clone : unit -> SqlString
member CompareInfo : CompareInfo
member CompareTo : value:obj -> int + 1 overload
member CultureInfo : CultureInfo
member Equals : value:obj -> bool
member GetHashCode : unit -> int
member GetNonUnicodeBytes : unit -> byte[]
member GetUnicodeBytes : unit -> byte[]
member IsNull : bool
...
end
Full name: System.Data.SqlTypes.SqlString
--------------------
SqlTypes.SqlString()
SqlTypes.SqlString(data: string) : unit
SqlTypes.SqlString(data: string, lcid: int) : unit
SqlTypes.SqlString(lcid: int, compareOptions: SqlTypes.SqlCompareOptions, data: byte []) : unit
SqlTypes.SqlString(data: string, lcid: int, compareOptions: SqlTypes.SqlCompareOptions) : unit
SqlTypes.SqlString(lcid: int, compareOptions: SqlTypes.SqlCompareOptions, data: byte [], fUnicode: bool) : unit
SqlTypes.SqlString(lcid: int, compareOptions: SqlTypes.SqlCompareOptions, data: byte [], index: int, count: int) : unit
SqlTypes.SqlString(lcid: int, compareOptions: SqlTypes.SqlCompareOptions, data: byte [], index: int, count: int, fUnicode: bool) : unit
type DateTime =
struct
new : ticks:int64 -> DateTime + 10 overloads
member Add : value:TimeSpan -> DateTime
member AddDays : value:float -> DateTime
member AddHours : value:float -> DateTime
member AddMilliseconds : value:float -> DateTime
member AddMinutes : value:float -> DateTime
member AddMonths : months:int -> DateTime
member AddSeconds : value:float -> DateTime
member AddTicks : value:int64 -> DateTime
member AddYears : value:int -> DateTime
...
end
Full name: System.DateTime
--------------------
DateTime()
(+0 other overloads)
DateTime(ticks: int64) : unit
(+0 other overloads)
DateTime(ticks: int64, kind: DateTimeKind) : unit
(+0 other overloads)
DateTime(year: int, month: int, day: int) : unit
(+0 other overloads)
DateTime(year: int, month: int, day: int, calendar: Globalization.Calendar) : unit
(+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int) : unit
(+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, kind: DateTimeKind) : unit
(+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, calendar: Globalization.Calendar) : unit
(+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, millisecond: int) : unit
(+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, millisecond: int, kind: DateTimeKind) : unit
(+0 other overloads)
Full name: Output.Get42
Full name: Output.xs
Full name: Microsoft.FSharp.Collections.Seq.cache
Full name: Microsoft.FSharp.Collections.Seq.nth
from Output
Full name: Output.SqlDataReader.toMaps
inherit DbDataReader
member Close : unit -> unit
member Depth : int
member FieldCount : int
member GetBoolean : i:int -> bool
member GetByte : i:int -> byte
member GetBytes : i:int * dataIndex:int64 * buffer:byte[] * bufferIndex:int * length:int -> int64
member GetChar : i:int -> char
member GetChars : i:int * dataIndex:int64 * buffer:char[] * bufferIndex:int * length:int -> int64
member GetDataTypeName : i:int -> string
member GetDateTime : i:int -> DateTime
...
Full name: System.Data.SqlClient.SqlDataReader
val seq : sequence:seq<'T> -> seq<'T>
Full name: Microsoft.FSharp.Core.Operators.seq
--------------------
type seq<'T> = Collections.Generic.IEnumerable<'T>
Full name: Microsoft.FSharp.Collections.seq<_>
Full name: Microsoft.FSharp.Core.Operators.not
module Map
from Microsoft.FSharp.Collections
--------------------
type Map<'Key,'Value (requires comparison)> =
interface IEnumerable
interface IComparable
interface IEnumerable<KeyValuePair<'Key,'Value>>
interface ICollection<KeyValuePair<'Key,'Value>>
interface IDictionary<'Key,'Value>
new : elements:seq<'Key * 'Value> -> Map<'Key,'Value>
member Add : key:'Key * value:'Value -> Map<'Key,'Value>
member ContainsKey : key:'Key -> bool
override Equals : obj -> bool
member Remove : key:'Key -> Map<'Key,'Value>
...
Full name: Microsoft.FSharp.Collections.Map<_,_>
--------------------
new : elements:seq<'Key * 'Value> -> Map<'Key,'Value>
Full name: Microsoft.FSharp.Collections.Map.ofList