<#@ assembly name="System.Core" #> <#@ assembly name="System.Data" #> <#@ assembly name="System.Data.Entity.Design" #> <#@ import namespace="System.Globalization" #> <#@ import namespace="System.IO" #> <#@ import namespace="System.Linq" #> <#@ import namespace="System.Text" #> <#@ import namespace="System.Collections.Generic" #> <#@ import namespace="System.Data.Entity.Design.PluralizationServices" #> <#@ output extension=".txt" #> <# EnvDTE.DTE Dte; //this relies on the nuget packages: T4EnvDte and T4MultiFile #> <#@ include file="MultipleOutputHelper.ttinclude" #> <#@ include file="EnvDteHelper.ttinclude" #> Main File Output <#+ public static void Generate(ITextTemplatingEngineHost host, EnvDTE.DTE dte, StringBuilder generationEnvironment, string targetProjectName, IEnumerable<string> tables, string cString, bool doMultiFile) { generationEnvironment.AppendLine(host.TemplateFile); Action<int,string> appendLine = (indentLevels, text) => generationEnvironment.AppendLine(String.Join(string.Empty,Enumerable.Repeat(" ",indentLevels)) + text); var useOptions = false; var manager = Manager.Create(host, generationEnvironment); var projects = RecurseSolutionProjects(dte); var targetProject = projects.First(p => p.Name==targetProjectName); var targetProjectFolder = Path.GetDirectoryName(targetProject.FullName); var pluralizer = PluralizationService.CreateService(new CultureInfo("en")); // https://msdn.microsoft.com/en-us/library/system.data.entity.design.pluralizationservices.pluralizationservice(v=vs.110).aspx generationEnvironment.AppendLine("Main file output"); foreach(var p in projects) { generationEnvironment.AppendLine(p.Name + " " + p.FullName); } using(var cn = new System.Data.SqlClient.SqlConnection(cString)) { cn.Open(); foreach(var tableName in tables) { manager.StartNewFile(Path.Combine(targetProjectFolder,tableName + ".generated.fs"),targetProject); var typeName = pluralizer.Singularize(tableName); var columns = new List<ColumnDescription>(); using(var cmd= new System.Data.SqlClient.SqlCommand("sp_help " + tableName,cn)) using(var r = cmd.ExecuteReader()) { r.NextResult(); // ignore the first tables while(r.Read()) { // columns and info var columnName = r["Column_name"].ToString(); var type = r["Type"].ToString(); // var computed = r["Computed"]; var length = Convert.ToInt32(r["Length"]); // var prec = r["Prec"]; columns.Add(new ColumnDescription{ColumnName=columnName, Type=type, Length=length, Nullable = r["Nullable"].ToString() =="yes"}); } } columns = new List<ColumnDescription>( columns.OrderBy(c => c.ColumnName)); generationEnvironment.AppendLine ("namespace Pm.Schema.DataModels." + pluralizer.Pluralize(typeName) + " // Generated by item in namespace " + manager.DefaultProjectNamespace ); generationEnvironment.AppendLine(string.Empty); generationEnvironment.AppendLine("open System"); generationEnvironment.AppendLine("open System.ComponentModel"); generationEnvironment.AppendLine("open System.Linq.Expressions"); generationEnvironment.AppendLine(string.Empty); GenerateInterface(typeName, columns, appendLine,writeable:false, useOptions:useOptions); GenerateInterface(typeName, columns, appendLine,writeable:true, useOptions:useOptions); GenerateRecord(typeName, columns, appendLine, useOptions); GenerateModule(typeName, columns, appendLine, useOptions); GenerateClass(typeName, columns, appendLine, useOptions); manager.EndBlock(); } } manager.Process(doMultiFile); } public static void GenerateInterface(string typeName, IEnumerable<ColumnDescription> columns, Action<int,string> appendLine, bool writeable, bool useOptions) { appendLine(0, TypeComment(columns.Count())); appendLine(0,"type I" + typeName + (writeable ? "RW" : string.Empty) + " ="); foreach(var cd in columns) { appendLine(1, "abstract member " + cd.ColumnName + ":" + MapSqlType(cd.Type, cd.Nullable, useOptions) + " with get" + (writeable ? ",set" : string.Empty)); } appendLine(0,string.Empty); } public static void GenerateRecord(string typeName, IEnumerable<ColumnDescription> columns, Action<int,string> appendLine, bool useOptions) { appendLine(0, TypeComment(columns.Count())); if (!useOptions) { appendLine(0,"[<NoComparison>]"); } appendLine(0, "type " + typeName + "Record ="); appendLine(1, "{"); foreach(var cd in columns) { appendLine(1, ColumnComment(cd)); appendLine(1, cd.ColumnName + ":" + MapSqlType(cd.Type,cd.Nullable,useOptions)); } appendLine(1,"}"); appendLine(1,"interface I" + typeName + " with"); foreach(var cd in columns ) { appendLine(2, ColumnComment(cd)); appendLine(2, "member x." + cd.ColumnName + " with get () = x." + cd.ColumnName); } appendLine(1,"static member Zero () = "); appendLine(2,"{"); foreach(var cd in columns ) { var mapped = MapSqlType(cd.Type,cd.Nullable,useOptions); appendLine(2, cd.ColumnName + " = " + GetDefaultValue(mapped)); } appendLine(2,"}"); appendLine(0,string.Empty); } public static void GenerateModule(string typeName, IEnumerable<ColumnDescription> columns, Action<int,string> appendLine, bool useOptions) { var camelType = toCamel(typeName); appendLine(0, "module " + typeName + "Helpers ="); appendLine(1, "let ToRecord (i" + typeName + ":I" + typeName + ") ="); appendLine(2, "{"); foreach(var cd in columns ) { var mapped = MapSqlType(cd.Type,cd.Nullable,useOptions); appendLine(2, cd.ColumnName + " = i" + typeName + "." + cd.ColumnName); } appendLine(2, "}"); appendLine(0,string.Empty); appendLine(1, "let toRecord " + camelType + " ="); appendLine(2, "{"); foreach(var cd in columns ) { var mapped = MapSqlType(cd.Type,cd.Nullable,useOptions); appendLine(2, cd.ColumnName + " = " + camelType + "." + cd.ColumnName); } appendLine(2, "}"); appendLine(0,string.Empty); appendLine(1, "let inline toRecordStp (" + camelType + ": ^a) ="); appendLine(2, "{"); foreach(var cd in columns ) { var mapped = MapSqlType(cd.Type,cd.Nullable,useOptions); appendLine(2, cd.ColumnName + " = (^a: (member " + cd.ColumnName + ": _) " + camelType + ")"); } appendLine(2, "}"); appendLine(0,string.Empty); } public static void GenerateClass(string typeName, IEnumerable<ColumnDescription> columns, Action<int,string> appendLine, bool useOptions) { appendLine(0, TypeComment(columns.Count())); appendLine(0, "type "+ typeName + "N (model:" + typeName + "Record) = "); appendLine(0, string.Empty); appendLine(1, "let propertyChanged = new Event<_, _>()"); appendLine(0, string.Empty); appendLine(0, string.Empty); foreach(var cd in columns) // https://fadsworld.wordpress.com/2011/05/18/f-quotations-for-inotifypropertychanged/ { var camel = toCamel(cd.ColumnName); appendLine(1, "let mutable "+ camel + " = model." + cd.ColumnName); } appendLine(0, string.Empty); appendLine(1, "interface I" + typeName + " with"); foreach(var cd in columns) { appendLine(2, "member x." + cd.ColumnName + " with get () = x." + cd.ColumnName); } appendLine(1, "interface I" + typeName + "RW with" ); foreach(var cd in columns) { appendLine(2, "member x." + cd.ColumnName + " with get () = x." + cd.ColumnName + " and set v = x." + cd.ColumnName + " <- v"); } appendLine(0, string.Empty); appendLine(1, "member x.MakeRecord () ="); appendLine(2, "{"); foreach(var cd in columns) { appendLine(2, cd.ColumnName + " = x." + cd.ColumnName); } appendLine(2, "}"); appendLine(0, string.Empty); appendLine(1, "interface INotifyPropertyChanged with"); appendLine(2, "[<CLIEvent>]"); appendLine(2, "member x.PropertyChanged = propertyChanged.Publish"); appendLine(1, "abstract member RaisePropertyChanged : string -> unit"); appendLine(1, "default x.RaisePropertyChanged(propertyName : string) = propertyChanged.Trigger(x, PropertyChangedEventArgs(propertyName))"); appendLine(0, string.Empty); foreach(var cd in columns) { var camel = toCamel(cd.ColumnName); appendLine(0,string.Empty); appendLine(1, ColumnComment(cd)); appendLine(1, "member x."+ cd.ColumnName); appendLine(2, "with get() = " + camel); appendLine(2, "and set v = "); appendLine(3, camel +" <- v"); appendLine(3, "x.RaisePropertyChanged \"" + cd.ColumnName +"\""); } } public class ColumnDescription { public string ColumnName{get;set;} public string Type {get;set;} public int Length {get;set;} public bool Nullable{get;set;} } static string MapSqlType(string type, bool nullable, bool useOptions) { switch (type.ToLower()){ case "char": case "nchar": case "nvarchar": case "varchar": return "string"; case "bit": return nullable ? (useOptions ? "bool option" : "bool Nullable") : "bool"; case "date": case "datetime": case "smalldatetime": return nullable ? (useOptions ? "DateTime option" : "DateTime Nullable") : "DateTime"; case "int": return nullable ? (useOptions ? "int option" : "int Nullable") : "int"; case "decimal": return nullable ? (useOptions ? "decimal option": "decimal Nullable") : "decimal"; default : return type ?? string.Empty; } } static string GetDefaultValue(string mappedType) { if(mappedType.EndsWith("Nullable")) return "Nullable()"; if(mappedType.EndsWith("option")) return "None"; switch(mappedType.ToLower()){ case "int": return "0"; case "bool": return "false"; case "decimal": return "0m"; case "datetime": return "System.DateTime.MinValue"; default : return "null"; } } static string ColumnComment(ColumnDescription cd) { return "/// " + (cd.Type ?? "null") + " (" + cd.Length + ") " + (cd.Nullable? "null" : "not null"); } static string TypeComment(int columnCount) { return "/// " + columnCount + " properties"; } static string toCamel(string s) // https://github.com/ayoung/Newtonsoft.Json/blob/master/Newtonsoft.Json/Utilities/StringUtils.cs { if (string.IsNullOrEmpty(s)) return s; if (!char.IsUpper(s[0])) return s; string camelCase = char.ToLower(s[0], CultureInfo.InvariantCulture).ToString(CultureInfo.InvariantCulture); if (s.Length > 1) camelCase += s.Substring(1); return camelCase; } #>