8

Replacing Entity Framework Classes With Record Types | Jamie Dixon's Home

 3 years ago
source link: https://jamessdixon.com/2018/08/31/replacing-entity-framework-classes-with-record-types/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Replacing Entity Framework Classes With Record Types

August 31, 2018 Leave a comment

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK