F# exception handling multiple “Tries”

笑着哭i 提交于 2019-12-24 05:34:15

问题


I'm trying to read a bunch of csv files in SQL Server using SQL Bulk Insert and DataContext.ExecuteCommand. (Maybe this isn't the best way to do it, but it does allow me stay in the Type Provider context--as opposed to with SqlBulkCopy I think.) Now the upload is glitchy with intermittent success. Some files read in, some fail with "Data conversion error (truncation)". I think this has to do with the row terminators not always working.

When the upload works, it seems to be with the '0x0A' terminator. But when that fails, I want to try repeatedly again with other row terminators. So I want to go into a Try statement, and on failure go into another Try statement, and another if that one fails, ... . This may not be the best way to upload, but I am still curious about the Try logic for it's own state.

Here's what I've come up with so far and it's not too pretty (but it works). Cutting out a few nested layers:

let FileRead path = 

    try
        db.DataContext.ExecuteCommand(@"BULK INSERT...ROWTERMINATOR='0x0A')") |> ignore 
        true
    with
        | exn -> 
            try
                db.DataContext.ExecuteCommand(@"BULK INSERT...ROWTERMINATOR='\r')") |> ignore 
                true
            with
                | exn -> 
                    try
                        db.DataContext.ExecuteCommand(@"BULK INSERT...ROWTERMINATOR='\n')") |> ignore 
                        true
                    with
                        | exn -> 
                            false

This doens't feel right but I haven't figured out any other syntax.

EDIT: What I ended up doing, just for the record. Appreciate being put on a productive path. There's plenty to improve in this. With one of the more significant things being to use Async's and run it Parallel (which I have gotten experience with in other sections).

type dbSchema = SqlDataConnection<dbConnection>
let db = dbSchema.GetDataContext() 

let TryUpLd table pathFile rowTerm = 
    try
        db.DataContext.ExecuteCommand( @"BULK INSERT " + table + " FROM '" + pathFile + 
                                       @"' WITH (FIELDTERMINATOR=',', FIRSTROW = 2, ROWTERMINATOR='" 
                                       + rowTerm + "')" ) |> ignore
        File.Delete (pathFile)  |> Some
    with
        | exn -> None

let NxtUpLd UL intOpt =
    match intOpt with
    | None -> UL
    | _ -> intOpt

let MoveTable ID table1 table2 = 
    //...
    ()

let NxtMoveTable MT intOpt = 
    match intOpt with
    | Some i -> MT
    | _ -> ()

let UpLdFile path (file:string) = 
    let (table1, table2) = 
        match path with
        | p when p = dlXPath   -> ("Data.dbo.ImportXs", "Data.dbo.Xs")
        | p when p = dlYPath -> ("Data.dbo.ImportYs", "Data.dbo.Ys")
        | _ -> ("ERROR path to tables", "")        

    let ID = file.Replace(fileExt, "")

    let TryRowTerm = TryUpLd table1 (path + file)

    TryRowTerm "0x0A" 
    |> NxtUpLd (TryRowTerm "\r")
    |> NxtUpLd (TryRowTerm "\n")
    |> NxtUpLd (TryRowTerm "\r\n")
    |> NxtUpLd (TryRowTerm "\n\r")
    |> NxtUpLd (TryRowTerm "\0")
    |> NxtMoveTable (MoveTable ID table1 table2) 


let UpLdData path = 
    let dir = new DirectoryInfo(path)
    let fileList = dir.GetFiles()

    fileList |> Array.iter (fun file -> UpLdFile path file.Name ) |> ignore

回答1:


Here's one way to do it, using monadic composition.

First, define a function that takes another function as input, but converts any exception to a None value:

let attempt f =
    try f () |> Some
    with | _ -> None

This function has the type (unit -> 'a) -> 'a option; that is: f is inferred to be any function that takes unit as input, and returns a value. As you can see, if no exception happens, the return value from invoking f is wrapped in a Some case. The attempt function suppresses all exceptions, which you shouldn't normally do.

Next, define this attemptNext function:

let attemptNext f = function
    | Some x -> Some x
    | None -> attempt f

This function has the type (unit -> 'a) -> 'a option -> 'a option. If the input 'a option is Some then it's simply returned. In other words, the value is interpreted as already successful, so there's no reason to try the next function.

Otherwise, if the input 'a option is None, this is interpreted as though the previous step resulted in a failure. In that case, the input function f is attempted, using the attempt function.

This means that you can now compose functions together, and get the first successful result.

Here are some functions to test with:

let throwyFunction () = raise (new System.InvalidOperationException("Boo"))
let throwyFunction' x y = raise (new System.InvalidOperationException("Hiss"))
let goodFunction () = "Hooray"
let goodFunction' x y = "Yeah"

Try them out in F# Interactive:

> let res1 =
    attempt throwyFunction
    |> attemptNext (fun () -> throwyFunction' 42 "foo")
    |> attemptNext goodFunction
    |> attemptNext (fun () -> goodFunction' true 13.37);;

val res1 : string option = Some "Hooray"

> let res2 =
    attempt goodFunction
    |> attemptNext throwyFunction
    |> attemptNext (fun () -> throwyFunction' 42 "foo")
    |> attemptNext (fun () -> goodFunction' true 13.37);;

val res2 : string option = Some "Hooray"

> let res3 =
    attempt (fun () -> throwyFunction' 42 "foo")
    |> attemptNext throwyFunction    
    |> attemptNext (fun () -> goodFunction' true 13.37)
    |> attemptNext goodFunction;;

val res3 : string option = Some "Yeah"

> let res4 =
    attempt (fun () -> throwyFunction' 42 "foo")
    |> attemptNext (fun () -> goodFunction' true 13.37)
    |> attemptNext throwyFunction    
    |> attemptNext goodFunction;;

val res4 : string option = Some "Yeah"


来源:https://stackoverflow.com/questions/32662563/f-exception-handling-multiple-tries

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!