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
42 namespace DbLinq.Vendor.Implementation
\r
49 abstract partial class SchemaLoader : ISchemaLoader
\r
52 /// Underlying vendor
\r
55 public abstract IVendor Vendor { get; }
\r
57 /// Vendor typed DataContext type
\r
60 public abstract System.Type DataContextType { get; }
\r
62 /// Connection used to read schema
\r
65 public IDbConnection Connection { get; set; }
\r
67 /// Gets or sets the name formatter.
\r
69 /// <value>The name formatter.</value>
\r
70 public INameFormatter NameFormatter { get; set; }
\r
72 private TextWriter log;
\r
76 public TextWriter Log
\r
78 get { return log ?? Console.Out; }
\r
79 set { log = value; }
\r
83 /// Loads database schema
\r
85 /// <param name="databaseName"></param>
\r
86 /// <param name="nameAliases"></param>
\r
87 /// <param name="nameFormat"></param>
\r
88 /// <param name="loadStoredProcedures"></param>
\r
89 /// <param name="contextNamespace"></param>
\r
90 /// <param name="entityNamespace"></param>
\r
91 /// <returns></returns>
\r
92 public virtual Database Load(string databaseName, INameAliases nameAliases, NameFormat nameFormat,
\r
93 bool loadStoredProcedures, string contextNamespace, string entityNamespace)
\r
95 // check if connection is open. Note: we may use something more flexible
\r
96 if (Connection.State != ConnectionState.Open)
\r
99 // get the database name. If we don't have one, take it from connection string...
\r
100 if (string.IsNullOrEmpty(databaseName))
\r
101 databaseName = Connection.Database;
\r
102 // ... and if connection string doesn't provide a name, then throw an error
\r
103 if (string.IsNullOrEmpty(databaseName))
\r
104 throw new ArgumentException("A database name is required. Please specify /database=<databaseName>");
\r
106 databaseName = GetDatabaseName(databaseName);
\r
108 var schemaName = NameFormatter.GetSchemaName(databaseName, GetExtraction(databaseName), nameFormat);
\r
109 var names = new Names();
\r
110 var schema = new Database
\r
112 Name = schemaName.DbName,
\r
113 Class = schemaName.ClassName,
\r
114 BaseType = typeof(DataContext).FullName,
\r
115 ContextNamespace = contextNamespace,
\r
116 EntityNamespace = entityNamespace,
\r
119 // order is important, we must have:
\r
123 LoadTables(schema, schemaName, Connection, nameAliases, nameFormat, names);
\r
124 LoadColumns(schema, schemaName, Connection, nameAliases, nameFormat, names);
\r
125 CheckColumnsName(schema);
\r
126 LoadConstraints(schema, schemaName, Connection, nameFormat, names);
\r
127 CheckConstraintsName(schema);
\r
128 if (loadStoredProcedures)
\r
129 LoadStoredProcedures(schema, schemaName, Connection, nameFormat);
\r
130 // names aren't checked here anymore, because this confuses DBML editor.
\r
131 // they will (for now) be checked before .cs generation
\r
132 // in the end, when probably will end up in mapping source (or somewhere around)
\r
133 //CheckNamesSafety(schema);
\r
135 // generate backing fields name (since we have here correct names)
\r
136 GenerateStorageFields(schema);
\r
142 /// Gets a usable name for the database.
\r
144 /// <param name="databaseName">Name of the database.</param>
\r
145 /// <returns></returns>
\r
146 protected virtual string GetDatabaseName(string databaseName)
\r
148 return databaseName;
\r
152 /// Writes an error line.
\r
154 /// <param name="format">The format.</param>
\r
155 /// <param name="arg">The arg.</param>
\r
156 protected void WriteErrorLine(string format, params object[] arg)
\r
159 if (o == Console.Out)
\r
161 o.WriteLine(format, arg);
\r
164 protected SchemaLoader()
\r
166 NameFormatter = ObjectFactory.Create<INameFormatter>(); // the Pluralize property is set dynamically, so no singleton
\r
170 /// Gets the extraction type from a columnname.
\r
172 /// <param name="dbColumnName">Name of the db column.</param>
\r
173 /// <returns></returns>
\r
174 protected virtual WordsExtraction GetExtraction(string dbColumnName)
\r
176 bool isMixedCase = dbColumnName != dbColumnName.ToLower() && dbColumnName != dbColumnName.ToUpper();
\r
177 return isMixedCase ? WordsExtraction.FromCase : WordsExtraction.FromDictionary;
\r
181 /// Gets the full name of a name and schema.
\r
183 /// <param name="dbName">Name of the db.</param>
\r
184 /// <param name="dbSchema">The db schema.</param>
\r
185 /// <returns></returns>
\r
186 protected virtual string GetFullDbName(string dbName, string dbSchema)
\r
189 if (dbSchema == null)
\r
190 fullDbName = dbName;
\r
192 fullDbName = string.Format("{0}.{1}", dbSchema, dbName);
\r
197 /// Creates the name of the table given a name and schema
\r
199 /// <param name="dbTableName">Name of the db table.</param>
\r
200 /// <param name="dbSchema">The db schema.</param>
\r
201 /// <param name="nameAliases">The name aliases.</param>
\r
202 /// <param name="nameFormat">The name format.</param>
\r
203 /// <param name="extraction">The extraction.</param>
\r
204 /// <returns></returns>
\r
205 protected virtual TableName CreateTableName(string dbTableName, string dbSchema, INameAliases nameAliases, NameFormat nameFormat, WordsExtraction extraction)
\r
207 // if we have an alias, use it, and don't try to analyze it (a human probably already did the job)
\r
208 var tableTypeAlias = nameAliases != null ? nameAliases.GetTableTypeAlias(dbTableName, dbSchema) : null;
\r
209 if (tableTypeAlias != null)
\r
210 extraction = WordsExtraction.None;
\r
212 tableTypeAlias = dbTableName;
\r
214 var tableName = NameFormatter.GetTableName(tableTypeAlias, extraction, nameFormat);
\r
216 // alias for member
\r
217 var tableMemberAlias = nameAliases != null ? nameAliases.GetTableMemberAlias(dbTableName, dbSchema) : null;
\r
218 if (tableMemberAlias != null)
\r
219 tableName.MemberName = tableMemberAlias;
\r
221 tableName.DbName = GetFullDbName(dbTableName, dbSchema);
\r
225 protected virtual TableName CreateTableName(string dbTableName, string dbSchema, INameAliases nameAliases, NameFormat nameFormat)
\r
227 return CreateTableName(dbTableName, dbSchema, nameAliases, nameFormat, GetExtraction(dbTableName));
\r
231 /// Creates the name of the column.
\r
233 /// <param name="dbColumnName">Name of the db column.</param>
\r
234 /// <param name="dbTableName">Name of the db table.</param>
\r
235 /// <param name="dbSchema">The db schema.</param>
\r
236 /// <param name="nameAliases">The name aliases.</param>
\r
237 /// <param name="nameFormat">The name format.</param>
\r
238 /// <returns></returns>
\r
239 protected virtual ColumnName CreateColumnName(string dbColumnName, string dbTableName, string dbSchema, INameAliases nameAliases, NameFormat nameFormat)
\r
241 var columnNameAlias = nameAliases != null ? nameAliases.GetColumnMemberAlias(dbColumnName, dbTableName, dbSchema) : null;
\r
242 WordsExtraction extraction;
\r
243 if (columnNameAlias != null)
\r
245 extraction = WordsExtraction.None;
\r
249 extraction = GetExtraction(dbColumnName);
\r
250 columnNameAlias = dbColumnName;
\r
252 var columnName = NameFormatter.GetColumnName(columnNameAlias, extraction, nameFormat);
\r
253 // The member name can not be the same as the class
\r
254 // we add a "1" (just like SqlMetal does)
\r
255 var tableName = CreateTableName(dbTableName, dbSchema, nameAliases, nameFormat);
\r
256 if (columnName.PropertyName == tableName.ClassName)
\r
257 columnName.PropertyName = columnName.PropertyName + "1";
\r
258 columnName.DbName = dbColumnName;
\r
263 /// Creates the name of the procedure.
\r
265 /// <param name="dbProcedureName">Name of the db procedure.</param>
\r
266 /// <param name="dbSchema">The db schema.</param>
\r
267 /// <param name="nameFormat">The name format.</param>
\r
268 /// <returns></returns>
\r
269 protected virtual ProcedureName CreateProcedureName(string dbProcedureName, string dbSchema, NameFormat nameFormat)
\r
271 var procedureName = NameFormatter.GetProcedureName(dbProcedureName, GetExtraction(dbProcedureName), nameFormat);
\r
272 procedureName.DbName = GetFullDbName(dbProcedureName, dbSchema);
\r
273 return procedureName;
\r
277 /// Creates the name of the association.
\r
279 /// <param name="dbManyName">Name of the db many.</param>
\r
280 /// <param name="dbManySchema">The db many schema.</param>
\r
281 /// <param name="dbOneName">Name of the db one.</param>
\r
282 /// <param name="dbOneSchema">The db one schema.</param>
\r
283 /// <param name="dbConstraintName">Name of the db constraint.</param>
\r
284 /// <param name="foreignKeyName">Name of the foreign key.</param>
\r
285 /// <param name="nameFormat">The name format.</param>
\r
286 /// <returns></returns>
\r
287 protected virtual AssociationName CreateAssociationName(string dbManyName, string dbManySchema,
\r
288 string dbOneName, string dbOneSchema, string dbConstraintName, string foreignKeyName, NameFormat nameFormat)
\r
290 var associationName = NameFormatter.GetAssociationName(dbManyName, dbOneName,
\r
291 dbConstraintName, foreignKeyName, GetExtraction(dbManyName), nameFormat);
\r
292 associationName.DbName = GetFullDbName(dbManyName, dbManySchema);
\r
293 return associationName;
\r
297 /// Creates the name of the schema.
\r
299 /// <param name="databaseName">Name of the database.</param>
\r
300 /// <param name="connection">The connection.</param>
\r
301 /// <param name="nameFormat">The name format.</param>
\r
302 /// <returns></returns>
\r
303 protected virtual SchemaName CreateSchemaName(string databaseName, IDbConnection connection, NameFormat nameFormat)
\r
305 if (string.IsNullOrEmpty(databaseName))
\r
307 databaseName = connection.Database;
\r
308 if (string.IsNullOrEmpty(databaseName))
\r
309 throw new ArgumentException("Could not deduce database name from connection string. Please specify /database=<databaseName>");
\r
311 return NameFormatter.GetSchemaName(databaseName, GetExtraction(databaseName), nameFormat);
\r
314 protected virtual ParameterName CreateParameterName(string dbParameterName, NameFormat nameFormat)
\r
316 var parameterName = NameFormatter.GetParameterName(dbParameterName, GetExtraction(dbParameterName), nameFormat);
\r
317 return parameterName;
\r
320 protected class Names
\r
322 public IDictionary<string, TableName> TablesNames = new Dictionary<string, TableName>();
\r
323 public IDictionary<string, IDictionary<string, ColumnName>> ColumnsNames = new Dictionary<string, IDictionary<string, ColumnName>>();
\r
325 public void AddColumn(string dbTableName, ColumnName columnName)
\r
327 IDictionary<string, ColumnName> columns;
\r
328 if (!ColumnsNames.TryGetValue(dbTableName, out columns))
\r
330 columns = new Dictionary<string, ColumnName>();
\r
331 ColumnsNames[dbTableName] = columns;
\r
333 columns[columnName.DbName] = columnName;
\r
338 /// Loads the tables in the given schema.
\r
340 /// <param name="schema">The schema.</param>
\r
341 /// <param name="schemaName">Name of the schema.</param>
\r
342 /// <param name="conn">The conn.</param>
\r
343 /// <param name="nameAliases">The name aliases.</param>
\r
344 /// <param name="nameFormat">The name format.</param>
\r
345 /// <param name="names">The names.</param>
\r
346 protected virtual void LoadTables(Database schema, SchemaName schemaName, IDbConnection conn, INameAliases nameAliases, NameFormat nameFormat, Names names)
\r
348 var tables = ReadTables(conn, schemaName.DbName);
\r
349 foreach (var row in tables)
\r
351 var tableName = CreateTableName(row.Name, row.Schema, nameAliases, nameFormat);
\r
352 names.TablesNames[tableName.DbName] = tableName;
\r
354 var table = new Table();
\r
355 table.Name = tableName.DbName;
\r
356 table.Member = tableName.MemberName;
\r
357 table.Type.Name = tableName.ClassName;
\r
358 schema.Tables.Add(table);
\r
363 /// Loads the columns.
\r
365 /// <param name="schema">The schema.</param>
\r
366 /// <param name="schemaName">Name of the schema.</param>
\r
367 /// <param name="conn">The conn.</param>
\r
368 /// <param name="nameAliases">The name aliases.</param>
\r
369 /// <param name="nameFormat">The name format.</param>
\r
370 /// <param name="names">The names.</param>
\r
371 protected void LoadColumns(Database schema, SchemaName schemaName, IDbConnection conn, INameAliases nameAliases, NameFormat nameFormat, Names names)
\r
373 var columnRows = ReadColumns(conn, schemaName.DbName);
\r
374 foreach (var columnRow in columnRows)
\r
376 var columnName = CreateColumnName(columnRow.ColumnName, columnRow.TableName, columnRow.TableSchema, nameAliases, nameFormat);
\r
377 names.AddColumn(columnRow.TableName, columnName);
\r
379 //find which table this column belongs to
\r
380 string fullColumnDbName = GetFullDbName(columnRow.TableName, columnRow.TableSchema);
\r
381 DbLinq.Schema.Dbml.Table tableSchema = schema.Tables.FirstOrDefault(tblSchema => fullColumnDbName == tblSchema.Name);
\r
382 if (tableSchema == null)
\r
384 WriteErrorLine("ERROR L46: Table '" + columnRow.TableName + "' not found for column " + columnRow.ColumnName);
\r
387 var column = new Column();
\r
388 column.Name = columnName.DbName;
\r
389 column.Member = columnName.PropertyName;
\r
390 column.DbType = columnRow.FullType;
\r
392 if (columnRow.PrimaryKey.HasValue)
\r
393 column.IsPrimaryKey = columnRow.PrimaryKey.Value;
\r
395 if (columnRow.Generated.HasValue)
\r
396 column.IsDbGenerated = columnRow.Generated.Value;
\r
398 // the Expression can originate from two sources:
\r
401 // we use any valid source (we can't have both)
\r
402 if (column.IsDbGenerated && columnRow.DefaultValue != null)
\r
403 column.Expression = columnRow.DefaultValue;
\r
405 column.CanBeNull = columnRow.Nullable;
\r
407 var columnType = MapDbType(columnName.DbName, columnRow);
\r
409 var columnEnumType = columnType as EnumType;
\r
410 if (columnEnumType != null)
\r
412 var enumType = column.SetExtendedTypeAsEnumType();
\r
413 enumType.Name = columnEnumType.Name;
\r
414 foreach (KeyValuePair<string, int> enumValue in columnEnumType.EnumValues)
\r
416 enumType[enumValue.Key] = enumValue.Value;
\r
420 column.Type = columnType.ToString();
\r
422 tableSchema.Type.Columns.Add(column);
\r