5 // Copyright (c) 2007-2008 Jiri Moudry, Pascal Craponne
\r
7 // Permission is hereby granted, free of charge, to any person obtaining a copy
\r
8 // of this software and associated documentation files (the "Software"), to deal
\r
9 // in the Software without restriction, including without limitation the rights
\r
10 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
\r
11 // copies of the Software, and to permit persons to whom the Software is
\r
12 // furnished to do so, subject to the following conditions:
\r
14 // The above copyright notice and this permission notice shall be included in
\r
15 // all copies or substantial portions of the Software.
\r
17 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
\r
18 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
\r
19 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
\r
20 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
\r
21 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
\r
22 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
\r
27 using System.Collections.Generic;
\r
33 using System.Data.Linq;
\r
35 using DbLinq.Data.Linq;
\r
38 using DbLinq.Factory;
\r
39 using DbLinq.Schema;
\r
40 using DbLinq.Schema.Dbml;
\r
41 using System.Text.RegularExpressions;
\r
43 namespace DbLinq.Vendor.Implementation
\r
48 abstract partial class SchemaLoader : ISchemaLoader
\r
51 /// Underlying vendor
\r
54 public abstract IVendor Vendor { get; set; }
\r
56 /// Connection used to read schema
\r
59 public IDbConnection Connection { get; set; }
\r
61 /// Gets or sets the name formatter.
\r
63 /// <value>The name formatter.</value>
\r
64 public INameFormatter NameFormatter { get; set; }
\r
66 private TextWriter log;
\r
70 public TextWriter Log
\r
72 get { return log ?? Console.Out; }
\r
73 set { log = value; }
\r
77 /// Loads database schema
\r
79 /// <param name="databaseName"></param>
\r
80 /// <param name="nameAliases"></param>
\r
81 /// <param name="nameFormat"></param>
\r
82 /// <param name="loadStoredProcedures"></param>
\r
83 /// <param name="contextNamespace"></param>
\r
84 /// <param name="entityNamespace"></param>
\r
85 /// <returns></returns>
\r
86 public virtual Database Load(string databaseName, INameAliases nameAliases, NameFormat nameFormat,
\r
87 bool loadStoredProcedures, string contextNamespace, string entityNamespace)
\r
89 // check if connection is open. Note: we may use something more flexible
\r
90 if (Connection.State != ConnectionState.Open)
\r
93 // get the database name. If we don't have one, take it from connection string...
\r
94 if (string.IsNullOrEmpty(databaseName))
\r
95 databaseName = Connection.Database;
\r
96 // ... and if connection string doesn't provide a name, then throw an error
\r
97 if (string.IsNullOrEmpty(databaseName))
\r
98 throw new ArgumentException("A database name is required. Please specify /database=<databaseName>");
\r
100 databaseName = GetDatabaseNameAliased(databaseName, nameAliases);
\r
102 var schemaName = NameFormatter.GetSchemaName(databaseName, GetExtraction(databaseName), nameFormat);
\r
103 var names = new Names();
\r
104 var schema = new Database
\r
106 Name = schemaName.DbName,
\r
107 Class = GetRuntimeClassName(schemaName.ClassName, nameAliases),
\r
108 BaseType = typeof(DataContext).FullName,
\r
109 ContextNamespace = contextNamespace,
\r
110 EntityNamespace = entityNamespace,
\r
113 // order is important, we must have:
\r
117 LoadTables(schema, schemaName, Connection, nameAliases, nameFormat, names);
\r
118 LoadColumns(schema, schemaName, Connection, nameAliases, nameFormat, names);
\r
119 CheckColumnsName(schema);
\r
120 LoadConstraints(schema, schemaName, Connection, nameFormat, names);
\r
121 CheckConstraintsName(schema);
\r
122 if (loadStoredProcedures)
\r
123 LoadStoredProcedures(schema, schemaName, Connection, nameFormat);
\r
124 // names aren't checked here anymore, because this confuses DBML editor.
\r
125 // they will (for now) be checked before .cs generation
\r
126 // in the end, when probably will end up in mapping source (or somewhere around)
\r
127 //CheckNamesSafety(schema);
\r
129 // generate backing fields name (since we have here correct names)
\r
130 GenerateStorageAndMemberFields(schema);
\r
136 /// Gets a usable name for the database.
\r
138 /// <param name="databaseName">Name of the database.</param>
\r
139 /// <returns></returns>
\r
140 protected virtual string GetDatabaseName(string databaseName)
\r
142 return databaseName;
\r
145 protected virtual string GetDatabaseNameAliased(string databaseName, INameAliases nameAliases)
\r
147 string databaseNameAliased = nameAliases != null ? nameAliases.GetDatabaseNameAlias(databaseName) : null;
\r
148 return (databaseNameAliased != null) ? databaseNameAliased : GetDatabaseName(databaseName);
\r
152 /// Gets a usable name for the database class.
\r
154 /// <param name="databaseName">Name of the clas.</param>
\r
155 /// <returns></returns>
\r
156 protected virtual string GetRuntimeClassName(string className, INameAliases nameAliases)
\r
158 string classNameAliased = nameAliases != null ? nameAliases.GetClassNameAlias(className) : null;
\r
159 return (classNameAliased != null) ? classNameAliased : className;
\r
163 /// Writes an error line.
\r
165 /// <param name="format">The format.</param>
\r
166 /// <param name="arg">The arg.</param>
\r
167 protected void WriteErrorLine(string format, params object[] arg)
\r
170 if (o == Console.Out)
\r
172 o.WriteLine(format, arg);
\r
175 protected SchemaLoader()
\r
177 NameFormatter = ObjectFactory.Create<INameFormatter>(); // the Pluralize property is set dynamically, so no singleton
\r
181 /// Gets the extraction type from a columnname.
\r
183 /// <param name="dbColumnName">Name of the db column.</param>
\r
184 /// <returns></returns>
\r
185 protected virtual WordsExtraction GetExtraction(string dbColumnName)
\r
187 bool isMixedCase = dbColumnName != dbColumnName.ToLower() && dbColumnName != dbColumnName.ToUpper();
\r
188 return isMixedCase ? WordsExtraction.FromCase : WordsExtraction.FromDictionary;
\r
192 /// Gets the full name of a name and schema.
\r
194 /// <param name="dbName">Name of the db.</param>
\r
195 /// <param name="dbSchema">The db schema.</param>
\r
196 /// <returns></returns>
\r
197 protected virtual string GetFullDbName(string dbName, string dbSchema)
\r
200 if (dbSchema == null)
\r
201 fullDbName = dbName;
\r
203 fullDbName = string.Format("{0}.{1}", dbSchema, dbName);
\r
208 /// Creates the name of the table given a name and schema
\r
210 /// <param name="dbTableName">Name of the db table.</param>
\r
211 /// <param name="dbSchema">The db schema.</param>
\r
212 /// <param name="nameAliases">The name aliases.</param>
\r
213 /// <param name="nameFormat">The name format.</param>
\r
214 /// <param name="extraction">The extraction.</param>
\r
215 /// <returns></returns>
\r
216 protected virtual TableName CreateTableName(string dbTableName, string dbSchema, INameAliases nameAliases, NameFormat nameFormat, WordsExtraction extraction)
\r
218 // if we have an alias, use it, and don't try to analyze it (a human probably already did the job)
\r
219 var tableTypeAlias = nameAliases != null ? nameAliases.GetTableTypeAlias(dbTableName, dbSchema) : null;
\r
220 if (tableTypeAlias != null)
\r
221 extraction = WordsExtraction.None;
\r
223 tableTypeAlias = dbTableName;
\r
225 var tableName = NameFormatter.GetTableName(tableTypeAlias, extraction, nameFormat);
\r
227 // alias for member
\r
228 var tableMemberAlias = nameAliases != null ? nameAliases.GetTableMemberAlias(dbTableName, dbSchema) : null;
\r
229 if (tableMemberAlias != null)
\r
230 tableName.MemberName = tableMemberAlias;
\r
232 tableName.DbName = GetFullDbName(dbTableName, dbSchema);
\r
236 protected virtual TableName CreateTableName(string dbTableName, string dbSchema, INameAliases nameAliases, NameFormat nameFormat)
\r
238 return CreateTableName(dbTableName, dbSchema, nameAliases, nameFormat, GetExtraction(dbTableName));
\r
241 Regex startsWithNumber = new Regex(@"^\d", RegexOptions.Compiled);
\r
244 /// Creates the name of the column.
\r
246 /// <param name="dbColumnName">Name of the db column.</param>
\r
247 /// <param name="dbTableName">Name of the db table.</param>
\r
248 /// <param name="dbSchema">The db schema.</param>
\r
249 /// <param name="nameAliases">The name aliases.</param>
\r
250 /// <param name="nameFormat">The name format.</param>
\r
251 /// <returns></returns>
\r
252 protected virtual ColumnName CreateColumnName(string dbColumnName, string dbTableName, string dbSchema, INameAliases nameAliases, NameFormat nameFormat)
\r
254 var columnNameAlias = nameAliases != null ? nameAliases.GetColumnMemberAlias(dbColumnName, dbTableName, dbSchema) : null;
\r
255 WordsExtraction extraction;
\r
256 if (columnNameAlias != null)
\r
258 extraction = WordsExtraction.None;
\r
262 extraction = GetExtraction(dbColumnName);
\r
263 columnNameAlias = dbColumnName;
\r
265 var columnName = NameFormatter.GetColumnName(columnNameAlias, extraction, nameFormat);
\r
266 // The member name can not be the same as the class
\r
267 // we add a "1" (just like SqlMetal does)
\r
268 var tableName = CreateTableName(dbTableName, dbSchema, nameAliases, nameFormat);
\r
269 if (columnName.PropertyName == tableName.ClassName)
\r
270 columnName.PropertyName = columnName.PropertyName + "1";
\r
272 if (startsWithNumber.IsMatch(columnName.PropertyName))
\r
273 columnName.PropertyName = "_" + columnName.PropertyName;
\r
275 columnName.DbName = dbColumnName;
\r
280 /// Creates the name of the procedure.
\r
282 /// <param name="dbProcedureName">Name of the db procedure.</param>
\r
283 /// <param name="dbSchema">The db schema.</param>
\r
284 /// <param name="nameFormat">The name format.</param>
\r
285 /// <returns></returns>
\r
286 protected virtual ProcedureName CreateProcedureName(string dbProcedureName, string dbSchema, NameFormat nameFormat)
\r
288 var procedureName = NameFormatter.GetProcedureName(dbProcedureName, GetExtraction(dbProcedureName), nameFormat);
\r
289 procedureName.DbName = GetFullDbName(dbProcedureName, dbSchema);
\r
290 return procedureName;
\r
294 /// Creates the name of the association.
\r
296 /// <param name="dbManyName">Name of the db many.</param>
\r
297 /// <param name="dbManySchema">The db many schema.</param>
\r
298 /// <param name="dbOneName">Name of the db one.</param>
\r
299 /// <param name="dbOneSchema">The db one schema.</param>
\r
300 /// <param name="dbConstraintName">Name of the db constraint.</param>
\r
301 /// <param name="foreignKeyName">Name of the foreign key.</param>
\r
302 /// <param name="nameFormat">The name format.</param>
\r
303 /// <returns></returns>
\r
304 protected virtual AssociationName CreateAssociationName(string dbManyName, string dbManySchema,
\r
305 string dbOneName, string dbOneSchema, string dbConstraintName, string foreignKeyName, NameFormat nameFormat)
\r
307 var associationName = NameFormatter.GetAssociationName(dbManyName, dbOneName,
\r
308 dbConstraintName, foreignKeyName, GetExtraction(dbManyName), nameFormat);
\r
309 associationName.DbName = GetFullDbName(dbManyName, dbManySchema);
\r
310 return associationName;
\r
314 /// Creates the name of the schema.
\r
316 /// <param name="databaseName">Name of the database.</param>
\r
317 /// <param name="connection">The connection.</param>
\r
318 /// <param name="nameFormat">The name format.</param>
\r
319 /// <returns></returns>
\r
320 protected virtual SchemaName CreateSchemaName(string databaseName, IDbConnection connection, NameFormat nameFormat)
\r
322 if (string.IsNullOrEmpty(databaseName))
\r
324 databaseName = connection.Database;
\r
325 if (string.IsNullOrEmpty(databaseName))
\r
326 throw new ArgumentException("Could not deduce database name from connection string. Please specify /database=<databaseName>");
\r
328 return NameFormatter.GetSchemaName(databaseName, GetExtraction(databaseName), nameFormat);
\r
331 protected virtual ParameterName CreateParameterName(string dbParameterName, NameFormat nameFormat)
\r
333 var parameterName = NameFormatter.GetParameterName(dbParameterName, GetExtraction(dbParameterName), nameFormat);
\r
334 return parameterName;
\r
337 protected class Names
\r
339 public IDictionary<string, TableName> TablesNames = new Dictionary<string, TableName>();
\r
340 public IDictionary<string, IDictionary<string, ColumnName>> ColumnsNames = new Dictionary<string, IDictionary<string, ColumnName>>();
\r
342 public void AddColumn(string dbTableName, ColumnName columnName)
\r
344 IDictionary<string, ColumnName> columns;
\r
345 if (!ColumnsNames.TryGetValue(dbTableName, out columns))
\r
347 columns = new Dictionary<string, ColumnName>();
\r
348 ColumnsNames[dbTableName] = columns;
\r
350 columns[columnName.DbName] = columnName;
\r
355 /// Loads the tables in the given schema.
\r
357 /// <param name="schema">The schema.</param>
\r
358 /// <param name="schemaName">Name of the schema.</param>
\r
359 /// <param name="conn">The conn.</param>
\r
360 /// <param name="nameAliases">The name aliases.</param>
\r
361 /// <param name="nameFormat">The name format.</param>
\r
362 /// <param name="names">The names.</param>
\r
363 protected virtual void LoadTables(Database schema, SchemaName schemaName, IDbConnection conn, INameAliases nameAliases, NameFormat nameFormat, Names names)
\r
365 var tables = ReadTables(conn, schemaName.DbName);
\r
366 foreach (var row in tables)
\r
368 var tableName = CreateTableName(row.Name, row.Schema, nameAliases, nameFormat);
\r
369 names.TablesNames[tableName.DbName] = tableName;
\r
371 var table = new Table();
\r
372 table.Name = tableName.DbName;
\r
373 table.Member = tableName.MemberName;
\r
374 table.Type.Name = tableName.ClassName;
\r
375 schema.Tables.Add(table);
\r
380 /// Loads the columns.
\r
382 /// <param name="schema">The schema.</param>
\r
383 /// <param name="schemaName">Name of the schema.</param>
\r
384 /// <param name="conn">The conn.</param>
\r
385 /// <param name="nameAliases">The name aliases.</param>
\r
386 /// <param name="nameFormat">The name format.</param>
\r
387 /// <param name="names">The names.</param>
\r
388 protected void LoadColumns(Database schema, SchemaName schemaName, IDbConnection conn, INameAliases nameAliases, NameFormat nameFormat, Names names)
\r
390 var columnRows = ReadColumns(conn, schemaName.DbName);
\r
391 foreach (var columnRow in columnRows)
\r
393 var columnName = CreateColumnName(columnRow.ColumnName, columnRow.TableName, columnRow.TableSchema, nameAliases, nameFormat);
\r
394 names.AddColumn(columnRow.TableName, columnName);
\r
396 //find which table this column belongs to
\r
397 string fullColumnDbName = GetFullDbName(columnRow.TableName, columnRow.TableSchema);
\r
398 DbLinq.Schema.Dbml.Table tableSchema = schema.Tables.FirstOrDefault(tblSchema => fullColumnDbName == tblSchema.Name);
\r
399 if (tableSchema == null)
\r
401 WriteErrorLine("ERROR L46: Table '" + columnRow.TableName + "' not found for column " + columnRow.ColumnName);
\r
404 var column = new Column();
\r
405 column.Name = columnName.DbName;
\r
406 column.Member = columnName.PropertyName;
\r
407 column.DbType = columnRow.FullType;
\r
409 if (columnRow.PrimaryKey.HasValue)
\r
410 column.IsPrimaryKey = columnRow.PrimaryKey.Value;
\r
412 bool? generated = (nameAliases != null) ? nameAliases.GetColumnGenerated(columnRow.ColumnName, columnRow.TableName, columnRow.TableSchema) : null;
\r
413 if (!generated.HasValue)
\r
414 generated = columnRow.Generated;
\r
415 if (generated.HasValue)
\r
416 column.IsDbGenerated = generated.Value;
\r
418 AutoSync? autoSync = (nameAliases != null) ? nameAliases.GetColumnAutoSync(columnRow.ColumnName, columnRow.TableName, columnRow.TableSchema) : null;
\r
419 if (autoSync.HasValue)
\r
420 column.AutoSync = autoSync.Value;
\r
422 // the Expression can originate from two sources:
\r
425 // we use any valid source (we can't have both)
\r
426 if (column.IsDbGenerated && columnRow.DefaultValue != null)
\r
427 column.Expression = columnRow.DefaultValue;
\r
429 column.CanBeNull = columnRow.Nullable;
\r
431 string columnTypeAlias = nameAliases != null ? nameAliases.GetColumnForcedType(columnRow.ColumnName, columnRow.TableName, columnRow.TableSchema) : null;
\r
432 var columnType = MapDbType(columnName.DbName, columnRow);
\r
434 var columnEnumType = columnType as EnumType;
\r
435 if (columnEnumType != null)
\r
437 var enumType = column.SetExtendedTypeAsEnumType();
\r
438 enumType.Name = columnEnumType.Name;
\r
439 foreach (KeyValuePair<string, int> enumValue in columnEnumType.EnumValues)
\r
441 enumType[enumValue.Key] = enumValue.Value;
\r
444 else if (columnTypeAlias != null)
\r
445 column.Type = columnTypeAlias;
\r
447 column.Type = columnType.ToString();
\r
449 tableSchema.Type.Columns.Add(column);
\r