Skip to content

Commit 4a5dd0b

Browse files
Enable typed data table constructor to initialize an empty datatable with the expected columns.
1 parent 4520fed commit 4a5dd0b

File tree

4 files changed

+94
-49
lines changed

4 files changed

+94
-49
lines changed

src/SqlClient.Tests/DataTablesTests.fs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,3 +407,25 @@ type DataTablesTests() =
407407

408408
Assert.Equal(1, rowsAffected)
409409

410+
[<Fact>]
411+
member __.``can build arbitrary data table from inline sql``() =
412+
use table = new SqlCommandProvider<"SELECT 1 a, 2 b", ConnectionStrings.AdventureWorksNamed, ResultType.DataTable>.Table()
413+
let r = table.NewRow()
414+
table.Columns.a.set_ReadOnly false
415+
table.Columns.a.SetValue(r, 2)
416+
Assert.Equal(r.a , 2)
417+
418+
[<Fact>]
419+
member __.``can build arbitrary table and columns exist``() =
420+
let t = new GetArbitraryDataAsDataTable.Table()
421+
let r = t.NewRow()
422+
t.Columns.a.set_ReadOnly false
423+
t.Columns.a.SetValue(r, 1)
424+
Assert.Equal(r.a , 1)
425+
426+
[<Fact>]
427+
member __.``can't update on arbitrarilly constructed table``() =
428+
let t = new GetArbitraryDataAsDataTable.Table()
429+
let e = Assert.Throws(fun () -> t.Update() |> ignore)
430+
Assert.Equal<string>("This command wasn't constructed from SqlProgrammabilityProvider, call to Update is not supported.", e.Message)
431+

src/SqlClient/DataTable.fs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,11 @@ type DataTable<'T when 'T :> DataRow>(selectCommand: SqlCommand, ?connectionStri
4141
member private this.IsDirectTable = this.TableName <> null
4242

4343
member this.Update(?connection, ?transaction, ?batchSize, ?continueUpdateOnError) =
44-
44+
// not supported on all DataTable instances
45+
match selectCommand with
46+
| null -> failwith "This command wasn't constructed from SqlProgrammabilityProvider, call to Update is not supported."
47+
| _ -> ()
48+
4549
connection |> Option.iter selectCommand.set_Connection
4650
transaction |> Option.iter selectCommand.set_Transaction
4751

@@ -88,6 +92,9 @@ type DataTable<'T when 'T :> DataRow>(selectCommand: SqlCommand, ?connectionStri
8892
| _, Some(t: SqlTransaction) -> t.Connection, t
8993
| Some c, None -> c, null
9094
| None, None ->
95+
match selectCommand with
96+
| null -> failwith "To issue BulkCopy on this table, you need to provide your own connection or transaction"
97+
| _ -> ()
9198
if this.IsDirectTable
9299
then
93100
assert(connectionString.IsSome)

src/SqlClient/DesignTime.fs

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,28 @@ open ProviderImplementation.ProvidedTypes
1212
open FSharp.Data
1313
open System.Text.RegularExpressions
1414

15+
module RuntimeInternals =
16+
let setupTableFromSerializedColumns (serializedSchema: string) (table: System.Data.DataTable) =
17+
let primaryKey = ResizeArray()
18+
for line in serializedSchema.Split('\n') do
19+
let xs = line.Split('\t')
20+
let col = new DataColumn()
21+
col.ColumnName <- xs.[0]
22+
col.DataType <- Type.GetType( xs.[1], throwOnError = true)
23+
col.AllowDBNull <- Boolean.Parse xs.[2]
24+
if col.DataType = typeof<string>
25+
then
26+
col.MaxLength <- int xs.[3]
27+
col.ReadOnly <- Boolean.Parse xs.[4]
28+
col.AutoIncrement <- Boolean.Parse xs.[5]
29+
if Boolean.Parse xs.[6]
30+
then
31+
primaryKey.Add col
32+
table.Columns.Add col
33+
34+
table.PrimaryKey <- Array.ofSeq primaryKey
35+
36+
1537
type internal RowType = {
1638
Provided: Type
1739
ErasedTo: Type
@@ -31,6 +53,10 @@ type internal ReturnType = {
3153
| Some x -> Expr.Value( x.ErasedTo.AssemblyQualifiedName)
3254
| None -> <@@ null: string @@>
3355

56+
type internal DataTableType =
57+
| SqlProgrammabilityTable of isHostedExecution: bool * connectionString: DesignTimeConnectionString * schemaName: string * tableName: string * columns: Column list
58+
| CommandResultTable
59+
3460
module internal SharedLogic =
3561
/// Adds .Record or .Table inner type depending on resultType
3662
let alterReturnTypeAccordingToResultType (returnType: ReturnType) (cmdProvidedType: ProvidedTypeDefinition) resultType =
@@ -246,7 +272,7 @@ type DesignTime private() =
246272

247273
rowType
248274

249-
static member internal GetDataTableType(typeName, dataRowType: ProvidedTypeDefinition, outputColumns: Column list) =
275+
static member internal GetDataTableType(typeName, dataRowType: ProvidedTypeDefinition, outputColumns: Column list, dataTableType: DataTableType) =
250276
let tableType = ProvidedTypeBuilder.MakeGenericType(typedefof<_ DataTable>, [ dataRowType ])
251277
let tableProvidedType = ProvidedTypeDefinition(typeName, Some tableType)
252278

@@ -325,6 +351,41 @@ type DesignTime private() =
325351
)
326352
dataRowType.AddMember tableProperty
327353

354+
let getColumnsSerializedSchema columns =
355+
columns
356+
|> List.map (fun x ->
357+
let nullable = x.Nullable || x.HasDefaultConstraint
358+
sprintf "%s\t%s\t%b\t%i\t%b\t%b\t%b"
359+
x.Name x.TypeInfo.ClrTypeFullName nullable x.MaxLength x.ReadOnly x.Identity x.PartOfUniqueKey
360+
)
361+
|> String.concat "\n"
362+
363+
let ctorCode =
364+
fun _ ->
365+
let serializedSchema = getColumnsSerializedSchema outputColumns
366+
match dataTableType with
367+
| SqlProgrammabilityTable (isHostedExecution, connectionString, schemaName, tableName, _) ->
368+
369+
let twoPartTableName = sprintf "[%s].[%s]" schemaName tableName
370+
371+
<@@
372+
let connectionString = lazy %%connectionString.RunTimeValueExpr(isHostedExecution)
373+
let selectCommand = new SqlCommand("SELECT * FROM " + twoPartTableName)
374+
let table = new DataTable<DataRow>(selectCommand, connectionString)
375+
table.TableName <- twoPartTableName
376+
RuntimeInternals.setupTableFromSerializedColumns serializedSchema table
377+
table
378+
@@>
379+
| CommandResultTable ->
380+
<@@
381+
let table = new DataTable<DataRow>(null, null)
382+
RuntimeInternals.setupTableFromSerializedColumns serializedSchema table
383+
table
384+
@@>
385+
386+
387+
ProvidedConstructor([], InvokeCode = ctorCode) |> tableProvidedType.AddMember
388+
328389
tableProvidedType
329390

330391
static member internal GetOutputTypes (outputColumns: Column list, resultType, rank: ResultRank, hasOutputParameters, ?unitsOfMeasurePerSchema) =
@@ -337,7 +398,7 @@ type DesignTime private() =
337398
elif resultType = ResultType.DataTable
338399
then
339400
let dataRowType = DesignTime.GetDataRowType(outputColumns, ?unitsOfMeasurePerSchema = unitsOfMeasurePerSchema)
340-
let dataTableType = DesignTime.GetDataTableType("Table", dataRowType, outputColumns)
401+
let dataTableType = DesignTime.GetDataTableType("Table", dataRowType, outputColumns, CommandResultTable)
341402
dataTableType.AddMember dataRowType
342403

343404
{ Single = dataTableType; PerRow = None }
@@ -807,4 +868,3 @@ type DesignTime private() =
807868
| None -> m.Groups.[0].Value))
808869

809870
(vars |> Array.map(fun (name,typ) -> sprintf "DECLARE %s%s %s = @%s" Prefixes.tableVar name typ name) |> String.concat "; ") + "; " + commandText
810-

src/SqlClient/SqlClientProvider.fs

Lines changed: 1 addition & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -248,8 +248,6 @@ type SqlProgrammabilityProvider(config : TypeProviderConfig) as this =
248248
conn.GetTables(schema, isSqlAzure)
249249
|> List.map (fun (tableName, baseTableName, baseSchemaName, description) ->
250250

251-
let twoPartTableName = sprintf "[%s].[%s]" schema tableName
252-
253251
let descriptionSelector =
254252
if isSqlAzure
255253
then "(SELECT NULL AS Value)"
@@ -352,55 +350,13 @@ type SqlProgrammabilityProvider(config : TypeProviderConfig) as this =
352350
dataRowType.AddMember property
353351

354352
//type data table
355-
let dataTableType = DesignTime.GetDataTableType(tableName, dataRowType, columns)
353+
let dataTableType = DesignTime.GetDataTableType(tableName, dataRowType, columns, SqlProgrammabilityTable(config.IsHostedExecution, connectionString, schema, tableName, columns))
356354
tagProvidedType dataTableType
357355
dataTableType.AddMember dataRowType
358356

359357
do
360358
description |> Option.iter (fun x -> dataTableType.AddXmlDoc( sprintf "<summary>%s</summary>" x))
361359

362-
do //ctor
363-
let ctor = ProvidedConstructor []
364-
ctor.InvokeCode <- fun _ ->
365-
let serializedSchema =
366-
columns
367-
|> List.map (fun x ->
368-
let nullable = x.Nullable || x.HasDefaultConstraint
369-
sprintf "%s\t%s\t%b\t%i\t%b\t%b\t%b"
370-
x.Name x.TypeInfo.ClrTypeFullName nullable x.MaxLength x.ReadOnly x.Identity x.PartOfUniqueKey
371-
)
372-
|> String.concat "\n"
373-
374-
<@@
375-
376-
let connectionString = lazy %%connectionString.RunTimeValueExpr(config.IsHostedExecution)
377-
let selectCommand = new SqlCommand("SELECT * FROM " + twoPartTableName)
378-
let table = new DataTable<DataRow>(selectCommand, connectionString)
379-
table.TableName <- twoPartTableName
380-
381-
let primaryKey = ResizeArray()
382-
for line in serializedSchema.Split('\n') do
383-
let xs = line.Split('\t')
384-
let col = new DataColumn()
385-
col.ColumnName <- xs.[0]
386-
col.DataType <- Type.GetType( xs.[1], throwOnError = true)
387-
col.AllowDBNull <- Boolean.Parse xs.[2]
388-
if col.DataType = typeof<string>
389-
then
390-
col.MaxLength <- int xs.[3]
391-
col.ReadOnly <- Boolean.Parse xs.[4]
392-
col.AutoIncrement <- Boolean.Parse xs.[5]
393-
if Boolean.Parse xs.[6]
394-
then
395-
primaryKey.Add col
396-
table.Columns.Add col
397-
398-
table.PrimaryKey <- Array.ofSeq primaryKey
399-
400-
table
401-
@@>
402-
dataTableType.AddMember ctor
403-
404360
do
405361
let parameters, updateableColumns =
406362
[

0 commit comments

Comments
 (0)