With the DatabaseBase and TableHelper classes, you still have to generate a CreateTable field per table. Why do it by hand? I wrote an F# script to generate the statements. I must say, I'm loving F# more than I had hoped. The more I learn, the better it gets. I've noticed (even in C#) that using a functional style generally means less errors. This script worked without bugs the first time (i.e., as soon as it compiled), which is pretty cool (granted, it's not big, but I'm sure if I had tons of for loops, I woulda messed up somewhere). Maybe this weekend I'll try writing it in C# just to compare (pretty sure it'll be more than 59 lines!). And I'll preempt comments about readability: Yes, it may be difficult to read if you don't know F#, but more on that later...
I'd love feedback as to making it more "functional"; years of imperative programming don't die quickly. Also, I'm not very happy with the definition of chooseAttr, but I can't seem to get it to infer the type I want otherwise.
Anyways, here's the code. You'll need to specify the references when compiling: -r "C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.5\System.Data.Linq.dll" -r "C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\System.Runtime.Serialization.dll"
1 // crudcreatetable.fsx: Generates LINQ CRUD table fields using the horribly named DatabaseBase code
2 //
3 // Tables look like: [Table(Name="dbo.Accounts")]
4 // Columns look like this:
5 // [Column(Storage="_AccountName", DbType="VarChar(128) NOT NULL", CanBeNull=false, IsPrimaryKey=true)]
6 // [DataMember(Order=1)] // Exists if serialization is turned on; used to order key parameters
7 // Emits:
8 // public static readonly TableHelper<Account, String> Accounts =
9 // CreateTable(dc => dc.Accounts, a => a.AccountName);
10
11 #light
12 open System
13 open System.Reflection
14 open System.Data.Linq.Mapping
15 open System.Runtime.Serialization
16
17 let getAttr<'target, 'a when 'a :> ICustomAttributeProvider> (ty : 'a) =
18 match List.of_array (ty.GetCustomAttributes(typeof<'target>, true)) with
19 | a::_ -> Some (a :?> 'target)
20 | [] -> None
21 let chooseAttr<'target, 'a when 'a :> ICustomAttributeProvider> (ty : 'a) =
22 match getAttr<'target,_> ty with
23 | Some(a) -> Some(ty,a)
24 | None -> None
25
26 let joinStrings (sep:string) items = items |> Seq.fold1 (fun acc x -> acc + sep + x)
27 let pluralize (name:string) = if name.EndsWith("s") then name else name + "s"
28
29 let generate(asmpath:string) =
30 let genTable (t:Type, tableName) =
31 let keyProps =
32 t.GetProperties()
33 |> Seq.choose (chooseAttr<ColumnAttribute,_>)
34 |> Seq.filter(fun (p,c) -> c.IsPrimaryKey)
35 |> Seq.map(fun (p,_) -> p, getAttr<DataMemberAttribute, _> p)
36 |> Seq.orderBy(function | _,Some(dm) -> dm.Order | _ -> 0)
37 |> Seq.map (fun (p,_) -> p)
38 let tw = new IO.StringWriter()
39 let pn fmt = Printf.twprintfn tw fmt
40 pn "public static readonly TableHelper<%s, %s> %s ="
41 t.Name
42 (joinStrings ", " (keyProps |> Seq.map (fun p -> p.PropertyType.Name)))
43 tableName
44 pn "\tCreateTable(dc => dc.%s, %s);"
45 tableName
46 (joinStrings ", " (keyProps |> Seq.map (fun p -> "a => a." + p.Name)))
47 pn ""
48 tw.ToString()
49
50 let asm = Assembly.LoadFrom asmpath // Don't use ReflectionOnly 'cause it won't resolve dependencies
51 asm.GetExportedTypes ()
52 |> Seq.choose (chooseAttr<TableAttribute,_>)
53 |> Seq.map (fun (t,ta) -> t, ta.Name.Replace("dbo.", "") |> pluralize)
54 |> Seq.orderBy (fun (t,_) -> t.Name)
55 |> Seq.map genTable
56 |> Seq.fold1 (+)
57
58 generate "C:\\yourlinq.dll"
59 |> Console.WriteLine
Remember Me