/** * RelationshipDiscoverer Constructor */ public RelationshipDiscoverer(HashSet <Type> Models) { this.Discovered = new List <Relation>(); // Loop through all models in the context. Models.ForEach(model => { // Loop through all mapped props of the model Model.Dynamic(model).MappedProps.ForEach(prop => { // Ignore primative types. if (TypeMapper.IsClrType(prop.PropertyType)) { return; } // Get the relationship discriptor. Relation?relation; if (TypeMapper.IsListOfEntities(prop)) { if ((relation = this.IsManyToMany(prop)) == null) { if ((relation = this.IsManyToOne(prop)) == null) { throw new UnknownRelationshipException(prop); } } } else { // Make sure the type is a Graceful Model. // If this exception throws, it probably means the // TypeMapper has failed us. if (!prop.PropertyType.IsSubclassOf(typeof(Model))) { throw new UnknownRelationshipException(prop); } if ((relation = this.IsOneToMany(prop)) == null) { if ((relation = this.IsOneToOne(prop)) == null) { throw new UnknownRelationshipException(prop); } } } // Add it to our discovered list. this.Discovered.Add((Relation)relation); }); }); }
/** * Builds the SQL Query that will be used to ALTER * an existing table to match the Model Class. */ protected void BuildAlterTableQuery(StringBuilder query, string table, List <PropertyInfo> props) { // Loop through each of the properties. // Regardless of if the column is of the correct data type or not // we will create an ALTER statement for it. That way we can be // sure the column is the correct type. props.ForEach(prop => { // Skip the Primary Key, this should never change. if (prop.Name == "Id") { return; } // Check to see if the type is a built in clr type or not. if (TypeMapper.IsClrType(prop.PropertyType)) { // Open the ALTER TABLE statement query.Append("ALTER TABLE "); query.Append(new SqlId(this.Ctx.DatabaseName + ".dbo." + table).Value); // Does the column already exist? var colExists = this.Ctx.Qb.ColumnExists(table, prop.Name); if (colExists) { query.Append(" ALTER COLUMN "); } else { query.Append(" ADD "); } // Append the name of the column query.Append(new SqlId(prop.Name).Value); query.Append(" "); // Append the column type query.Append(this.GetColumnType(prop)); // Append the column length query.Append(this.GetColumnLength(prop)); // Set the nullability of the column if (this.IsNullableProperty(prop)) { query.Append(" NULL"); } else { query.Append(" NOT NULL"); // If we are "ADDING" a new column and the table is not // empty then we need to set a default value that existing // rows will get set to. An empty string seems to work. if (!colExists && !this.Ctx.Qb.TableEmpty(table)) { query.Append(" DEFAULT ''"); } } // Is the column unique? if (this.IsUnique(prop)) { this.UniqueConstraints.Add(new UniqueConstraint { Table = table, Column = prop.Name, Strict = prop.GetCustomAttribute <UniqueAttribute>(false).Strict }); } // Next column query.Append(";\n"); } else { // The type is a Complex Type, ie: a relationship. // Lets get the pre discovered relationship details. var relation = this.Ctx.Relationships.Discovered.Single ( r => r.LocalProperty == prop ); // Have we already dealt with the other side of this relationship? if (this.DealtWithRelationships.Any(p => p == relation.ForeignProperty)) { // We have so there is nothing for us to do here. // Continue on to the next property. return; } else { // We have not dealt with this relationship. // So lets make it known that we have now. this.DealtWithRelationships.Add(prop); } // Take action based on the relationship type. switch (relation.Type) { case RelationshipDiscoverer.Relation.RelationType.MtoM: // Create the pivot table, we don't need to add // anything at all to this tables definition. this.UpdatePivotTable(query, relation); break; case RelationshipDiscoverer.Relation.RelationType.MtoO: // Create a new foreign key column // in the foreign table. this.UpdateFKToFT(relation); break; case RelationshipDiscoverer.Relation.RelationType.OtoM: // Create a new foreign key // for the 1:Many relationship. this.UpdateFKToLTOm(query, relation); break; case RelationshipDiscoverer.Relation.RelationType.OtoO: // Create a new foreign key for the 1:1 relationship. this.UpdateFKToLTOo(query, relation); break; } } }); // If we are allowed to sustain some data losses, // we will remove any columns that are no longer needed. if (DataLossAllowed) { using (var reader = this.Ctx.Qb .SELECT("{0}", new SqlId("COLUMN_NAME")) .FROM("INFORMATION_SCHEMA.COLUMNS") .WHERE("{0} = {1}", new SqlId("TABLE_NAME"), table) .Reader) { while (reader.Read()) { var existingCol = reader.GetString(0); if (!props.Exists(p => p.Name == existingCol)) { // Guard against deleting Foregin Key Cols if (!this.Ctx.Relationships.Discovered.Any(r => r.ForeignKeyTableName == table && r.ForeignKeyColumnName == existingCol)) { query.Append("ALTER TABLE "); query.Append(new SqlId(this.Ctx.DatabaseName + ".dbo." + table).Value); query.Append(" DROP COLUMN "); query.Append(new SqlId(existingCol).Value); query.Append(";\n"); } } } } } }
/** * Builds the SQL Query that will be used to CREATE * a new table that matches the Model Class. */ protected void BuildCreateTableQuery(StringBuilder query, string table, List <PropertyInfo> props) { // Open the CREATE TABLE statement query.Append("CREATE TABLE "); query.Append(new SqlId(this.Ctx.DatabaseName + ".dbo." + table).Value); query.Append("\n(\n"); // Loop through each of the properties props.ForEach(prop => { // Check to see if the type is a built in clr type or not. if (TypeMapper.IsClrType(prop.PropertyType)) { // Append the name of the column query.Append("\t"); query.Append(new SqlId(prop.Name).Value); query.Append(" "); // Append the column type query.Append(this.GetColumnType(prop)); // Append the column length query.Append(this.GetColumnLength(prop)); // Is the column the primary key? if (prop.GetCustomAttribute(typeof(KeyAttribute)) != null) { query.Append(" IDENTITY(1,1) PRIMARY KEY"); } else { // Set the nullability of the column if (this.IsNullableProperty(prop)) { query.Append(" NULL"); } else { query.Append(" NOT NULL"); } // Is the column unique? if (this.IsUnique(prop)) { this.UniqueConstraints.Add(new UniqueConstraint { Table = table, Column = prop.Name, Strict = prop.GetCustomAttribute <UniqueAttribute>(false).Strict }); } } // Next column query.Append(",\n"); } else { // The type is a Complex Type, ie: a relationship. // Lets get the pre discovered relationship details. var relation = this.Ctx.Relationships.Discovered.Single ( r => r.LocalProperty == prop ); // Have we already dealt with the other side of this relationship? if (this.DealtWithRelationships.Any(p => p == relation.ForeignProperty)) { // We have so there is nothing for us to do here. // Continue on to the next property. return; } else { // We have not dealt with this relationship. // So lets make it known that we have now. this.DealtWithRelationships.Add(prop); } // Take action based on the relationship type. switch (relation.Type) { case RelationshipDiscoverer.Relation.RelationType.MtoM: // Create the pivot table, we don't need to add // anything at all to this tables definition. this.CreatePivotTable(relation); break; case RelationshipDiscoverer.Relation.RelationType.MtoO: // Create a new foreign key column in the foreign table. this.AddFKToFT(relation); break; case RelationshipDiscoverer.Relation.RelationType.OtoM: // Create a new foreign key column in this table. this.AddFKToLTOm(query, relation); break; case RelationshipDiscoverer.Relation.RelationType.OtoO: // Create a new foreign key column in this table. this.AddFKToLTOo(query, relation); break; } } }); // Remove the last comma if (query[query.Length - 2] == ',') { query.Remove(query.Length - 2, 2); } // Close the statement query.Append("\n);"); }