The best way to determine the type of variable and treat each of them differently in F #


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 = 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

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?

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)