Replacing Entity Framework Classes With Record Types

I recently have been working on a mixed-language project (C# and F#) where the original implementation used the Entity Framework reverse POCO generator found here.  If you are not familiar, this Visual Studio add-in uses a T4 template to inspect a database and generate Entity Framework classes based on the tables that are located in the database.  The generator created two files for a given table: the entity and a class for configuration.

Capture

At the time, it probably was the best way to generate the hundreds of classes that were needed for the C# project.  However, now that we are introducing F# to the code base, it made sense to use the right tool for the job.

AFAIK, there are two ways you can use F# to do ORM.  Way #1 is to use type providers and way #2 is to have record types and hand-roll the connectivity.  I would have preferred to use a type provider to expose the database types in two lines of code, but since these types are being used by other C# and VB.NET projects, this was not possible.

Option 2 was to create record types.  Instead of hand writing the types, I decided to create a quick script that turns C# classes into F# types.  Since the each individual C# class was in its own file, the script traverses a directory and pull all of the C# files:

1 open System 2 open System.IO 3 open System.Collections.Generic 4 5 let path = @"C:\Git\..." 6 let folderInfo = System.IO.DirectoryInfo(path) 7 let files = folderInfo.GetFiles("*.cs")

Within each file, I needed to get both the type name and then the properties.  Getting the name was a text search for “public class” and the attributes was “get;set”

1 let parseClass (values: IEnumerable<string>) = 2 let className = 3 values 4 |> Seq.filter(fun l -> l.Contains("public class")) 5 let typeName = 6 match className |> Seq.length with 7 | 0 -> None 8 | _ -> Some (className |> Seq.head) 9 let propNames = 10 values 11 |> Seq.filter(fun l -> l.Contains("{ get; set; }")) 12 typeName, propNames

With the parsed values (1 class name and an array of attributes), I could then create the type name and the type attributes:

1 let createTypeName (className:string option) = 2 match className with 3 | Some cn -> 4 let typeName = cn.Replace(" public class"," ").Trim() 5 match(typeName.Contains("Configuration")) with 6 | true -> None 7 | false -> Some typeName 8 | None -> None 9 10 let reverseValues (typeAttribute:string) = 11 let tokens = typeAttribute.Split(' ') 12 match tokens.Length with 13 | 0 | 1 -> "" 14 | _ -> tokens.[1] + ":" + tokens.[0] 15 16 let createTypeAttributes (items: IEnumerable<string>) = 17 let temp = 18 items 19 |> Seq.map(fun i -> i.Replace("public","")) 20 |> Seq.map(fun i -> i.Replace("{ get; set; }","")) 21 |> Seq.map(fun i -> i, i.IndexOf("//")) 22 |> Seq.filter(fun (i,t) -> t > 0) 23 |> Seq.map(fun (i,t) -> i.Substring(0,t)) 24 |> Seq.map(fun i -> i.Trim()) 25 |> Seq.map(fun i -> reverseValues i) 26 match Seq.length temp with 27 | 0 -> "" 28 | _ -> Seq.reduce(fun acc elem -> acc + ";" + elem) temp

With those values, set, I could then create the types:

1 let createType (values:string option * IEnumerable<string>) = 2 createTypeName(fst values), 3 createTypeAttributes (snd values)

and then it was just a matter of putting it all together:

1 let printType (typeName:string) (typeAttributes: string) = 2 printfn "type %s {%s}" typeName typeAttributes 3 4 files 5 |> Array.map(fun f -> f.FullName) 6 |> Array.map(fun fn -> fn, File.ReadLines(fn)) 7 |> Array.map(fun (fn,c) -> parseClass c) 8 |> Array.map(fun x -> createType x) 9 |> Array.filter(fun (x,y) -> x.IsSome) 10 |> Array.map(fun (x,y) -> x.Value, y) 11 |> Array.iter(fun (x,y) -> printType x y)

With these values, I could create a single file and have all of my domain objects in 1 place – with about 95% less noise code.

Git is here