#region License // Copyright 2005-2019 Paul Kohler (https://github.com/paulkohler/minisqlquery). All rights reserved. // This source code is made available under the terms of the GNU Lesser General Public License v3.0 // https://github.com/paulkohler/minisqlquery/blob/master/LICENSE #endregion using System; using System.Collections.Generic; using System.Data; using System.Data.Common; using System.Diagnostics; namespace MiniSqlQuery.Core.DbModel { /// /// The generic schema service. /// public class GenericSchemaService : IDatabaseSchemaService { /// The _connection. private string _connection; /// Gets or sets a value indicating whether ForeignKeyInformationAvailable. /// The foreign key information available. public bool ForeignKeyInformationAvailable { get; set; } /// Gets or sets ProviderName. /// The provider name. public string ProviderName { get; set; } /// Gets a database object model that represents the items defined by the . /// The connection string. /// An instance of describing the database. public virtual DbModelInstance GetDbObjectModel(string connection) { _connection = connection; DbModelInstance model = new DbModelInstance(); DbProviderFactory factory = DbProviderFactories.GetFactory(ProviderName); try { using (DbConnection dbConn = factory.CreateConnection()) { dbConn.ConnectionString = connection; dbConn.Open(); DataTable tables = dbConn.GetSchema("Tables"); Dictionary dbTypes = GetDbTypes(dbConn); model.Types = dbTypes; model.ProviderName = ProviderName; model.ConnectionString = _connection; if (tables == null) { return model; } DataView tablesDV = new DataView(tables, "TABLE_TYPE='TABLE' OR TABLE_TYPE='BASE TABLE'", "TABLE_SCHEMA, TABLE_NAME", DataViewRowState.CurrentRows); foreach (DataRowView row in tablesDV) { string schemaName = SafeGetString(row.Row, "TABLE_SCHEMA"); string tableName = SafeGetString(row.Row, "TABLE_NAME"); DbModelTable dbTable = new DbModelTable { Schema = schemaName, Name = tableName }; model.Add(dbTable); DataTable schemaTableKeyInfo = GetTableKeyInfo(dbConn, schemaName, tableName); GetColumnsForTable(dbTable, schemaTableKeyInfo, dbTypes); } DataView viewsDV = new DataView(tables, "TABLE_TYPE='VIEW'", "TABLE_SCHEMA, TABLE_NAME", DataViewRowState.CurrentRows); foreach (DataRowView row in viewsDV) { string schemaName = SafeGetString(row.Row, "TABLE_SCHEMA"); string tableName = SafeGetString(row.Row, "TABLE_NAME"); DbModelView dbTable = new DbModelView { Schema = schemaName, Name = tableName }; model.Add(dbTable); DataTable schemaTableKeyInfo = GetTableKeyInfo(dbConn, schemaName, tableName); GetColumnsForTable(dbTable, schemaTableKeyInfo, dbTypes); } // build FK relationships if (model.Tables != null) { foreach (DbModelTable table in model.Tables) { GetForeignKeyReferencesForTable(dbConn, table); ProcessForeignKeyReferencesForTable(dbConn, table); } } // build FK relationships if (model.Views != null) { foreach (DbModelView view in model.Views) { GetForeignKeyReferencesForTable(dbConn, view); ProcessForeignKeyReferencesForTable(dbConn, view); } } } } catch (Exception) { // catch all for providers that are not implementing the schema info. return model; } return model; } /// The get db types. /// The connection. /// A dictionary of named objects supported by the database. /// If the is null. public virtual Dictionary GetDbTypes(DbConnection connection) { if (connection == null) { throw new ArgumentNullException("connection"); } Dictionary dbTypes = new Dictionary(); DataTable dataTypes = connection.GetSchema("DataTypes"); foreach (DataRow row in dataTypes.Rows) { string typeName = SafeGetString(row, "TypeName"); int columnSize = SafeGetInt(row, "ColumnSize"); DbModelType dbType = new DbModelType(typeName, columnSize); // Some providers may double up on schema info, check first: if (!dbTypes.ContainsKey(typeName.ToLower())) { dbTypes.Add(typeName.ToLower(), dbType); dbType.CreateFormat = SafeGetString(row, "CreateFormat"); dbType.CreateParameters = SafeGetString(row, "CreateParameters"); dbType.LiteralPrefix = SafeGetString(row, "LiteralPrefix"); dbType.LiteralSuffix = SafeGetString(row, "LiteralSuffix"); dbType.SystemType = Type.GetType(SafeGetString(row, "DataType")); dbType.ProviderDbType = SafeGetString(row, "ProviderDbType"); } } return dbTypes; } /// The get description of the database. public string GetDescription() { return "todo"; } /// The get columns for table. /// The db table. /// The schema table key info. /// The db types. protected virtual void GetColumnsForTable(DbModelTable dbTable, DataTable schemaTableKeyInfo, Dictionary dbTypes) { if (schemaTableKeyInfo == null) { return; } foreach (DataRow columnRow in schemaTableKeyInfo.Rows) { if (SafeGetBool(columnRow, "IsHidden")) { continue; } string columnName = SafeGetString(columnRow, "ColumnName"); string dataType = GetDataTypeNameForColumn(dbTable, schemaTableKeyInfo, columnRow); // note - need a better work around for columns missing the data type info (e.g. access) if (string.IsNullOrEmpty(dataType)) { // try using the "ProviderDbType" to match string providerDbType = SafeGetString(columnRow, "ProviderType"); foreach (var type in dbTypes.Values) { if (type.ProviderDbType == providerDbType) { dataType = type.Name; break; } } } DbModelType dbType = DbModelType.Create( dbTypes, dataType, SafeGetInt(columnRow, "ColumnSize"), SafeGetInt(columnRow, "Precision"), SafeGetInt(columnRow, "Scale"), SafeGetString(columnRow, "DataType")); // todo - FK info DbModelColumn dbColumn = new DbModelColumn { Name = columnName, // Name = MakeSqlFriendly(columnName), Nullable = SafeGetBool(columnRow, "AllowDBNull"), IsKey = SafeGetBool(columnRow, "IsKey"), IsUnique = SafeGetBool(columnRow, "IsUnique"), IsRowVersion = SafeGetBool(columnRow, "IsRowVersion"), IsIdentity = SafeGetBool(columnRow, "IsIdentity"), IsAutoIncrement = SafeGetBool(columnRow, "IsAutoIncrement"), IsReadOnly = SafeGetBool(columnRow, "IsReadOnly"), DbType = dbType, }; dbTable.Add(dbColumn); } } /// The get data type name for column. /// The db table. /// The schema table key info. /// The column row. /// The get data type name for column. protected virtual string GetDataTypeNameForColumn(DbModelTable dbTable, DataTable schemaTableKeyInfo, DataRow columnRow) { return SafeGetString(columnRow, "DataTypeName"); } /// The get foreign key references for table. /// The db conn. /// The db table. protected virtual void GetForeignKeyReferencesForTable(DbConnection dbConn, DbModelTable dbTable) { // foreach (DbModelColumn column in dbTable.Columns) // { // // KF info for DB's varies widley, needs to be implemented by derived class // column.ForeignKeyReference = DbModelForeignKeyReference.NullForeignKeyReference; // } } /// The get table key information. /// The database connection. /// The schema. /// The name of the table. /// A describing the tables columns and key information. protected virtual DataTable GetTableKeyInfo(DbConnection dbConn, string schema, string name) { DataTable schemaTableKeyInfo = null; try { using (DbCommand command = dbConn.CreateCommand()) { string tableName = Utility.RenderSafeSchemaObjectName(schema, name); command.CommandText = "SELECT * FROM " + MakeSqlFriendly(tableName); using (DbDataReader reader = command.ExecuteReader(CommandBehavior.SchemaOnly | CommandBehavior.KeyInfo)) { schemaTableKeyInfo = reader.GetSchemaTable(); } } } catch (Exception exp) { // Generic catch all - not all provoders stick to the DbException base: Debug.WriteLine(GetType().FullName + " ERROR: " + exp.Message); } return schemaTableKeyInfo; } /// The make sql friendly. /// The name. /// The make sql friendly. protected virtual string MakeSqlFriendly(string name) { return Utility.MakeSqlFriendly(name); } /// The process foreign key references for table. /// The db conn. /// The db table. protected virtual void ProcessForeignKeyReferencesForTable(DbConnection dbConn, DbModelTable dbTable) { } /// The safe get bool. /// The row. /// The column name. /// The safe get bool. protected bool SafeGetBool(DataRow row, string columnName) { if (row.Table.Columns.Contains(columnName) && !row.IsNull(columnName)) { string value = row[columnName].ToString(); switch (value.ToLower()) { case "no": case "false": return false; case "yes": case "true": return true; } } return false; } /// The safe get int. /// The row. /// The column name. /// The safe get int. protected virtual int SafeGetInt(DataRow row, string columnName) { try { int result = -1; if (row.Table.Columns.Contains(columnName) && !row.IsNull(columnName)) { result = Convert.ToInt32(row[columnName]); } return result; } catch (OverflowException) { // In Oracle Maximum size for column is larger than Max Int32, instead of changing return value, just coerce on Max.Int32. return Int32.MaxValue; } } /// The safe get string. /// The row. /// The column name. /// The safe get string. protected string SafeGetString(DataRow row, string columnName) { string result = string.Empty; if (row.Table.Columns.Contains(columnName) && !row.IsNull(columnName)) { result = row[columnName].ToString(); } return result; } } }