/// <summary> /// Initializes a new instance of the <see cref="SqlTableType"/> class. /// </summary> /// <param name="baseType">The base type.</param> /// <param name="sqlSchema">The schema name.</param> /// <param name="name">The table name.</param> /// <param name="defaultSize">The default size information.</param> /// <param name="isNullable"> /// If set to <see langword="true"/> the value can be <see langword="null"/>. /// </param> /// <param name="isUserDefined"> /// If set to <see langword="true"/> the value is a user defined type. /// </param> /// <param name="isClr"> /// If set to <see langword="true"/> the value is a CLR type. /// </param> // ReSharper disable once NotNullMemberIsNotInitialized internal SqlTableType( [CanBeNull] SqlType baseType, [NotNull] SqlSchema sqlSchema, [NotNull] string name, SqlTypeSize defaultSize, bool isNullable, bool isUserDefined, bool isClr) : base( baseType, sqlSchema, name, defaultSize, isNullable, isUserDefined, isClr, true) { }
/// <summary> /// Initializes a new instance of the <see cref="TableDefinitionData" /> class. /// </summary> /// <param name="type">The type.</param> /// <param name="schemaID">The schema identifier.</param> /// <param name="name">The name.</param> /// <param name="ordinal">The ordinal.</param> /// <param name="columnName">Name of the column.</param> /// <param name="columnType">Type of the column.</param> /// <param name="columnSize">Size of the column.</param> /// <param name="isNullable">if set to <see langword="true" /> [is nullable].</param> /// <param name="tableTypeID">The ID of the associated <see cref="SqlType"/> if this table defines a <see cref="SqlType"/>.</param> public TableDefinitionData( SqlObjectType type, int schemaID, [NotNull] string name, int ordinal, [NotNull] string columnName, [NotNull] SqlType columnType, SqlTypeSize columnSize, bool isNullable, int? tableTypeID) { if (name == null) throw new ArgumentNullException("name"); if (columnName == null) throw new ArgumentNullException("columnName"); if (columnType == null) throw new ArgumentNullException("columnType"); Type = type; SchemaID = schemaID; Name = name; Column = new SqlColumn(ordinal, columnName, columnType, columnSize, isNullable); TableTypeID = tableTypeID; }
private async Task<DatabaseSchema> Load(bool forceReload, CancellationToken cancellationToken) { Instant requested = TimeHelpers.Clock.Now; using (await _lock.LockAsync(cancellationToken).ConfigureAwait(false)) { // Check to see if the currently loaded schema is acceptable. CurrentSchema current = _current; // ReSharper disable once ConditionIsAlwaysTrueOrFalse if ((current != null) && (!forceReload || (current.Loaded > requested))) { // Rethrow load errors. if (current.ExceptionDispatchInfo != null) current.ExceptionDispatchInfo.Throw(); Debug.Assert(current.Schema != null); return this; } // Create dictionaries Dictionary<int, SqlSchema> sqlSchemas = new Dictionary<int, SqlSchema>(); Dictionary<int, SqlType> typesByID = new Dictionary<int, SqlType>(); Dictionary<string, SqlType> typesByName = new Dictionary<string, SqlType>(StringComparer.InvariantCultureIgnoreCase); Dictionary<string, SqlProgramDefinition> programDefinitions = new Dictionary<string, SqlProgramDefinition>(StringComparer.InvariantCultureIgnoreCase); Dictionary<string, SqlTableDefinition> tables = new Dictionary<string, SqlTableDefinition>(StringComparer.InvariantCultureIgnoreCase); try { // Open a connection using (SqlConnection sqlConnection = new SqlConnection(ConnectionString)) { // ReSharper disable once PossibleNullReferenceException await sqlConnection.OpenAsync(cancellationToken).ConfigureAwait(false); Version version; if (!Version.TryParse(sqlConnection.ServerVersion, out version)) throw new DatabaseSchemaException( () => Resources.DatabaseSchema_Load_CouldNotParseVersionInformation); Debug.Assert(version != null); if (version.Major < 9) throw new DatabaseSchemaException( () => Resources.DatabaseSchema_Load_VersionNotSupported, version); string sql = version.Major == 9 ? SQLResources.RetrieveSchema9 : SQLResources.RetrieveSchema10; // Create the command first, as we will reuse on each connection. using ( SqlCommand command = new SqlCommand(sql, sqlConnection) { CommandType = CommandType.Text }) // Execute command using (SqlDataReader reader = // ReSharper disable once PossibleNullReferenceException await command.ExecuteReaderAsync(CommandBehavior.SequentialAccess, cancellationToken) .ConfigureAwait(false)) { /* * Load SQL Schemas */ while (await reader.ReadAsync(cancellationToken).ConfigureAwait(false)) { SqlSchema sqlSchema = new SqlSchema(reader.GetInt32(0), reader.GetString(1)); sqlSchemas.Add(sqlSchema.ID, sqlSchema); } if (sqlSchemas.Count < 1) throw new DatabaseSchemaException( () => Resources.DatabaseSchema_Load_CouldNotRetrieveSchemas); /* * Load types */ if (!(await reader.NextResultAsync(cancellationToken).ConfigureAwait(false))) throw new DatabaseSchemaException( () => Resources.DatabaseSchema_Load_RanOutOfResultsRetrievingTypes); while (await reader.ReadAsync(cancellationToken).ConfigureAwait(false)) { int schemaId = reader.GetInt32(0); SqlSchema sqlSchema; if (!sqlSchemas.TryGetValue(schemaId, out sqlSchema) || (sqlSchema == null)) throw new DatabaseSchemaException( () => Resources.DatabaseSchema_Load_CouldNotFindSchema, schemaId); int id = reader.GetInt32(1); string name = reader.GetString(2).ToLower(); SqlType baseType; if (reader.IsDBNull(3)) baseType = null; else { // NB SQL returns types in dependency order // i.e. base types are always seen first, so this code is much easier. int baseId = reader.GetInt32(3); typesByID.TryGetValue(baseId, out baseType); } short maxLength = reader.GetInt16(4); byte precision = reader.GetByte(5); byte scale = reader.GetByte(6); bool isNullable = reader.GetBoolean(7); bool isUserDefined = reader.GetBoolean(8); bool isCLR = reader.GetBoolean(9); bool isTable = reader.GetBoolean(10); // Create type SqlType type = isTable ? new SqlTableType( baseType, sqlSchema, name, new SqlTypeSize(maxLength, precision, scale), isNullable, isUserDefined, isCLR) : new SqlType( baseType, sqlSchema, name, new SqlTypeSize(maxLength, precision, scale), isNullable, isUserDefined, isCLR); // Add to dictionary typesByName.Add(type.FullName, type); if (!typesByName.ContainsKey(type.Name)) typesByName.Add(type.Name, type); typesByID.Add(id, type); } if (typesByName.Count < 1) throw new DatabaseSchemaException( () => Resources.DatabaseSchema_Load_CouldNotRetrieveTypes); /* * Load program definitions */ if (!(await reader.NextResultAsync(cancellationToken).ConfigureAwait(false))) throw new DatabaseSchemaException( () => Resources.DatabaseSchema_Load_RanOutOfResultsRetrievingPrograms); List<ProgramDefinitionData> programDefinitionData = new List<ProgramDefinitionData>(); while (await reader.ReadAsync(cancellationToken).ConfigureAwait(false)) { SqlObjectType type; string typeString = reader.GetString(0) ?? string.Empty; if (!ExtendedEnum<SqlObjectType>.TryParse(typeString, true, out type)) throw new DatabaseSchemaException( () => Resources.DatabaseSchema_Load_CouldNotFindTypeWhenLoadingPrograms, typeString); int schemaId = reader.GetInt32(1); SqlSchema sqlSchema; if (!sqlSchemas.TryGetValue(schemaId, out sqlSchema)) throw new DatabaseSchemaException( () => Resources.DatabaseSchema_Load_CouldNotFindSchemaWhenLoadingPrograms, schemaId); string name = reader.GetString(2).ToLower(); // If we have a null ordinal, we have no parameters. if (reader.IsDBNull(3)) { programDefinitionData.Add(new ProgramDefinitionData(type, schemaId, name)); continue; } int ordinal = reader.GetInt32(3); string parameterName = reader.GetString(4).ToLower(); int typeId = reader.GetInt32(5); SqlType parameterType; if (!typesByID.TryGetValue(typeId, out parameterType) || (parameterType == null)) throw new DatabaseSchemaException( () => Resources.DatabaseSchema_Load_ParameterTypeNotFound, parameterName, typeId, name); short maxLength = reader.GetInt16(6); byte precision = reader.GetByte(7); byte scale = reader.GetByte(8); SqlTypeSize parameterSize = new SqlTypeSize(maxLength, precision, scale); bool isOutput = reader.GetBoolean(9); ParameterDirection parameterDirection; if (!isOutput) parameterDirection = ParameterDirection.Input; else if (parameterName == string.Empty) parameterDirection = ParameterDirection.ReturnValue; else parameterDirection = ParameterDirection.InputOutput; bool parameterIsReadOnly = reader.GetBoolean(10); programDefinitionData.Add( new ProgramDefinitionData( type, schemaId, name, ordinal, parameterName, parameterType, parameterSize, parameterDirection, parameterIsReadOnly)); } // Create unique program definitions. foreach (SqlProgramDefinition program in programDefinitionData // ReSharper disable once PossibleNullReferenceException .GroupBy(d => d.ToString()) .Select( g => { Debug.Assert(g != null); // Get columns ordered by ordinal. SqlProgramParameter[] parameters = g // ReSharper disable once PossibleNullReferenceException .Select(d => d.Parameter) .Where(p => p != null) .OrderBy(p => p.Ordinal) .ToArray(); ProgramDefinitionData first = g.First(); Debug.Assert(first != null); Debug.Assert(first.Name != null); Debug.Assert(sqlSchemas != null); SqlSchema sqlSchema; if (!sqlSchemas.TryGetValue(first.SchemaID, out sqlSchema)) throw new DatabaseSchemaException( () => Resources .DatabaseSchema_Load_CouldNotFindSchemaLoadingTablesAndViews, first.SchemaID); Debug.Assert(sqlSchema != null); return new SqlProgramDefinition( first.Type, sqlSchema, first.Name, parameters); })) { Debug.Assert(program != null); programDefinitions[program.FullName] = program; if (!programDefinitions.ContainsKey(program.Name)) programDefinitions.Add(program.Name, program); } /* * Load tables and views */ if (!(await reader.NextResultAsync(cancellationToken).ConfigureAwait(false))) throw new DatabaseSchemaException( () => Resources.DatabaseSchema_Load_RanOutOfTablesAndViews); // Read raw data in. List<TableDefinitionData> tableDefinitionData = new List<TableDefinitionData>(); while (await reader.ReadAsync(cancellationToken).ConfigureAwait(false)) { SqlObjectType type; string typeString = reader.GetString(0) ?? string.Empty; if (!ExtendedEnum<SqlObjectType>.TryParse(typeString, true, out type)) throw new DatabaseSchemaException( () => Resources.DatabaseSchema_Load_CouldNotFindObjectType, typeString); int schemaId = reader.GetInt32(1); string name = reader.GetString(2).ToLower(); int ordinal = reader.GetInt32(3); string columnName = reader.GetString(4).ToLower(); int typeId = reader.GetInt32(5); SqlType sqlType; if (!typesByID.TryGetValue(typeId, out sqlType) || (sqlType == null)) throw new DatabaseSchemaException( () => Resources.DatabaseSchema_Load_ColumnTypeNotFound, columnName, typeId, name); short maxLength = reader.GetInt16(6); byte precision = reader.GetByte(7); byte scale = reader.GetByte(8); SqlTypeSize sqlTypeSize = new SqlTypeSize(maxLength, precision, scale); bool isNullable = reader.GetBoolean(9); int? tableType = reader.IsDBNull(10) ? null : (int?)reader.GetInt32(10); tableDefinitionData.Add( new TableDefinitionData( type, schemaId, name, ordinal, columnName, sqlType, sqlTypeSize, isNullable, tableType)); } // Create unique table definitions. foreach (SqlTableDefinition table in tableDefinitionData // ReSharper disable once PossibleNullReferenceException .GroupBy(d => d.ToString()) .Select( g => { Debug.Assert(g != null); // Get columns ordered by ordinal. SqlColumn[] columns = g // ReSharper disable PossibleNullReferenceException .Select(d => d.Column) .OrderBy(c => c.Ordinal) // ReSharper restore PossibleNullReferenceException .ToArray(); Debug.Assert(columns.Length > 0); TableDefinitionData first = g.First(); Debug.Assert(first != null); Debug.Assert(first.Name != null); Debug.Assert(sqlSchemas != null); SqlSchema sqlSchema; if (!sqlSchemas.TryGetValue(first.SchemaID, out sqlSchema)) throw new DatabaseSchemaException( () => Resources .DatabaseSchema_Load_CouldNotFindSchemaLoadingTablesAndViews, first.SchemaID); Debug.Assert(sqlSchema != null); SqlTableType tableType; if (first.TableTypeID != null) { Debug.Assert(typesByID != null); SqlType tType; if (!typesByID.TryGetValue(first.TableTypeID.Value, out tType)) throw new DatabaseSchemaException( () => Resources.DatabaseSchema_Load_TableTypeNotFound, first.TableTypeID.Value, first.Name); tableType = tType as SqlTableType; if (tableType == null) throw new DatabaseSchemaException( () => Resources.DatabaseSchema_Load_TypeNotTableType, first.TableTypeID.Value, first.Name); } else tableType = null; return new SqlTableDefinition( first.Type, sqlSchema, first.Name, columns, tableType); })) { Debug.Assert(table != null); tables[table.FullName] = table; if (!tables.ContainsKey(table.Name)) tables.Add(table.Name, table); } } } // Update the current schema. _current = new CurrentSchema(Schema.GetOrAdd(sqlSchemas, programDefinitions, tables, typesByName)); // Always return this return this; } // In the event of an error we don't set the loaded flag - this allows retries. catch (DatabaseSchemaException databaseSchemaException) { // Capture the exception in the current schema. _current = new CurrentSchema(ExceptionDispatchInfo.Capture(databaseSchemaException)); throw; } catch (Exception exception) { // Wrap exception in Database exception. DatabaseSchemaException databaseSchemaException = new DatabaseSchemaException( exception, LoggingLevel.Critical, () => Resources.DatabaseSchema_Load_ErrorOccurred); // Capture the exception in the current schema. _current = new CurrentSchema(ExceptionDispatchInfo.Capture(databaseSchemaException)); throw databaseSchemaException; } } }
/// <summary> /// Initializes a new instance of the <see cref="ProgramDefinitionData" /> class. /// </summary> /// <param name="type">The type.</param> /// <param name="schemaID">The schema identifier.</param> /// <param name="name">The name.</param> /// <param name="ordinal">The ordinal.</param> /// <param name="parameterName">Name of the parameter.</param> /// <param name="parameterType">Type of the parameter.</param> /// <param name="parameterSize">Size of the parameter.</param> /// <param name="parameterDirection">The parameter direction.</param> /// <param name="isReadonly">if set to <see langword="true" /> [is readonly].</param> public ProgramDefinitionData( SqlObjectType type, int schemaID, [NotNull] string name, int ordinal, [NotNull] string parameterName, [NotNull] SqlType parameterType, SqlTypeSize parameterSize, ParameterDirection parameterDirection, bool isReadonly) { if (name == null) throw new ArgumentNullException("name"); if (parameterName == null) throw new ArgumentNullException("parameterName"); if (parameterType == null) throw new ArgumentNullException("parameterType"); Type = type; SchemaID = schemaID; Name = name; Parameter = new SqlProgramParameter( ordinal, parameterName, parameterType, parameterSize, parameterDirection, isReadonly); }