Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When do I need to call the "ConvertToGenerated" member to generate types using a type provider

I'm having a hard time deciphering the "Providing Generated Types" section of the Type Provider Tutorial. The tutorial provides the following specification.

"You must also call ConvertToGenerated on a root provided type whose nested types form a closed set of generated types. This call emits the given provided type definition and its nested type definitions into an assembly and adjusts the Assembly property of all provided type definitions to return that assembly. The assembly is emitted only when the Assembly property on the root type is accessed for the first time. The host F# compiler does access this property when it processes a generative type declaration for the type."

I do not know where to place the ConvertToGenerated call and I'm not sure on the requirements of the assembly file name parameter. Can someone provide an example? Thanks.

like image 529
Arthur Greef Avatar asked Apr 17 '12 19:04

Arthur Greef


1 Answers

After some help from the F# team I solved my problem. This is what I did.

namespace Types

open System
open System.Data
open System.IO
open System.Linq
open System.Data.Linq
open Microsoft.FSharp.Data.TypeProviders
open Microsoft.FSharp.Linq
open Microsoft.FSharp.TypeProvider.Emit
open Microsoft.FSharp.Core.CompilerServices

type DatabaseSchema = 
    SqlDataConnection<"Data Source=(local);Initial Catalog=Test;Integrated Security=SSPI;">

[<TypeProvider>] 
type public MeasureTypeProvider(cfg:TypeProviderConfig) as this = 
inherit TypeProviderForNamespaces()

let assembly = System.Reflection.Assembly.GetExecutingAssembly()
let typesNamespace = "Types.Domain"
let providedTypeBuilder = ProvidedTypeBuilder.Default
let db = DatabaseSchema.GetDataContext()

let types =
    query { for m in db.Table do select m }
    |> Seq.map(fun dataEntity ->
                    let className:string = dataEntity.Identifier
                    let providedTypeDefinition =
                            ProvidedTypeDefinition(className = className,
                                                   baseType = Some typeof<obj>,
                                                   IsErased=false)
                    providedTypeDefinition.AddMember(
                                ProvidedConstructor([], InvokeCode = fun [] -> <@@ obj() @@>))
                    providedTypeDefinition
               ) |> Seq.toList

let rootType =
    let providedTypeDefinition = 
           ProvidedTypeDefinition(assembly, 
                                  typeNamespace, 
                                  "DomainTypes", 
                                  Some typeof<obj>, 
                                  IsErased=false)
    providedTypeDefinition.AddMembersDelayed(fun () -> types)
    this.AddNamespace(typesNamespace, [providedTypeDefinition])
    providedTypeDefinition

let path = Path.GetDirectoryName(assembly.Location) + @"\GeneratedTypes.dll"
do rootMeasureType.ConvertToGenerated(path)

[<assembly:TypeProviderAssembly>] 
do()

The TypeProvider.Emit framework automatically cleans up the generated assembly. Comment out the following statement if you want it to stick around.

File.Delete assemblyFileName

One other gotcha I found is that while I was able to provide types that derive from value types (like decimal) when IsErased=true, I was not able to provide these derived types when IsErased=false. This is because value types are sealed so it is is not possible to generate a "real" type that derives from a value type.

like image 105
Arthur Greef Avatar answered Sep 25 '22 15:09

Arthur Greef