diff --git a/src/SQLProvider.Common/SqlRuntime.Common.fs b/src/SQLProvider.Common/SqlRuntime.Common.fs index 91f803fa..817648fc 100644 --- a/src/SQLProvider.Common/SqlRuntime.Common.fs +++ b/src/SQLProvider.Common/SqlRuntime.Common.fs @@ -548,6 +548,8 @@ and ISqlDataContext = abstract CallSprocAsync : RunTimeSprocDefinition * QueryParameter[] * obj[] -> System.Threading.Tasks.Task /// Get individual row. Takes tablename and id. abstract GetIndividual : string * obj -> SqlEntity + /// Get individual row asynchronously. Takes tablename and id. + abstract GetIndividualAsync : string * obj -> System.Threading.Tasks.Task /// Save entity to database. abstract SubmitChangedEntity : SqlEntity -> unit /// Save database-changes in a transaction to database. @@ -1229,6 +1231,7 @@ module public OfflineTools = | false, _ -> failwith ("Add table to dummydata: " + pe) member this.GetIndividual(arg1: string, arg2: obj): SqlEntity = raise (System.NotImplementedException()) + member this.GetIndividualAsync(arg1: string, arg2: obj): System.Threading.Tasks.Task = raise (System.NotImplementedException()) member this.GetPendingEntities(): SqlEntity list = (CommonTasks.sortEntities pendingChanges) |> Seq.toList member this.GetPrimaryKeyDefinition(arg1: string): string = "" member this.ReadEntities(arg1: string, arg2: FSharp.Data.Sql.Schema.ColumnLookup, arg3: Data.IDataReader): SqlEntity array = raise (System.NotImplementedException()) diff --git a/src/SQLProvider.DesignTime/SqlDesignTime.fs b/src/SQLProvider.DesignTime/SqlDesignTime.fs index 17858f49..c25355d3 100644 --- a/src/SQLProvider.DesignTime/SqlDesignTime.fs +++ b/src/SQLProvider.DesignTime/SqlDesignTime.fs @@ -240,7 +240,25 @@ module DesignTimeUtils = , getterCode = getterCode ) ) - |> Array.append( propertyMap |> Map.toArray |> Array.map (snd >> snd)) + + // Add async method to fetch individual by primary key + let tableName = table.FullName + let pkColumn = columns.[pkName] + let pkType = Utilities.getType pkColumn.TypeMapping.ClrType + let returnType = typedefof>.MakeGenericType(tableTypeDef) + let getAsyncMethod = + ProvidedMethod("GetAsync", [ProvidedParameter("id", pkType)], returnType, invokeCode = fun args -> + let a0 = args.[0] + let pkValue = args.[1] + <@@ ((%%a0 : obj) :?> ISqlDataContext).GetIndividualAsync(tableName, %%pkValue) @@> + ) + getAsyncMethod.AddXmlDoc(sprintf "Asynchronously get an individual %s by primary key value" table.Name) + + seq { + yield getAsyncMethod :> MemberInfo + yield! props |> Seq.cast + yield! propertyMap |> Map.toSeq |> Seq.map (snd >> snd) |> Seq.cast + } |> Seq.toList propertyMap |> Map.toSeq diff --git a/src/SQLProvider.Runtime/SqlRuntime.DataContext.fs b/src/SQLProvider.Runtime/SqlRuntime.DataContext.fs index 368cc3e2..ef310069 100644 --- a/src/SQLProvider.Runtime/SqlRuntime.DataContext.fs +++ b/src/SQLProvider.Runtime/SqlRuntime.DataContext.fs @@ -236,6 +236,37 @@ type public SqlDataContext (typeName, connectionString:string, providerType:Data if provider.CloseConnectionAfterQuery then con.Close() entity + member this.GetIndividualAsync(table,id) = + task { + use con = provider.CreateConnection(connectionString) :?> System.Data.Common.DbConnection + let table = Table.FromFullName table + // this line is to ensure the columns for the table have been retrieved and therefore + // its primary key exists in the lookup + let columns = provider.GetColumns(con, table) + let pk = + match provider.GetPrimaryKey table with + | Some v -> columns.[v] + | None -> + // this fail case should not really be possible unless the runtime database is different to the design-time one + failwithf "Primary key could not be found on object %s. Individuals only supported on objects with a single primary key." table.FullName + use com = provider.CreateCommand(con,provider.GetIndividualQueryText(table,pk.Name)) :?> System.Data.Common.DbCommand + if commandTimeout.IsSome then + com.CommandTimeout <- commandTimeout.Value + //todo: establish pk SQL data type + com.Parameters.Add (provider.CreateCommandParameter(QueryParameter.Create("@id", 0, pk.TypeMapping),id)) |> ignore + if con.State <> ConnectionState.Open then + do! con.OpenAsync() + if con.State <> ConnectionState.Open then // Just ensure, as not all the providers seems to work so great with OpenAsync. + if con.State <> ConnectionState.Closed && provider.CloseConnectionAfterQuery then con.Close() + con.Open() + use! reader = com.ExecuteReaderAsync() + let! entities = (this :> ISqlDataContext).ReadEntitiesAsync(table.FullName, columns, reader) + let entity = entities |> Seq.exactlyOne + reader.Close() + if provider.CloseConnectionAfterQuery then con.Close() + return entity + } + member this.ReadEntities(name: string, columns: ColumnLookup, reader: IDataReader) = [| while reader.Read() do let e = SqlEntity(this, name, columns, reader.FieldCount)