PageRenderTime 43ms CodeModel.GetById 2ms app.highlight 33ms RepoModel.GetById 1ms app.codeStats 1ms

/mcs/class/System.Data.Linq/src/DbLinq/Vendor/Implementation/SchemaLoader.cs

https://github.com/ekovalenko-softheme/mono
C# | 453 lines | 252 code | 45 blank | 156 comment | 49 complexity | d463806215a1d766ac5237d6d5e67f00 MD5 | raw file
  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
 26using System;
 27using System.Collections.Generic;
 28using System.Data;
 29using System.IO;
 30using System.Linq;
 31
 32#if MONO_STRICT
 33using System.Data.Linq;
 34#else
 35using DbLinq.Data.Linq;
 36#endif
 37
 38using DbLinq.Factory;
 39using DbLinq.Schema;
 40using DbLinq.Schema.Dbml;
 41using System.Text.RegularExpressions;
 42
 43namespace 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>
 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>
140        protected virtual string GetDatabaseName(string databaseName)
141        {
142            return databaseName;
143        }
144
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>
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>
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
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>
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>
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>
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
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>
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>
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>
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>
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
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
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>
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>
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}