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.Collections.Generic; 28 using System.Data; 29 using System.IO; 30 using System.Linq; 31 32 #if MONO_STRICT 33 using System.Data.Linq; 34 #else 35 using DbLinq.Data.Linq; 36 #endif 37 38 using DbLinq.Factory; 39 using DbLinq.Schema; 40 using DbLinq.Schema.Dbml; 41 using System.Text.RegularExpressions; 42 43 namespace DbLinq.Vendor.Implementation 44 { 45 #if !MONO_STRICT 46 public 47 #endif 48 abstract partial class SchemaLoader : ISchemaLoader 49 { 50 /// <summary> 51 /// Underlying vendor 52 /// </summary> 53 /// <value></value> 54 public abstract IVendor Vendor { get; set; } 55 /// <summary> 56 /// Connection used to read schema 57 /// </summary> 58 /// <value></value> 59 public IDbConnection Connection { get; set; } 60 /// <summary> 61 /// Gets or sets the name formatter. 62 /// </summary> 63 /// <value>The name formatter.</value> 64 public INameFormatter NameFormatter { get; set; } 65 66 private TextWriter log; 67 /// <summary> 68 /// Log output 69 /// </summary> 70 public TextWriter Log 71 { 72 get { return log ?? Console.Out; } 73 set { log = value; } 74 } 75 76 /// <summary> 77 /// Loads database schema 78 /// </summary> 79 /// <param name="databaseName"></param> 80 /// <param name="nameAliases"></param> 81 /// <param name="nameFormat"></param> 82 /// <param name="loadStoredProcedures"></param> 83 /// <param name="contextNamespace"></param> 84 /// <param name="entityNamespace"></param> 85 /// <returns></returns> Load(string databaseName, INameAliases nameAliases, NameFormat nameFormat, bool loadStoredProcedures, string contextNamespace, string entityNamespace)86 public virtual Database Load(string databaseName, INameAliases nameAliases, NameFormat nameFormat, 87 bool loadStoredProcedures, string contextNamespace, string entityNamespace) 88 { 89 // check if connection is open. Note: we may use something more flexible 90 if (Connection.State != ConnectionState.Open) 91 Connection.Open(); 92 93 // get the database name. If we don't have one, take it from connection string... 94 if (string.IsNullOrEmpty(databaseName)) 95 databaseName = Connection.Database; 96 // ... and if connection string doesn't provide a name, then throw an error 97 if (string.IsNullOrEmpty(databaseName)) 98 throw new ArgumentException("A database name is required. Please specify /database=<databaseName>"); 99 100 databaseName = GetDatabaseNameAliased(databaseName, nameAliases); 101 102 var schemaName = NameFormatter.GetSchemaName(databaseName, GetExtraction(databaseName), nameFormat); 103 var names = new Names(); 104 var schema = new Database 105 { 106 Name = schemaName.DbName, 107 Class = GetRuntimeClassName(schemaName.ClassName, nameAliases), 108 BaseType = typeof(DataContext).FullName, 109 ContextNamespace = contextNamespace, 110 EntityNamespace = entityNamespace, 111 }; 112 113 // order is important, we must have: 114 // 1. tables 115 // 2. columns 116 // 3. constraints 117 LoadTables(schema, schemaName, Connection, nameAliases, nameFormat, names); 118 LoadColumns(schema, schemaName, Connection, nameAliases, nameFormat, names); 119 CheckColumnsName(schema); 120 LoadConstraints(schema, schemaName, Connection, nameFormat, names); 121 CheckConstraintsName(schema); 122 if (loadStoredProcedures) 123 LoadStoredProcedures(schema, schemaName, Connection, nameFormat); 124 // names aren't checked here anymore, because this confuses DBML editor. 125 // they will (for now) be checked before .cs generation 126 // in the end, when probably will end up in mapping source (or somewhere around) 127 //CheckNamesSafety(schema); 128 129 // generate backing fields name (since we have here correct names) 130 GenerateStorageAndMemberFields(schema); 131 132 return schema; 133 } 134 135 /// <summary> 136 /// Gets a usable name for the database. 137 /// </summary> 138 /// <param name="databaseName">Name of the database.</param> 139 /// <returns></returns> GetDatabaseName(string databaseName)140 protected virtual string GetDatabaseName(string databaseName) 141 { 142 return databaseName; 143 } 144 GetDatabaseNameAliased(string databaseName, INameAliases nameAliases)145 protected virtual string GetDatabaseNameAliased(string databaseName, INameAliases nameAliases) 146 { 147 string databaseNameAliased = nameAliases != null ? nameAliases.GetDatabaseNameAlias(databaseName) : null; 148 return (databaseNameAliased != null) ? databaseNameAliased : GetDatabaseName(databaseName); 149 } 150 151 /// <summary> 152 /// Gets a usable name for the database class. 153 /// </summary> 154 /// <param name="databaseName">Name of the clas.</param> 155 /// <returns></returns> GetRuntimeClassName(string className, INameAliases nameAliases)156 protected virtual string GetRuntimeClassName(string className, INameAliases nameAliases) 157 { 158 string classNameAliased = nameAliases != null ? nameAliases.GetClassNameAlias(className) : null; 159 return (classNameAliased != null) ? classNameAliased : className; 160 } 161 162 /// <summary> 163 /// Writes an error line. 164 /// </summary> 165 /// <param name="format">The format.</param> 166 /// <param name="arg">The arg.</param> WriteErrorLine(string format, params object[] arg)167 protected void WriteErrorLine(string format, params object[] arg) 168 { 169 var o = Log; 170 if (o == Console.Out) 171 o = Console.Error; 172 o.WriteLine(format, arg); 173 } 174 SchemaLoader()175 protected SchemaLoader() 176 { 177 NameFormatter = ObjectFactory.Create<INameFormatter>(); // the Pluralize property is set dynamically, so no singleton 178 } 179 180 /// <summary> 181 /// Gets the extraction type from a columnname. 182 /// </summary> 183 /// <param name="dbColumnName">Name of the db column.</param> 184 /// <returns></returns> GetExtraction(string dbColumnName)185 protected virtual WordsExtraction GetExtraction(string dbColumnName) 186 { 187 bool isMixedCase = dbColumnName != dbColumnName.ToLower() && dbColumnName != dbColumnName.ToUpper(); 188 return isMixedCase ? WordsExtraction.FromCase : WordsExtraction.FromDictionary; 189 } 190 191 /// <summary> 192 /// Gets the full name of a name and schema. 193 /// </summary> 194 /// <param name="dbName">Name of the db.</param> 195 /// <param name="dbSchema">The db schema.</param> 196 /// <returns></returns> GetFullDbName(string dbName, string dbSchema)197 protected virtual string GetFullDbName(string dbName, string dbSchema) 198 { 199 string fullDbName; 200 if (dbSchema == null) 201 fullDbName = dbName; 202 else 203 fullDbName = string.Format("{0}.{1}", dbSchema, dbName); 204 return fullDbName; 205 } 206 207 /// <summary> 208 /// Creates the name of the table given a name and schema 209 /// </summary> 210 /// <param name="dbTableName">Name of the db table.</param> 211 /// <param name="dbSchema">The db schema.</param> 212 /// <param name="nameAliases">The name aliases.</param> 213 /// <param name="nameFormat">The name format.</param> 214 /// <param name="extraction">The extraction.</param> 215 /// <returns></returns> CreateTableName(string dbTableName, string dbSchema, INameAliases nameAliases, NameFormat nameFormat, WordsExtraction extraction)216 protected virtual TableName CreateTableName(string dbTableName, string dbSchema, INameAliases nameAliases, NameFormat nameFormat, WordsExtraction extraction) 217 { 218 // if we have an alias, use it, and don't try to analyze it (a human probably already did the job) 219 var tableTypeAlias = nameAliases != null ? nameAliases.GetTableTypeAlias(dbTableName, dbSchema) : null; 220 if (tableTypeAlias != null) 221 extraction = WordsExtraction.None; 222 else 223 tableTypeAlias = dbTableName; 224 225 var tableName = NameFormatter.GetTableName(tableTypeAlias, extraction, nameFormat); 226 227 // alias for member 228 var tableMemberAlias = nameAliases != null ? nameAliases.GetTableMemberAlias(dbTableName, dbSchema) : null; 229 if (tableMemberAlias != null) 230 tableName.MemberName = tableMemberAlias; 231 232 tableName.DbName = GetFullDbName(dbTableName, dbSchema); 233 return tableName; 234 } 235 CreateTableName(string dbTableName, string dbSchema, INameAliases nameAliases, NameFormat nameFormat)236 protected virtual TableName CreateTableName(string dbTableName, string dbSchema, INameAliases nameAliases, NameFormat nameFormat) 237 { 238 return CreateTableName(dbTableName, dbSchema, nameAliases, nameFormat, GetExtraction(dbTableName)); 239 } 240 241 Regex startsWithNumber = new Regex(@"^\d", RegexOptions.Compiled); 242 243 /// <summary> 244 /// Creates the name of the column. 245 /// </summary> 246 /// <param name="dbColumnName">Name of the db column.</param> 247 /// <param name="dbTableName">Name of the db table.</param> 248 /// <param name="dbSchema">The db schema.</param> 249 /// <param name="nameAliases">The name aliases.</param> 250 /// <param name="nameFormat">The name format.</param> 251 /// <returns></returns> CreateColumnName(string dbColumnName, string dbTableName, string dbSchema, INameAliases nameAliases, NameFormat nameFormat)252 protected virtual ColumnName CreateColumnName(string dbColumnName, string dbTableName, string dbSchema, INameAliases nameAliases, NameFormat nameFormat) 253 { 254 var columnNameAlias = nameAliases != null ? nameAliases.GetColumnMemberAlias(dbColumnName, dbTableName, dbSchema) : null; 255 WordsExtraction extraction; 256 if (columnNameAlias != null) 257 { 258 extraction = WordsExtraction.None; 259 } 260 else 261 { 262 extraction = GetExtraction(dbColumnName); 263 columnNameAlias = dbColumnName; 264 } 265 var columnName = NameFormatter.GetColumnName(columnNameAlias, extraction, nameFormat); 266 // The member name can not be the same as the class 267 // we add a "1" (just like SqlMetal does) 268 var tableName = CreateTableName(dbTableName, dbSchema, nameAliases, nameFormat); 269 if (columnName.PropertyName == tableName.ClassName) 270 columnName.PropertyName = columnName.PropertyName + "1"; 271 272 if (startsWithNumber.IsMatch(columnName.PropertyName)) 273 columnName.PropertyName = "_" + columnName.PropertyName; 274 275 columnName.DbName = dbColumnName; 276 return columnName; 277 } 278 279 /// <summary> 280 /// Creates the name of the procedure. 281 /// </summary> 282 /// <param name="dbProcedureName">Name of the db procedure.</param> 283 /// <param name="dbSchema">The db schema.</param> 284 /// <param name="nameFormat">The name format.</param> 285 /// <returns></returns> CreateProcedureName(string dbProcedureName, string dbSchema, NameFormat nameFormat)286 protected virtual ProcedureName CreateProcedureName(string dbProcedureName, string dbSchema, NameFormat nameFormat) 287 { 288 var procedureName = NameFormatter.GetProcedureName(dbProcedureName, GetExtraction(dbProcedureName), nameFormat); 289 procedureName.DbName = GetFullDbName(dbProcedureName, dbSchema); 290 return procedureName; 291 } 292 293 /// <summary> 294 /// Creates the name of the association. 295 /// </summary> 296 /// <param name="dbManyName">Name of the db many.</param> 297 /// <param name="dbManySchema">The db many schema.</param> 298 /// <param name="dbOneName">Name of the db one.</param> 299 /// <param name="dbOneSchema">The db one schema.</param> 300 /// <param name="dbConstraintName">Name of the db constraint.</param> 301 /// <param name="foreignKeyName">Name of the foreign key.</param> 302 /// <param name="nameFormat">The name format.</param> 303 /// <returns></returns> CreateAssociationName(string dbManyName, string dbManySchema, string dbOneName, string dbOneSchema, string dbConstraintName, string foreignKeyName, NameFormat nameFormat)304 protected virtual AssociationName CreateAssociationName(string dbManyName, string dbManySchema, 305 string dbOneName, string dbOneSchema, string dbConstraintName, string foreignKeyName, NameFormat nameFormat) 306 { 307 var associationName = NameFormatter.GetAssociationName(dbManyName, dbOneName, 308 dbConstraintName, foreignKeyName, GetExtraction(dbManyName), nameFormat); 309 associationName.DbName = GetFullDbName(dbManyName, dbManySchema); 310 return associationName; 311 } 312 313 /// <summary> 314 /// Creates the name of the schema. 315 /// </summary> 316 /// <param name="databaseName">Name of the database.</param> 317 /// <param name="connection">The connection.</param> 318 /// <param name="nameFormat">The name format.</param> 319 /// <returns></returns> CreateSchemaName(string databaseName, IDbConnection connection, NameFormat nameFormat)320 protected virtual SchemaName CreateSchemaName(string databaseName, IDbConnection connection, NameFormat nameFormat) 321 { 322 if (string.IsNullOrEmpty(databaseName)) 323 { 324 databaseName = connection.Database; 325 if (string.IsNullOrEmpty(databaseName)) 326 throw new ArgumentException("Could not deduce database name from connection string. Please specify /database=<databaseName>"); 327 } 328 return NameFormatter.GetSchemaName(databaseName, GetExtraction(databaseName), nameFormat); 329 } 330 CreateParameterName(string dbParameterName, NameFormat nameFormat)331 protected virtual ParameterName CreateParameterName(string dbParameterName, NameFormat nameFormat) 332 { 333 var parameterName = NameFormatter.GetParameterName(dbParameterName, GetExtraction(dbParameterName), nameFormat); 334 return parameterName; 335 } 336 337 protected class Names 338 { 339 public IDictionary<string, TableName> TablesNames = new Dictionary<string, TableName>(); 340 public IDictionary<string, IDictionary<string, ColumnName>> ColumnsNames = new Dictionary<string, IDictionary<string, ColumnName>>(); 341 AddColumn(string dbTableName, ColumnName columnName)342 public void AddColumn(string dbTableName, ColumnName columnName) 343 { 344 IDictionary<string, ColumnName> columns; 345 if (!ColumnsNames.TryGetValue(dbTableName, out columns)) 346 { 347 columns = new Dictionary<string, ColumnName>(); 348 ColumnsNames[dbTableName] = columns; 349 } 350 columns[columnName.DbName] = columnName; 351 } 352 } 353 354 /// <summary> 355 /// Loads the tables in the given schema. 356 /// </summary> 357 /// <param name="schema">The schema.</param> 358 /// <param name="schemaName">Name of the schema.</param> 359 /// <param name="conn">The conn.</param> 360 /// <param name="nameAliases">The name aliases.</param> 361 /// <param name="nameFormat">The name format.</param> 362 /// <param name="names">The names.</param> LoadTables(Database schema, SchemaName schemaName, IDbConnection conn, INameAliases nameAliases, NameFormat nameFormat, Names names)363 protected virtual void LoadTables(Database schema, SchemaName schemaName, IDbConnection conn, INameAliases nameAliases, NameFormat nameFormat, Names names) 364 { 365 var tables = ReadTables(conn, schemaName.DbName); 366 foreach (var row in tables) 367 { 368 var tableName = CreateTableName(row.Name, row.Schema, nameAliases, nameFormat); 369 names.TablesNames[tableName.DbName] = tableName; 370 371 var table = new Table(); 372 table.Name = tableName.DbName; 373 table.Member = tableName.MemberName; 374 table.Type.Name = tableName.ClassName; 375 schema.Tables.Add(table); 376 } 377 } 378 379 /// <summary> 380 /// Loads the columns. 381 /// </summary> 382 /// <param name="schema">The schema.</param> 383 /// <param name="schemaName">Name of the schema.</param> 384 /// <param name="conn">The conn.</param> 385 /// <param name="nameAliases">The name aliases.</param> 386 /// <param name="nameFormat">The name format.</param> 387 /// <param name="names">The names.</param> LoadColumns(Database schema, SchemaName schemaName, IDbConnection conn, INameAliases nameAliases, NameFormat nameFormat, Names names)388 protected void LoadColumns(Database schema, SchemaName schemaName, IDbConnection conn, INameAliases nameAliases, NameFormat nameFormat, Names names) 389 { 390 var columnRows = ReadColumns(conn, schemaName.DbName); 391 foreach (var columnRow in columnRows) 392 { 393 var columnName = CreateColumnName(columnRow.ColumnName, columnRow.TableName, columnRow.TableSchema, nameAliases, nameFormat); 394 names.AddColumn(columnRow.TableName, columnName); 395 396 //find which table this column belongs to 397 string fullColumnDbName = GetFullDbName(columnRow.TableName, columnRow.TableSchema); 398 DbLinq.Schema.Dbml.Table tableSchema = schema.Tables.FirstOrDefault(tblSchema => fullColumnDbName == tblSchema.Name); 399 if (tableSchema == null) 400 { 401 WriteErrorLine("ERROR L46: Table '" + columnRow.TableName + "' not found for column " + columnRow.ColumnName); 402 continue; 403 } 404 var column = new Column(); 405 column.Name = columnName.DbName; 406 column.Member = columnName.PropertyName; 407 column.DbType = columnRow.FullType; 408 409 if (columnRow.PrimaryKey.HasValue) 410 column.IsPrimaryKey = columnRow.PrimaryKey.Value; 411 412 bool? generated = (nameAliases != null) ? nameAliases.GetColumnGenerated(columnRow.ColumnName, columnRow.TableName, columnRow.TableSchema) : null; 413 if (!generated.HasValue) 414 generated = columnRow.Generated; 415 if (generated.HasValue) 416 column.IsDbGenerated = generated.Value; 417 418 AutoSync? autoSync = (nameAliases != null) ? nameAliases.GetColumnAutoSync(columnRow.ColumnName, columnRow.TableName, columnRow.TableSchema) : null; 419 if (autoSync.HasValue) 420 column.AutoSync = autoSync.Value; 421 422 // the Expression can originate from two sources: 423 // 1. DefaultValue 424 // 2. Expression 425 // we use any valid source (we can't have both) 426 if (column.IsDbGenerated && columnRow.DefaultValue != null) 427 column.Expression = columnRow.DefaultValue; 428 429 column.CanBeNull = columnRow.Nullable; 430 431 string columnTypeAlias = nameAliases != null ? nameAliases.GetColumnForcedType(columnRow.ColumnName, columnRow.TableName, columnRow.TableSchema) : null; 432 var columnType = MapDbType(columnName.DbName, columnRow); 433 434 var columnEnumType = columnType as EnumType; 435 if (columnEnumType != null) 436 { 437 var enumType = column.SetExtendedTypeAsEnumType(); 438 enumType.Name = columnEnumType.Name; 439 foreach (KeyValuePair<string, int> enumValue in columnEnumType.EnumValues) 440 { 441 enumType[enumValue.Key] = enumValue.Value; 442 } 443 } 444 else if (columnTypeAlias != null) 445 column.Type = columnTypeAlias; 446 else 447 column.Type = columnType.ToString(); 448 449 tableSchema.Type.Columns.Add(column); 450 } 451 } 452 } 453 } 454