0

From the MSDN documentation I understand that if Run is implemented it will be called automatically at the end of the computational expression. It says that:

builder.Run(builder.Delay(fun () -> {| cexpr |}))

will be generated for the computational expression. Run and/or Delay will be omitted if they are not defined in the workflow builder. I was expecting my ReaderBuilder to return a list of MyItem objects when Run is called automatically. So I do not understand why I'm getting a type mismatch error. The errors are generated by the return statement inside the ProcedureBuilder foo at the end of my code listing here. Could someone please explain what I'm misunderstanding about workflow builders and what I have implemented incorrectly?

I'm getting the following errors:

The type ''a list' is not compatible with the type 'ReaderBuilder'

Type constraint mismatch. The type 'a list is not compatible with type ReaderBuilder The type ''a list' is not compatible with the type 'ReaderBuilder'

open System
open System.Data
open System.Data.Common
open System.Configuration

let config = ConfigurationManager.ConnectionStrings.Item("db")
let factory = DbProviderFactories.GetFactory(config.ProviderName)

type Direction =
    | In
    | Out
    | Ref
    | Return

type dbType =
    | Int32
    | String of int


type ReaderBuilder(cmd) =
    let mutable items = []
    member x.Foo = 2

    member x.YieldFrom item =
        items <- item::items
        item

    member x.Run item =
        items


type ProcBuilder(procedureName:string) =
    let name = procedureName
    let mutable parameters = []
    let mutable cmd:DbCommand = null
    let mutable data = []

    member x.Command with get() = cmd

    member x.CreateCommand() =
        factory.CreateCommand()
    member x.AddParameter(p:string*dbType*Direction) =
        parameters <- p::parameters

    member x.Bind(v,f) =
        f v

    member x.Reader = ReaderBuilder(cmd)

    member x.Return(rBuilder:ReaderBuilder) =
        data


let (?<-) (builder:ProcBuilder) (prop:string) (value:'t) =
    builder.Command.Parameters.[prop].Value <- value


type MyItem() =
    let mutable _a = 0
    let mutable _b = String.Empty
    let mutable _c = DateTime.Now

    member x.a
        with get() = _a
        and set n = _a <- n
    member x.b
        with get() = _b
        and set n  = _b <- n
    member x.c
        with get() = _c
        and set n = _c <- n


let proc name = ProcBuilder(name)

let (%) (builder:ProcBuilder) (p:string*dbType*Direction) =
    builder.AddParameter(p)
    builder

let (?) (r:DbDataReader) (s:string) = r.GetOrdinal(s)
let foo x y = 
    let foo = proc "foo" % ("x", Int32, In) % ("y", String(15), In)
    foo?x <- x
    foo?y <- y

    foo {
        do! foo?x <- x
        do! foo?y <- y
        return foo.Reader {
            let item = MyItem()
            item.a <- r.GetInt32("a")
            item.b <- r.GetString("b")
            item.c <- r.GetDateTime("c")
            yield! item
        }
    }
Charles Lambert
  • 5,042
  • 26
  • 47

1 Answers1

2

The problem in your example is that the foo.Reader { ... } block has a return type MyItem list (because this is what the Run member of the ReaderBuilder type returns). However, the Return member of ProcBuilder expects an argument of type ReaderBuilder.

The data field of ReaderBuilder will be always an empty list, so this is also suspicious. I think you probably want to change the Return of ProcBuilder to take an argument MyItem list instead.

However, I think that using custom computation builder for database access doesn't really give you much advantage. You're not creating a "non-standard computation" in some sense. Instead, you probably just want a nice syntax for calling commands & reading data. Using the dynamic operator can make this quite elegant even without computation builders - I wrote an article about this some time ago.

Tomas Petricek
  • 240,744
  • 19
  • 378
  • 553
  • I know that the list will be empty. There are lots of other missing parts. I just was trying to get it to the point of compiling. I'm going to add in all the implementation after I can get it to compile. – Charles Lambert May 15 '11 at 00:53
  • I understand what you have said, but I think it will take me a bit to figure out the best way to get around the problem. – Charles Lambert May 15 '11 at 00:56
  • you may be right about this not being the best use for a computational expression, but I sure am learning a lot :) – Charles Lambert May 15 '11 at 01:02
  • BTW: A computation builder doesn't usually have any private state. Trying to design it such taht a private (mutable) state is not needed is a good exercise that helps you understand how computation builders should work and it also helps with figuring out what the best design is. In general, you should first design the "type" created by computation builders. I wrote about this in Chapter 12 of my book. The chapter is available as a free sample: http://www.manning.com/petricek/SampleChapter12.pdf – Tomas Petricek May 15 '11 at 02:33
  • I have that book on my amazon wish list already. Hopefully I will have the money to buy it in June. It is really nice to be able to get advice from someone with your level of knowledge. I appreciate all of it. – Charles Lambert May 15 '11 at 04:13