Skip to content

Commit a7dd844

Browse files
Merge pull request #219 from smoothdeveloper/expose-data-columns
Expose DataColumn properties on DataColumnCollection
2 parents 1b476b1 + 5b2967c commit a7dd844

File tree

5 files changed

+129
-30
lines changed

5 files changed

+129
-30
lines changed

docs/content/data modification.fsx

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ do
2929
INSERT INTO Sales.CurrencyRate
3030
VALUES (@currencyRateDate, @fromCurrencyCode, @toCurrencyCode,
3131
@averageRate, @endOfDayRate, DEFAULT)
32-
", connectionString>()
32+
", connectionString>(connectionString)
3333

3434
let recordsInserted =
3535
cmd.Execute(
@@ -63,7 +63,7 @@ let businessEntityID, jobTitle, hireDate =
6363
HumanResources.Employee
6464
WHERE
6565
BusinessEntityID = @id
66-
", connectionString, ResultType.Tuples, SingleRow = true>()
66+
", connectionString, ResultType.Tuples, SingleRow = true>(connectionString))
6767

6868
jamesKramerId |> cmd.Execute |> Option.get
6969

@@ -72,7 +72,7 @@ assert("Production Technician - WC60" = jobTitle)
7272
let newJobTitle = "Uber " + jobTitle
7373

7474
let recordsAffrected =
75-
use updatedJobTitle = new AdventureWorks.HumanResources.uspUpdateEmployeeHireInfo()
75+
use updatedJobTitle = new AdventureWorks.HumanResources.uspUpdateEmployeeHireInfo(connectionString))
7676
updatedJobTitle.Execute(
7777
businessEntityID,
7878
newJobTitle,
@@ -88,7 +88,7 @@ assert(recordsAffrected = 1)
8888
let updatedJobTitle =
8989
// Static Create factory method provides better IntelliSense than ctor.
9090
// See https://github.com/Microsoft/visualfsharp/issues/449
91-
use cmd = new AdventureWorks.dbo.ufnGetContactInformation()
91+
use cmd = new AdventureWorks.dbo.ufnGetContactInformation(connectionString)
9292

9393
//Use ExecuteSingle if you're sure it return 0 or 1 rows.
9494
let result = cmd.ExecuteSingle(PersonID = jamesKramerId)
@@ -133,6 +133,13 @@ The IntelliSense experience is left a little clunky to retain legacy `DataRow` t
133133
let firstRow = currencyRates.Rows.[0]
134134
firstRow.AverageRate
135135

136+
(**
137+
It is possible to get a reference to the DataColumn object
138+
*)
139+
140+
let averageRateColumn = currencyRates.Columns.AverageRate
141+
142+
136143
(**
137144
The `AddRow` method adds a new row to a table.
138145
@@ -210,7 +217,7 @@ do
210217
WHERE FromCurrencyCode = @from
211218
AND ToCurrencyCode = @to
212219
AND CurrencyRateDate > @date
213-
", connectionString, ResultType.DataReader>()
220+
", connectionString, ResultType.DataReader>(connectionString)
214221
//ResultType.DataReader !!!
215222
let currencyRates = new AdventureWorks.Sales.Tables.CurrencyRate()
216223
//load data into data table
@@ -347,7 +354,7 @@ do
347354
WHERE FromCurrencyCode = @from
348355
AND ToCurrencyCode = @to
349356
AND CurrencyRateDate > @date
350-
", connectionString, ResultType.DataTable>()
357+
", connectionString, ResultType.DataTable>(connectionString)
351358
//ResultType.DataTable !!!
352359
let currencyRates = cmd.Execute("USD", "GBP", DateTime(2014, 1, 1))
353360

src/SqlClient.Tests/DataTablesTests.fs

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ type ProductCostHistory = AdventureWorks.Production.Tables.ProductCostHistory
1717

1818
type GetRowCount = SqlCommandProvider<"SELECT COUNT(*) FROM HumanResources.Shift", ConnectionStrings.AdventureWorksNamed, SingleRow = true>
1919
type GetShiftTableData = SqlCommandProvider<"SELECT * FROM HumanResources.Shift", ConnectionStrings.AdventureWorksNamed, ResultType.DataReader>
20-
type GetArbitraryDataAsDataTable = SqlCommandProvider<"select 1 a, 2 b, 3 c", ConnectionStrings.AdventureWorksNamed, ResultType.DataTable>
20+
type GetArbitraryDataAsDataTable = SqlCommandProvider<"select 1 a, 2 b, 3 c, cast(null as int) d", ConnectionStrings.AdventureWorksNamed, ResultType.DataTable>
2121
type DataTablesTests() =
2222

2323
do
@@ -281,3 +281,42 @@ type DataTablesTests() =
281281
Assert.Equal(1, t.Rows.[0].a)
282282
Assert.Equal(2, t.Rows.[0].b)
283283
Assert.Equal(3, t.Rows.[0].c)
284+
Assert.Equal(None, t.Rows.[0].d)
285+
286+
[<Fact>]
287+
member __.``Can use datacolumns and access like a normal DataRow`` () =
288+
let t = (new GetArbitraryDataAsDataTable()).Execute()
289+
let r = t.Rows.[0]
290+
291+
Assert.Equal(1, r.[t.Columns.a] :?> int)
292+
Assert.Equal(2, r.[t.Columns.b] :?> int)
293+
Assert.Equal(3, r.[t.Columns.c] :?> int)
294+
// getting value same way as a plain datatable still yields DBNull
295+
Assert.Equal(DBNull.Value, r.[t.Columns.d] :?> DBNull)
296+
297+
298+
[<Fact>]
299+
member __.``Can use DataColumnCollection`` () =
300+
301+
let table = (new GetArbitraryDataAsDataTable()).Execute()
302+
303+
let columnCollection : System.Data.DataColumnCollection =
304+
// this is more involved than just doing table.Columns because
305+
// DataColumnCollection is a sealed class, and the generative TP
306+
// attaches properties to a fake inherited type
307+
// In order to get a real DataColumnCollection, just use the table
308+
// as a normal DataTable.
309+
let table : System.Data.DataTable = table :> _
310+
table.Columns
311+
Assert.Equal(table.Columns.Count, columnCollection.Count)
312+
313+
[<Fact>]
314+
member __.``Can use datacolumns on SqlProgrammabilityProvider`` () =
315+
let products = AdventureWorks.Production.Tables.Product()
316+
317+
let product = products.NewRow()
318+
product.Name <- "foo"
319+
320+
// use as plain DataColumns
321+
let name = product.[products.Columns.Name] :?> string
322+
Assert.True(product.Name = name)

src/SqlClient/DesignTime.fs

Lines changed: 65 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -147,35 +147,82 @@ type DesignTime private() =
147147
@@>
148148
recordType.AddMember ctor
149149

150-
recordType
150+
recordType
151+
152+
static member internal GetDataRowPropertyGetterAndSetterCode (column: Column) =
153+
let name = column.Name
154+
if column.Nullable then
155+
let getter = QuotationsFactory.GetBody("GetNullableValueFromDataRow", column.TypeInfo.ClrType, name)
156+
let setter = QuotationsFactory.GetBody("SetNullableValueInDataRow", column.TypeInfo.ClrType, name)
157+
getter, setter
158+
else
159+
let getter = QuotationsFactory.GetBody("GetNonNullableValueFromDataRow", column.TypeInfo.ClrType, name)
160+
let setter = QuotationsFactory.GetBody("SetNonNullableValueInDataRow", column.TypeInfo.ClrType, name)
161+
getter, setter
151162

152163
static member internal GetDataRowType (columns: Column list) =
153164
let rowType = ProvidedTypeDefinition("Row", Some typeof<DataRow>)
154165

155-
columns |> List.mapi( fun i col ->
156-
let name = col.Name
157-
if name = "" then failwithf "Column #%i doesn't have name. Only columns with names accepted. Use explicit alias." (i + 1)
166+
columns |> List.mapi(fun i col ->
167+
168+
if col.Name = "" then failwithf "Column #%i doesn't have name. Only columns with names accepted. Use explicit alias." (i + 1)
158169

159170
let propertyType = col.ClrTypeConsideringNullable
160-
if col.Nullable
161-
then
162-
let property = ProvidedProperty(name, propertyType, GetterCode = QuotationsFactory.GetBody("GetNullableValueFromDataRow", col.TypeInfo.ClrType, name))
163-
if not col.ReadOnly
164-
then property.SetterCode <- QuotationsFactory.GetBody("SetNullableValueInDataRow", col.TypeInfo.ClrType, name)
165-
property
166-
else
167-
let property = ProvidedProperty(name, propertyType, GetterCode = (fun args -> <@@ (%%args.[0] : DataRow).[name] @@>))
168-
if not col.ReadOnly
169-
then property.SetterCode <- fun args -> <@@ (%%args.[0] : DataRow).[name] <- %%Expr.Coerce(args.[1], typeof<obj>) @@>
170-
property
171+
172+
let getter, setter = DesignTime.GetDataRowPropertyGetterAndSetterCode col
173+
let property = ProvidedProperty(col.Name, propertyType, GetterCode = getter)
174+
175+
if not col.ReadOnly then
176+
// only expose a setter if the column is not readonly
177+
// note: if this is an issue, we always expose a SetValue method on the typed DataColumn itself
178+
property.SetterCode <- setter
179+
180+
property
171181
)
172182
|> rowType.AddMembers
173183

174184
rowType
175185

176-
static member internal GetDataTableType dataRowType =
186+
static member internal GetDataTableType typeName dataRowType (outputColumns: Column list) =
177187
let tableType = ProvidedTypeBuilder.MakeGenericType(typedefof<_ DataTable>, [ dataRowType ])
178-
let tableProvidedType = ProvidedTypeDefinition("Table", Some tableType)
188+
let tableProvidedType = ProvidedTypeDefinition(typeName, Some tableType)
189+
190+
let columnsType = ProvidedTypeDefinition("Columns", Some typeof<DataColumnCollection>)
191+
192+
let ctor = ProvidedConstructor([])
193+
ctor.InvokeCode <- fun args ->
194+
<@@
195+
()
196+
@@>
197+
columnsType.AddMember ctor
198+
let columnsProperty = ProvidedProperty("Columns", columnsType)
199+
tableProvidedType.AddMember columnsType
200+
201+
columnsProperty.GetterCode <-
202+
fun args ->
203+
<@@
204+
let table : DataTable<DataRow> = %%args.[0]
205+
table.Columns
206+
@@>
207+
208+
tableProvidedType.AddMember columnsProperty
209+
210+
for column in outputColumns do
211+
let propertyType = ProvidedTypeDefinition(column.Name, Some typeof<DataColumn>)
212+
let property = ProvidedProperty(column.Name, propertyType)
213+
214+
property.GetterCode <-
215+
fun args ->
216+
let columnName = column.Name
217+
<@@
218+
let columns : DataColumnCollection = %%args.[0]
219+
let column = columns.[columnName]
220+
column
221+
@@>
222+
223+
columnsType.AddMember property
224+
columnsType.AddMember propertyType
225+
179226
tableProvidedType
180227

181228
static member internal GetOutputTypes (outputColumns: Column list, resultType, rank: ResultRank, hasOutputParameters) =
@@ -188,7 +235,7 @@ type DesignTime private() =
188235
elif resultType = ResultType.DataTable
189236
then
190237
let dataRowType = DesignTime.GetDataRowType outputColumns
191-
let dataTableType = DesignTime.GetDataTableType dataRowType
238+
let dataTableType = DesignTime.GetDataTableType "Table" dataRowType outputColumns
192239

193240
// add .Row to .Table
194241
dataTableType.AddMember dataRowType

src/SqlClient/QuotationsFactory.fs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,4 +119,10 @@ type QuotationsFactory private() =
119119
static member GetMapperWithNullsToOptions(nullsToOptions, mapper: obj[] -> obj) =
120120
fun values ->
121121
nullsToOptions values
122-
mapper values
122+
mapper values
123+
124+
static member private GetNonNullableValueFromDataRow<'T>(exprArgs : Expr list, name: string) =
125+
<@ (%%exprArgs.[0] : DataRow).[name] @>
126+
127+
static member private SetNonNullableValueInDataRow<'T>(exprArgs : Expr list, name : string) =
128+
<@ (%%exprArgs.[0] : DataRow).[name] <- %%Expr.Coerce(exprArgs.[1], typeof<obj>) @>

src/SqlClient/SqlClientProvider.fs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -304,8 +304,8 @@ type public SqlProgrammabilityProvider(config : TypeProviderConfig) as this =
304304
//remove from PK if default value provided by DB on insert.
305305
else c
306306
)
307-
|> Seq.toArray
308-
307+
|> Seq.toList
308+
309309

310310
//type data row
311311
let dataRowType = ProvidedTypeDefinition("Row", Some typeof<DataRow>)
@@ -349,7 +349,7 @@ type public SqlProgrammabilityProvider(config : TypeProviderConfig) as this =
349349
dataRowType.AddMember property
350350

351351
//type data table
352-
let dataTableType = ProvidedTypeDefinition(tableName, baseType = Some( typedefof<_ DataTable>.MakeGenericType(dataRowType)))
352+
let dataTableType = DesignTime.GetDataTableType tableName dataRowType columns
353353
tagProvidedType dataTableType
354354
dataTableType.AddMember dataRowType
355355

@@ -361,7 +361,7 @@ type public SqlProgrammabilityProvider(config : TypeProviderConfig) as this =
361361
ctor.InvokeCode <- fun _ ->
362362
let serializedSchema =
363363
columns
364-
|> Array.map (fun x ->
364+
|> List.map (fun x ->
365365
let nullable = x.Nullable || x.HasDefaultConstraint
366366
sprintf "%s\t%s\t%b\t%i\t%b\t%b\t%b"
367367
x.Name x.TypeInfo.ClrTypeFullName nullable x.MaxLength x.ReadOnly x.Identity x.PartOfUniqueKey

0 commit comments

Comments
 (0)