views:

113

answers:

2

I have a function that will create a select where clause, but right now everything has to be a string.

I would like to look at the variable passed in and determine what type it is and then treat it properly.

For example, numeric values don't have single quotes around them, option type will either be null or have some value and boolean will actually be zero or one.

member self.BuildSelectWhereQuery (oldUser:'a)  =  //'
    let properties = List.zip oldUser.ToSqlValuesList sqlColumnList 
    let init = false, new StringBuilder()
    let anyChange, (formatted:StringBuilder) = 
        properties |> Seq.fold (fun (anyChange, sb) (oldVal, name) ->
            match(anyChange) with
            | true -> true, sb.AppendFormat(" AND {0} = '{1}'", name, oldVal)
            | _ -> true, sb.AppendFormat("{0} = '{1}'", name, oldVal)
        ) init
    formatted.ToString()

Here is one entity:

type CityType() =
    inherit BaseType()
    let mutable name = ""
    let mutable stateId = 0
    member this.Name with get() = name and set restnameval=name <- restnameval
    member this.StateId with get() = stateId and set stateidval=stateId <- stateidval
    override this.ToSqlValuesList = [this.Name; this.StateId.ToString()]

So, if name was some other value besides a string, or stateId can be optional, then I have two changes to make:

  1. How do I modify ToSqlValuesList to have the variable so I can tell the variable type?
  2. How do I change my select function to handle this?

I am thinking that I need a new function does the processing, but what is the best FP way to do this, rather than using something like typeof?

+2  A: 

I think that one clear functional approach would be to define a data type that represents the various (more complicated situations) that you need to handle. You mentioned that a value may be optional and that you need to distinguish numeric and textual values (for the encoding to SQL).

You could define a discriminated union (if there are other cases that you'd like to handle, the definition may be a bit more complicated):

type SqlValue = 
  | Missing
  | Numeric of string
  | Textual of string

Note that the Textual case also carries string, because I assume that the client who produces the value takes care of converting it to string - this is only information for your SQL query generator (so that it knows whether it needs to add quotes).

Your ToSqlValuesList member would return a list of values string & SqlValue, so for example, a sample product could be represented using the following list:

columns = [ "Name"; "Price"; "Description" ]
values = [ Textual("Tea"); Numeric(10); Missing ]

In the code that generates the SQL query, you'd use pattern matching to handle all the different cases (most importantly, encode string to avoid SQL injection in case the value is Textual :-)).

EDIT You'd need to implement the conversion from the specific data types to the SqlValue representation in every client. However, this can be simplified by writing a utility type (using the fact that members can be overloaded):

type SqlValue with 
  static member From(a:int) = Numeric(a.ToString())
  static member From(a:int option) = 
    match a with None -> Missing | Some(n) -> SqlValue.From(n)
  // ... similarly for other types

In the implementation of ToSqlValuesList, you would write SqlValue.From(description) and it would deal with the details autoamtically.

A more sophisticated approach would be to annotate public members of the types representing your data entities with .NET attributes and use Reflection to extract the values (and their types) at runtime. This is more advanced, but quite elegant (there is a nice exmaple of this technique in Don Syme's Expert F# book)

Tomas Petricek
So with an option type I would need to use a conditional to properly set Description, in your example?
James Black
Once I get this working, I will use a hashmap to connect a prepared statement name to it's value, but it is simpler to ignore that at the moment.
James Black
I added some more info.
Tomas Petricek
I will look for the reflection in the book, as that is one of two F# books I have, the other being yours. :) Thank you.
James Black
@James: The sample is in Chapter 9 (Using reflection). It does similar thing with CSV files.
Tomas Petricek
+3  A: 

You can use a type test pattern in a match. Would this meet your needs?

let f (x : obj) =
    match x with
    | :? int -> "int"
    | :? string -> "string"
    | :? bool -> "bool"
    | _ -> "who knows?"
Jason
That is an interesting approach. I will try it and see how it works.
James Black
I should have pointed out before that you can bind the "cast" value to a name with an "as" pattern, like this:| :? int as i -> sprintf "the value is %d" i
Jason