If you want to use an approach based on discriminated unions, then I think the declaration is more suitable (as you don't need to manipulate with quotations). Slight modification of the type suggested by Alex is:
type PropertyInfo<'a> =
| String of Expr<'a -> string>
| Date of Expr<'a -> System.DateTime>
| ...
Then you'd write something like:
let property (pi:PropertyInfo<'a>) (cfg:EntityInfo<'a>) =
match pi with
| String e -> cfg.Property e
| ...
cfg |> property (String <@ fun e -> e.Foo @>)
Another option would be to implement property
as a static member of a type, in which case you can use usual overloading (similar to C#). Something like:
type EF =
static member property (expr:Expr<'a -> string>) (cfg:EntityInfo<'a>) =
cfg.Property expr
static member property (expr:Expr<'a -> System.DateTime>) (cfg:EntityInfo<'a>) =
cfg.Property expr
static member property (expr:Expr<'a -> System.Byte>) (cfg:EntityInfo<'a>) =
cfg.Property expr
Then you'd write:
cfg |> EF.property <@ e -> e.Foo @>
Finally, you could also make it a bit simpler (but less safer) by making the function fully generic and doing dynamic type test (to decide what return type is used). Something like:
let property<'a, 'r> (e:Expr<'a -> 'r>) (cfg:EntityInfo<'a>) =
if typeof<'r> = typeof<string> then
// ...