2010-06-21 20 views
19

Estoy empezando a aprender F #. Anoche escribí este código F #/ADO.NET. ¿De qué manera mejorarías la sintaxis? ¿Te sentirías como idiomático F #?F # y ADO.NET - idiomático F #

let cn = new OleDbConnection(cnstr) 
    let sql = "SELECT * FROM People" 
    let da = new OleDbDataAdapter(new OleDbCommand(sql, cn)) 
    let ds = new DataSet() 
    cn.Open() 
    let i = da.Fill(ds) 
    let rowCol = ds.Tables.[0].Rows 
    let rowCount = rowCol.Count 
    printfn "%A" rowCount 

    for i in 0 .. (rowCount - 1) do 
     let row:DataRow = rowCol.[i] 
     printfn "%A" row.["LastName"] 

Nota: me pareció el comprobador de sintaxis no le gustaba Fila Col [i] [ "Apellido"] Cuál es la forma correcta de manejar los dual-indexadores..? Tuve que dividir el código en dos líneas.

También Si no hubiera bajado por la ruta del conjunto de datos y hubiera utilizado un SqlDataReader que haya cargado sus datos en los registros F #. ¿Qué estructura de cobro debo usar para contener los registros? La lista .NET estándar <>?

+0

No estoy seguro Entiendo la pregunta. – BuddyJoe

+1

Quiso decir que la tarea en cuestión (operaciones DB con bibliotecas .NET) necesariamente termina en un código imperativo, por lo que F # no brilla allí. Sin embargo, puede ser muy adecuado para procesar los datos una vez que lo saca de la base de datos. – Mau

+0

@tyndall Mau es 100% correcto. tu código es 100% imperativo. puede convertirlo a C# utilizando FindReplace. Creo que F # podría no ser la mejor herramienta aquí. – Andrey

Respuesta

30

La parte clave de sus ofertas de código con la API .NET que no es funcional, lo que no hay manera de hacer esta parte del código particular más idiomática o más agradable. Sin embargo, la clave en la programación funcional es abstracción, por lo que puede ocultar este código (feo) en alguna función idiomática y reutilizable.

Para representar colecciones de datos en F #, puede usar el tipo de lista F # estándar (que es bueno para el procesamiento de datos funcionales) o seq<'a> (que es .NET IEnumerable<'a> estándar bajo la cubierta), que funciona bien cuando se trabaja con otros Bibliotecas .NET.

Dependiendo de cómo se accede a la base de datos en otra parte de su código, la siguiente podría funcionar:

// Runs the specified query 'sql' and formats rows using function 'f' 
let query sql f = 
    // Return a sequence of values formatted using function 'f' 
    seq { use cn = new OleDbConnection(cnstr) // will be disposed 
     let da = new OleDbDataAdapter(new OleDbCommand(sql, cn)) 
     let ds = new DataSet() 
     cn.Open() 
     let i = da.Fill(ds) 
     // Iterate over rows and format each row 
     let rowCol = ds.Tables.[0].Rows 
     for i in 0 .. (rowCount - 1) do 
      yield f (rowCol.[i]) } 

Ahora puede utilizar la función de query para escribir su código original más o menos así:

let names = query "SELECT * FROM People" (fun row -> row.["LastName"]) 
printfn "count = %d" (Seq.count names) 
for name in names do printfn "%A" name 

// Using 'Seq.iter' makes the code maybe nicer 
// (but that's a personal preference): 
names |> Seq.iter (printfn "%A") 

Otro ejemplo que podría escribir es:

// Using records to store the data 
type Person { LastName : string; FirstName : string } 
let ppl = query "SELECT * FROM People" (fun row -> 
    { FirstName = row.["FirstName"]; LastName = row.["LastName"]; }) 

let johns = ppl |> Seq.filter (fun p -> p.FirstName = "John") 

BTW: En cuanto a la sugerencia por Mau No utilizaría excesivamente las funciones de orden superior si hay una forma más directa de escribir el código utilizando construcciones de lenguaje como for. El ejemplo con iter arriba es bastante simple y algunas personas lo encontrarán más legible, pero no hay una regla general ...

+1

Si entiendes primero el ejemplo correctamente, el seq {} hará que se vuelva a consultar la base de datos, ¿correcto? y su línea "let ppl =" evita el problema de mutabilidad al establecerlo en una secuencia. ¿Estoy en el camino correcto aquí? – BuddyJoe

+1

@tyndall: ¡Ese es un buen punto! La secuencia se volverá a evaluar (y se consultará nuevamente la base de datos) cada vez que use la secuencia (por ejemplo, usando 'Seq.count' o' for'). Esto es un poco desafortunado y 'let ppl = ..' no evita eso. Sin embargo, puede escribir 'let ppl = ... |> Seq.cache' o por ejemplo' List.ofSeq' para ejecutar la consulta y obtener el resultado como lista. –

+0

Me gusta la idea de List.ofSeq. +1 – BuddyJoe

4

Bueno, no hay mucho que pueda cambiar en el primer bit, pero cada vez que está procesando colecciones de datos como en las últimas filas, puede usar las funciones incorporadas Seq, List, Array.

for i in 0 .. (rowCount - 1) do 
    let row:DataRow = rowCol.[i] 
    printfn "%A" row.["LastName"] 

=

rowCol |> Seq.cast<DataRow> 
     |> Seq.iter (fun row -> printfn "%A" row.["LastName"]) 
7

Escribí un functional wrapper over ADO.NET for F#. Con esta biblioteca, su ejemplo se ve así:

let openConn() = 
    let cn = new OleDbConnection(cnstr) 
    cn.Open() 
    cn :> IDbConnection 

let query sql = Sql.execReader (Sql.withNewConnection openConn) sql 

let people = query "select * from people" |> List.ofDataReader 
printfn "%d" people.Length 
people |> Seq.iter (fun r -> printfn "%s" (r?LastName).Value)