@@ -7,6 +7,8 @@ open System.Data.Common
77open Rezoom
88open Rezoom.SQL
99open Rezoom.SQL .Mapping
10+ open FSharp.Control .Tasks .ContextInsensitive
11+ open System.Threading
1012
1113type private ExecutionLocalConnections ( provider : ConnectionProvider ) =
1214 let connections = Dictionary()
@@ -99,6 +101,73 @@ type private CommandErrand<'a>(command : Command<'a>) =
99101 let truncate = 80
100102 if all.Length < truncate then all else all.Substring( 0 , truncate - 3 ) + " ..."
101103
104+ type private SharedCommandStepState < 'id , 'a when 'id : equality >( factory : SharedCommandFactory < 'id , 'a >, batch : AsyncCommandBatch ) =
105+ let ids = ResizeArray< 'id>()
106+ // defer the command-building till the last possible moment before the batch executes
107+ let bulkTask = batch.Batch( fun () -> factory.BuildCommand( ids))
108+ let lazyResults =
109+ lazy
110+ task {
111+ let! resultSet = bulkTask CancellationToken.None
112+ let dict = Dictionary()
113+ for resultRow in resultSet do
114+ let id = factory.Selector( resultRow)
115+ let succ , found = dict.TryGetValue( id)
116+ let found =
117+ if succ then found else
118+ let it = ResizeArray()
119+ dict.[ id] <- it
120+ it
121+ found.Add( resultRow)
122+ return dict
123+ }
124+ member this.PrepareId ( id : 'id ) =
125+ ids.Add( id)
126+ fun ( _ : CancellationToken ) ->
127+ task {
128+ let! results = lazyResults.Value
129+ let succ , found = results.TryGetValue( id)
130+ return
131+ if succ then found :> 'a IReadOnlyList
132+ else [||] :> 'a IReadOnlyList
133+ }
134+
135+ and private SharedCommandStepStateLookup < 'id , 'a when 'id : equality >() =
136+ let idsByFactory = Dictionary< obj, SharedCommandStepState< 'id, 'a>>()
137+ member this.ByFactory ( factory : SharedCommandFactory < 'id , 'a >, batch : AsyncCommandBatch ) =
138+ let succ , found = idsByFactory.TryGetValue( factory)
139+ if succ then found else
140+ let state = SharedCommandStepState< 'id, 'a>( factory, batch)
141+ idsByFactory.[ factory] <- state
142+ state
143+
144+ and private SharedCommandStepStateLookupFactory < 'id , 'a when 'id : equality >() =
145+ inherit ServiceFactory< SharedCommandStepStateLookup< 'id, 'a>>()
146+ override __.ServiceLifetime = ServiceLifetime.StepLocal
147+ override __.CreateService ( _ ) = SharedCommandStepStateLookup< 'id, 'a>()
148+ override __.DisposeService ( _ , _ ) = ()
149+
150+ and SharedCommandFactory < 'id , 'a when 'id : equality >( buildCommand : 'id seq -> Command < 'a IReadOnlyList >, selector : 'a -> 'id ) =
151+ let templateCommand = buildCommand Seq.empty
152+ let connectionName = templateCommand.ConnectionName
153+ let cacheArgument = CommandErrandArgument( templateCommand.Parameters)
154+ member internal __.BuildCommand = buildCommand
155+ member internal __.Selector = selector
156+ member factory.ErrandForKey ( id : 'id ) =
157+ let cacheArg = box ( id, cacheArgument)
158+ { new AsynchronousErrand< 'a IReadOnlyList>() with
159+ override __.CacheInfo = templateCommand.CacheInfo
160+ override __.CacheArgument = cacheArg
161+ override __.SequenceGroup = null
162+ override __.ToString () =
163+ templateCommand.ToString() + " (Arg = " + string ( box id) + " )"
164+ override __.Prepare ( cxt ) =
165+ let batches = cxt.GetService< StepLocalBatchesFactory, _>()
166+ let batch = batches.GetBatch( connectionName)
167+ let subErrands = cxt.GetService< SharedCommandStepStateLookupFactory< 'id, 'a>, _>() .ByFactory( factory, batch)
168+ subErrands.PrepareId( id)
169+ } :> Errand< 'a IReadOnlyList>
170+
102171// Have to use a C#-style extension method to support the scalar constraint.
103172
104173[<Extension>]
0 commit comments