1 #region MIT license 2 // 3 // MIT license 4 // 5 // Copyright (c) 2007-2008 Jiri Moudry, Pascal Craponne 6 // 7 // Permission is hereby granted, free of charge, to any person obtaining a copy 8 // of this software and associated documentation files (the "Software"), to deal 9 // in the Software without restriction, including without limitation the rights 10 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 // copies of the Software, and to permit persons to whom the Software is 12 // furnished to do so, subject to the following conditions: 13 // 14 // The above copyright notice and this permission notice shall be included in 15 // all copies or substantial portions of the Software. 16 // 17 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 // THE SOFTWARE. 24 // 25 #endregion 26 using System; 27 using System.CodeDom; 28 using System.CodeDom.Compiler; 29 using System.Collections.Generic; 30 using System.ComponentModel; 31 using System.IO; 32 using System.Linq; 33 using System.Reflection; 34 using System.Text; 35 36 using Microsoft.CSharp; 37 using Microsoft.VisualBasic; 38 39 using DbLinq.Schema.Dbml; 40 41 namespace DbMetal.Generator.Implementation.CodeDomGenerator 42 { 43 #if !MONO_STRICT 44 public 45 #endif 46 abstract class AbstractCodeDomGenerator : ICodeGenerator 47 { 48 public abstract string LanguageCode { get; } 49 public abstract string Extension { get; } Write(TextWriter textWriter, Database dbSchema, GenerationContext context)50 public abstract void Write(TextWriter textWriter, Database dbSchema, GenerationContext context); 51 52 /// <summary> 53 /// Generates a C# source code wrapper for the database schema objects. 54 /// </summary> 55 /// <param name="database"></param> 56 /// <param name="filename"></param> GenerateCSharp(Database database, string filename)57 public void GenerateCSharp(Database database, string filename) 58 { 59 using (Stream stream = File.Open(filename, FileMode.Create)) 60 { 61 using (StreamWriter writer = new StreamWriter(stream)) 62 { 63 new CSharpCodeProvider().CreateGenerator(writer).GenerateCodeFromNamespace(GenerateCodeDomModel(database), writer, new CodeGeneratorOptions() { BracingStyle = "C" }); 64 } 65 } 66 } 67 68 /// <summary> 69 /// Generates a Visual Basic source code wrapper for the database schema objects. 70 /// </summary> 71 /// <param name="database"></param> 72 /// <param name="filename"></param> GenerateVisualBasic(Database database, string filename)73 public void GenerateVisualBasic(Database database, string filename) 74 { 75 using (Stream stream = File.Open(filename, FileMode.Create)) 76 { 77 using (StreamWriter writer = new StreamWriter(stream)) 78 { 79 new VBCodeProvider().CreateGenerator(writer).GenerateCodeFromNamespace(GenerateCodeDomModel(database), writer, new CodeGeneratorOptions() { BracingStyle = "C" }); 80 } 81 } 82 } 83 84 CodeThisReferenceExpression thisReference = new CodeThisReferenceExpression(); 85 GenerateCodeDomModel(Database database)86 protected virtual CodeNamespace GenerateCodeDomModel(Database database) 87 { 88 CodeNamespace _namespace = new CodeNamespace(database.ContextNamespace); 89 90 _namespace.Imports.Add(new CodeNamespaceImport("System")); 91 _namespace.Imports.Add(new CodeNamespaceImport("System.ComponentModel")); 92 _namespace.Imports.Add(new CodeNamespaceImport("System.Data")); 93 _namespace.Imports.Add(new CodeNamespaceImport("System.Data.Linq.Mapping")); 94 _namespace.Imports.Add(new CodeNamespaceImport("System.Diagnostics")); 95 _namespace.Imports.Add(new CodeNamespaceImport("DbLinq.Linq")); 96 _namespace.Imports.Add(new CodeNamespaceImport("DbLinq.Linq.Mapping")); 97 98 _namespace.Comments.Add(new CodeCommentStatement(GenerateCommentBanner(database))); 99 100 _namespace.Types.Add(GenerateContextClass(database)); 101 102 foreach (Table table in database.Tables) 103 _namespace.Types.Add(GenerateTableClass(table)); 104 return _namespace; 105 } 106 GenerateCommentBanner(Database database)107 protected virtual string GenerateCommentBanner(Database database) 108 { 109 var result = new StringBuilder(); 110 111 // http://www.network-science.de/ascii/ 112 // http://www.network-science.de/ascii/ascii.php?TEXT=MetalSequel&x=14&y=14&FONT=_all+fonts+with+your+text_&RICH=no&FORM=left&STRE=no&WIDT=80 113 result.Append( 114 @" 115 ____ _ __ __ _ _ 116 | _ \| |__ | \/ | ___| |_ __ _| | 117 | | | | '_ \| |\/| |/ _ \ __/ _` | | 118 | |_| | |_) | | | | __/ || (_| | | 119 |____/|_.__/|_| |_|\___|\__\__,_|_| 120 121 "); 122 result.AppendLine(String.Format(" Auto-generated from {0} on {1}.", database.Name, DateTime.Now)); 123 result.AppendLine(" Please visit http://linq.to/db for more information."); 124 125 return result.ToString(); 126 } 127 GenerateContextClass(Database database)128 protected virtual CodeTypeDeclaration GenerateContextClass(Database database) 129 { 130 var _class = new CodeTypeDeclaration() { IsClass = true, IsPartial = true, Name = database.Class, TypeAttributes = TypeAttributes.Public }; 131 132 if (database.BaseType != null) 133 _class.BaseTypes.Add(database.BaseType); 134 else // TODO: something with less risk 135 _class.BaseTypes.Add(String.Format("DbLinq.{0}.{0}DataContext", database.Provider)); 136 137 // CodeDom does not currently support partial methods. This will be a problem for VB. Will probably be fixed in .net 4 138 _class.Members.Add(new CodeSnippetTypeMember("\tpartial void OnCreated();")); 139 140 // Implement Constructor 141 var constructor = new CodeConstructor() { Attributes = MemberAttributes.Public, Parameters = { new CodeParameterDeclarationExpression("IDbConnection", "connection") } }; 142 constructor.BaseConstructorArgs.Add(new CodeArgumentReferenceExpression("connection")); 143 constructor.Statements.Add(new CodeExpressionStatement(new CodeMethodInvokeExpression(thisReference, "OnCreated"))); 144 _class.Members.Add(constructor); 145 146 // todo: override other constructors 147 148 foreach (Table table in database.Tables) 149 { 150 var tableType = new CodeTypeReference(table.Member); 151 var property = new CodeMemberProperty() { Type = new CodeTypeReference("Table", tableType), Name = table.Member, Attributes = MemberAttributes.Public | MemberAttributes.Final }; 152 property.GetStatements.Add 153 ( 154 new CodeMethodReturnStatement 155 ( 156 new CodeMethodInvokeExpression 157 ( 158 new CodeMethodReferenceExpression(thisReference, "GetTable", tableType) 159 ) 160 ) 161 ); 162 _class.Members.Add(property); 163 } 164 165 return _class; 166 } 167 GenerateTableClass(Table table)168 protected virtual CodeTypeDeclaration GenerateTableClass(Table table) 169 { 170 var _class = new CodeTypeDeclaration() { IsClass = true, IsPartial = true, Name = table.Member, TypeAttributes = TypeAttributes.Public }; 171 172 _class.CustomAttributes.Add(new CodeAttributeDeclaration("Table", new CodeAttributeArgument("Name", new CodePrimitiveExpression(table.Name)))); 173 174 // Implement Constructor 175 var constructor = new CodeConstructor() { Attributes = MemberAttributes.Public }; 176 constructor.Statements.Add(new CodeExpressionStatement(new CodeMethodInvokeExpression(thisReference, "OnCreated"))); 177 _class.Members.Add(constructor); 178 179 // todo: implement INotifyPropertyChanging 180 181 // Implement INotifyPropertyChanged 182 _class.BaseTypes.Add(typeof(INotifyPropertyChanged)); 183 184 var propertyChangedEvent = new CodeMemberEvent() { Type = new CodeTypeReference(typeof(PropertyChangedEventHandler)), Name = "PropertyChanged", Attributes = MemberAttributes.Public }; 185 _class.Members.Add(propertyChangedEvent); 186 187 var sendPropertyChangedMethod = new CodeMemberMethod() { Attributes = MemberAttributes.Family, Name = "SendPropertyChanged", Parameters = { new CodeParameterDeclarationExpression(typeof(System.String), "propertyName") } }; 188 sendPropertyChangedMethod.Statements.Add 189 ( 190 new CodeConditionStatement 191 ( 192 new CodeSnippetExpression(propertyChangedEvent.Name + " != null"), // todo: covert this to CodeBinaryOperatorExpression 193 new CodeExpressionStatement 194 ( 195 new CodeMethodInvokeExpression 196 ( 197 new CodeMethodReferenceExpression(thisReference, propertyChangedEvent.Name), 198 thisReference, 199 new CodeObjectCreateExpression(typeof(PropertyChangedEventArgs), new CodeArgumentReferenceExpression("propertyName")) 200 ) 201 ) 202 ) 203 ); 204 _class.Members.Add(sendPropertyChangedMethod); 205 206 // CodeDom does not currently support partial methods. This will be a problem for VB. Will probably be fixed in .net 4 207 _class.Members.Add(new CodeSnippetTypeMember("\tpartial void OnCreated();")); 208 209 // todo: add these when the actually get called 210 //partial void OnLoaded(); 211 //partial void OnValidate(System.Data.Linq.ChangeAction action); 212 213 // columns 214 foreach (Column column in table.Type.Columns) 215 { 216 var type = new CodeTypeReference(column.Type); 217 var columnMember = column.Member ?? column.Name; 218 219 var field = new CodeMemberField(type, "_" + columnMember); 220 _class.Members.Add(field); 221 var fieldReference = new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), field.Name); 222 223 // CodeDom does not currently support partial methods. This will be a problem for VB. Will probably be fixed in .net 4 224 string onChangingPartialMethodName = String.Format("On{0}Changing", columnMember); 225 _class.Members.Add(new CodeSnippetTypeMember(String.Format("\tpartial void {0}({1} instance);", onChangingPartialMethodName, column.Type))); 226 string onChangedPartialMethodName = String.Format("On{0}Changed", columnMember); 227 _class.Members.Add(new CodeSnippetTypeMember(String.Format("\tpartial void {0}();", onChangedPartialMethodName))); 228 229 var property = new CodeMemberProperty(); 230 property.Type = type; 231 property.Name = columnMember; 232 property.Attributes = MemberAttributes.Public | MemberAttributes.Final; 233 property.CustomAttributes.Add 234 ( 235 new CodeAttributeDeclaration 236 ( 237 "Column", 238 new CodeAttributeArgument("Storage", new CodePrimitiveExpression(column.Storage)), 239 new CodeAttributeArgument("Name", new CodePrimitiveExpression(column.Name)), 240 new CodeAttributeArgument("DbType", new CodePrimitiveExpression(column.DbType)), 241 new CodeAttributeArgument("CanBeNull", new CodePrimitiveExpression(column.CanBeNull)), 242 new CodeAttributeArgument("IsPrimaryKey", new CodePrimitiveExpression(column.IsPrimaryKey)) 243 ) 244 ); 245 property.CustomAttributes.Add(new CodeAttributeDeclaration("DebuggerNonUserCode")); 246 property.GetStatements.Add(new CodeMethodReturnStatement(fieldReference)); 247 property.SetStatements.Add 248 ( 249 new CodeConditionStatement 250 ( 251 new CodeSnippetExpression(field.Name + " != value"), // todo: covert this to CodeBinaryOperatorExpression 252 new CodeExpressionStatement(new CodeMethodInvokeExpression(thisReference, onChangingPartialMethodName, new CodePropertySetValueReferenceExpression())), 253 new CodeAssignStatement(fieldReference, new CodePropertySetValueReferenceExpression()), 254 new CodeExpressionStatement(new CodeMethodInvokeExpression(thisReference, sendPropertyChangedMethod.Name, new CodePrimitiveExpression(property.Name))), 255 new CodeExpressionStatement(new CodeMethodInvokeExpression(thisReference, onChangedPartialMethodName)) 256 ) 257 ); 258 _class.Members.Add(property); 259 } 260 261 // TODO: implement associations 262 263 // TODO: implement functions / procedures 264 265 // TODO: Override Equals and GetHashCode 266 267 return _class; 268 } 269 } 270 } 271