10

I have a datareader and i want to return collection of rows from it, after reading books for like a day i am not able to find out best way to do it in f#. I can do it normal C# way in F# but that is not why i am using f#

Here is what i am trying to achieve

let values =
    while reader.Read() do
        yield reader.GetString(0), reader.GetInt64(1)

Above is how i am trying to do

  • all values get collected into values, which could be dictinary or tuples or any collection
  • yield can't be used in while loop but that is what i am trying to do

What could be the best way to achieve this

5 Answers 5

17

F# also provides list comprehension for arrays and sequences.

let records_as_list = 
    [
        while reader.Read() 
            do yield (reader.GetString(0), reader.GetInt64(1)) 
    ]

let records_as_array = 
    [|
        while reader.Read() 
            do yield (reader.GetString(0), reader.GetInt64(1)) 
    |]

let records_as_sequence = 
    seq {
        while reader.Read() 
            do yield (reader.GetString(0), reader.GetInt64(1)) 
    }

F# has a convenient dictionary function built in called dict

let records_as_IDictionary = dict records_as_sequence
Sign up to request clarification or add additional context in comments.

Comments

7

You can use sequence expressions to implement enumerators:

let records = seq { while reader.NextResult() do yield (reader.GetString(0), reader.GetInt64(1)) }

If you need more than two fields, you can yield maps of column index (or name) -> field values (untested code):

let dbSchema = reader.GetSchemaTable()

let makeMap (rdr : IDataReader) (schema : DataTable) =
    schema.Columns |> Seq.cast<DataColumn> |> Seq.map (fun col -> (col.ColumnName, rdr.[col.ColumnName])) |> Map.ofSeq

let records = seq { while reader.NextResult() do yield (makeMap reader dbSchema) }

2 Comments

Problem with seq is that it does lazy loading so when you want to read it in this case reader would be closed
Easily fixed by appending |> Seq.toList to the last row to materialise the collection.
3

For this kind of task, I'd like to first transfer the input into a sequence of strings.

.Net 4.0 provides ReadLines, the type of its return value is seq<string>:

   open System.IO
   let readLinesSeq = File.ReadLines

In lower versions of .Net, one needs to implement this function:

   let readLines filePath = seq {
    use sr = new StreamReader (filePath)
    while not sr.EndOfStream do
     yield sr.ReadLine ()
   }

1 Comment

Yes, the basic idea is the same.
2

I might approach this problem more generically by adding an extension property to IDataReader that turns any datareader into a seq<IDataRecord>...

[<AutoOpen>]
module DataReaderEx =
    open System.Data

    type IDataReader with
        member this.toSeq =
            seq { while this.Read() do yield this :> IDataRecord }

This would allow you to use ordinary functions from the Seq module on any datareader:

reader.toSeq
|> Seq.map (fun row -> row.GetString(0), row.GetInt64(1))

Comments

0

Well we are not talking about files here, however, in earlier .NET versions you can do (with small files):

reader.ReadToEnd().Split(System.Environment.NewLine.ToCharArray())

Where reader is a TextReader.

2 Comments

If the file is big, reading all the content and splitting should be avoided.
In this question, reader is actually an IDataReader, not a TextReader

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.