/// <summary> /// Return the new added column, tables or modified prooerty /// Property Rename is not supported. renaming a property x will end up removing the column x and adding column y so there will be dataloss /// Adding a primary key is not supported either /// Abstract classes are ignored by default /// </summary> /// <assembly> Null for the current executed Assembly </assembly> /// <returns></returns> protected CodeToDataBaseMergeCollection GetCodeLatestChanges(Assembly assembly = null) { var codeToDataBaseMergeCollection = new CodeToDataBaseMergeCollection(this); MethodHelper.GetDbEntitys(assembly ?? this.GetType().Assembly).ForEach(x => { if (!x.IsAbstract) // Ignore abstract classes by default { _dbSchema.GetDatabase_Diff(x, codeToDataBaseMergeCollection); } }); return(codeToDataBaseMergeCollection); }
public CodeToDataBaseMergeCollection GetDatabase_Diff(Type tableType, CodeToDataBaseMergeCollection str = null, List <Type> createdTables = null) { str = str ?? new CodeToDataBaseMergeCollection(_repository); if (tableType.GetCustomAttribute <ExcludeFromAbstract>() != null || _alreadyControlled.Any(x => x == tableType)) { return(str); } _repository.CreateSchema(tableType); tableType = tableType.GetActualType(); _alreadyControlled.Add(tableType); createdTables = createdTables ?? new List <Type>(); if (createdTables.Any(x => x == tableType) || tableType.GetPrimaryKey() == null) { return(str); } if (CodeToDataBaseMergeCollection.ExecutedData.ContainsKey(tableType.FullName + _repository.DataBaseTypes.ToString())) { return(str); } createdTables.Add(tableType); var table = _repository.GetColumnSchema(tableType); var tableName = tableType.TableName(); var props = DeepCloner.GetFastDeepClonerProperties(tableType).Where(x => x.CanRead && !x.ContainAttribute <ExcludeFromAbstract>()); var codeToDataBaseMerge = new CodeToDataBaseMerge() { Object_Type = tableType }; var isPrimaryKey = ""; if (!IsValidName(tableName.Name)) { throw new EntityException(tableName.Name + " is not a valid Name for the current provider " + _repository.DataBaseTypes); } if (!table.Values.Any()) { codeToDataBaseMerge.Sql = new StringBuilder($"CREATE TABLE {tableName.GetName(_repository.DataBaseTypes)} ("); foreach (var prop in props.Where(x => (x.GetDbTypeByType(_repository.DataBaseTypes) != null || !x.IsInternalType || x.ContainAttribute <JsonDocument>() || x.ContainAttribute <XmlDocument>()) && !x.ContainAttribute <ExcludeFromAbstract>()).GroupBy(x => x.Name).Select(x => x.First()) .OrderBy(x => x.ContainAttribute <PrimaryKey>() ? null : x.Name)) { if (!prop.IsInternalType && !prop.ContainAttribute <JsonDocument>() && !prop.ContainAttribute <XmlDocument>()) { if (!str.Any(x => x.Object_Type == prop.PropertyType.GetActualType()) && createdTables.All(x => x != prop.PropertyType.GetActualType())) { GetDatabase_Diff(prop.PropertyType, str, createdTables); } continue; } isPrimaryKey = prop.ContainAttribute <PrimaryKey>() ? prop.GetPropertyName() : isPrimaryKey; var foreignKey = prop.GetCustomAttribute <ForeignKey>(); var dbType = prop.GetDbTypeByType(_repository.DataBaseTypes); var propName = string.Format("[{0}]", prop.GetPropertyName()); codeToDataBaseMerge.Sql.Append(propName + " "); if (!IsValidName(prop.GetPropertyName())) { throw new Exception(prop.GetPropertyName() + " is not a valid Name for the current provider " + _repository.DataBaseTypes); } if (!prop.ContainAttribute <PrimaryKey>() || _repository.DataBaseTypes == DataBaseTypes.Mssql) { codeToDataBaseMerge.Sql.Append(dbType + " "); } if (foreignKey != null && createdTables.All(x => x != foreignKey.Type)) { GetDatabase_Diff(foreignKey.Type, str, createdTables); } if (prop.ContainAttribute <PrimaryKey>()) { if (prop.PropertyType.IsNumeric() && prop.GetCustomAttribute <PrimaryKey>().AutoGenerate) { codeToDataBaseMerge.Sql.Append(_repository.DataBaseTypes == DataBaseTypes.Mssql ? "IDENTITY(1,1) NOT NULL," : (_repository.DataBaseTypes == DataBaseTypes.Sqllight ? " Integer PRIMARY KEY AUTOINCREMENT," : " BIGSERIAL PRIMARY KEY,")); } else { codeToDataBaseMerge.Sql.Append(_repository.DataBaseTypes == DataBaseTypes.Mssql ? "NOT NULL," : " " + dbType + " PRIMARY KEY,"); } continue; } if (foreignKey != null) { var key = propName + "-" + tableName.GetName(_repository.DataBaseTypes); if (!str.Keys.ContainsKey(key)) { str.Keys.Add(key, new Tuple <string, ForeignKey>(tableName.GetName(_repository.DataBaseTypes), foreignKey)); } } codeToDataBaseMerge.Sql.Append((Nullable.GetUnderlyingType(prop.PropertyType) != null || prop.PropertyType == typeof(string)) && !prop.ContainAttribute <NotNullable>() ? " NULL," : " NOT NULL,"); } if (str.Keys.Any() && _repository.DataBaseTypes == DataBaseTypes.Sqllight) { while (str.Keys.Any(x => x.Value.Item1 == tableName.Name)) { var key = str.Keys.FirstOrDefault(x => x.Value.Item1 == tableName.Name); var type = key.Value.Item2.Type.GetActualType(); var keyPrimary = type.GetPrimaryKey().GetPropertyName(); var tb = type.TableName(); codeToDataBaseMerge.Sql.Append("FOREIGN KEY(" + key.Key.Split('-')[0] + ") REFERENCES " + tb.GetName(_repository.DataBaseTypes) + "(" + keyPrimary + "),"); str.Keys.Remove(key.Key); } } if (!string.IsNullOrEmpty(isPrimaryKey) && _repository.DataBaseTypes == DataBaseTypes.Mssql) { codeToDataBaseMerge.Sql.Append(" CONSTRAINT [PK_" + tableName.Name + "] PRIMARY KEY CLUSTERED"); codeToDataBaseMerge.Sql.Append(" ([" + isPrimaryKey + "] ASC"); codeToDataBaseMerge.Sql.Append(")"); codeToDataBaseMerge.Sql.Append("WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]"); codeToDataBaseMerge.Sql.Append(") ON [PRIMARY]"); } else { if (_repository.DataBaseTypes == DataBaseTypes.Sqllight) { codeToDataBaseMerge.Sql = new StringBuilder(codeToDataBaseMerge.Sql.ToString().TrimEnd(',')); } codeToDataBaseMerge.Sql.Append(")"); } str.Add(codeToDataBaseMerge); } else { foreach (var prop in props.Where(x => (x.GetDbTypeByType(_repository.DataBaseTypes) != null || !x.IsInternalType) && !x.ContainAttribute <ExcludeFromAbstract>()).GroupBy(x => x.Name).Select(x => x.First()) .OrderBy(x => x.ContainAttribute <PrimaryKey>() ? null : x.Name)) { if (prop.ContainAttribute <ForeignKey>()) { GetDatabase_Diff(prop.GetCustomAttribute <ForeignKey>().Type, str, createdTables); } var propType = prop.PropertyType; if (prop.ContainAttribute <Stringify>() || prop.ContainAttribute <DataEncode>() || prop.ContainAttribute <ToBase64String>() || prop.ContainAttribute <JsonDocument>() || prop.ContainAttribute <XmlDocument>()) { propType = typeof(string); } var modify = prop.IsInternalType || prop.ContainAttribute <JsonDocument>() || prop.ContainAttribute <XmlDocument>() ? (_repository.DataBaseTypes == DataBaseTypes.PostgreSql ? table.Get(prop.GetPropertyName().ToLower()) : table.Get(prop.GetPropertyName())) : null; if (modify != null) { if (_repository.DataBaseTypes != DataBaseTypes.Sqllight && !(prop.GetDbTypeListByType(_repository.DataBaseTypes).Any(x => x.ToLower().Contains(modify.DataType.ToLower()))) && _repository.DataBaseTypes != DataBaseTypes.PostgreSql) { var constraine = Properties.Resources.DropContraine .Replace("@tb", $"'{tableName.Name}'").Replace("@col", $"'{prop.GetPropertyName()}'") .Replace("@schema", $"'{tableName.Schema ?? ""}'") .Replace("@TableName", "@" + counter++) .Replace("@ColumnName", "@" + counter++) .Replace("@fullName", "@" + counter++) .Replace("@DROP_COMMAND", "@" + counter++) .Replace("@FOREIGN_KEY_NAME", "@" + counter++); codeToDataBaseMerge.Sql.Append(constraine); codeToDataBaseMerge.Sql.Append($"\nALTER TABLE {tableName.GetName(_repository.DataBaseTypes)} ALTER COLUMN [{prop.GetPropertyName()}] {prop.GetDbTypeByType(_repository.DataBaseTypes)} {((Nullable.GetUnderlyingType(propType) != null || propType == typeof(string)) && !prop.ContainAttribute<NotNullable>() ? " NULL" : " NOT NULL")}"); } else { if (!(prop.GetDbTypeListByType(_repository.DataBaseTypes).Any(x => x.ToLower().Contains(modify.DataType.ToLower()))) && _repository.DataBaseTypes == DataBaseTypes.PostgreSql) { codeToDataBaseMerge.Sql.Append($"\nALTER TABLE {tableName.GetName(_repository.DataBaseTypes)} ALTER COLUMN [{prop.GetPropertyName()}] TYPE {prop.GetDbTypeByType(_repository.DataBaseTypes)}, ALTER COLUMN [{prop.GetPropertyName()}] SET DEFAULT {Querys.GetValueByTypeSTRING(MethodHelper.ConvertValue(null, propType), _repository.DataBaseTypes)};"); } } } else if (!prop.IsInternalType && !prop.ContainAttribute <JsonDocument>() && !prop.ContainAttribute <XmlDocument>()) { GetDatabase_Diff(prop.PropertyType, str, createdTables); } else { codeToDataBaseMerge.Sql.Append(string.Format("\nALTER TABLE {0} ADD " + (_repository.DataBaseTypes == DataBaseTypes.PostgreSql ? "COLUMN" : "") + " [{1}] {2} {3} DEFAULT {4};", tableName.GetName(_repository.DataBaseTypes), prop.GetPropertyName(), prop.GetDbTypeByType(_repository.DataBaseTypes), (Nullable.GetUnderlyingType(propType) != null || propType == typeof(string)) && !prop.ContainAttribute <NotNullable>() ? " NULL" : " NOT NULL", Querys.GetValueByTypeSTRING(MethodHelper.ConvertValue(null, propType), _repository.DataBaseTypes))); } } } var colRemove = new CodeToDataBaseMerge() { Object_Type = tableType }; // Now lets clean the table and remove unused columns foreach (var col in table.Values.Where(x => !props.Any(a => string.Equals(x.ColumnName, a.GetPropertyName(), StringComparison.CurrentCultureIgnoreCase) && (a.GetDbTypeByType(_repository.DataBaseTypes) != null || (!a.IsInternalType || a.ContainAttribute <JsonDocument>() || a.ContainAttribute <XmlDocument>())) && !a.ContainAttribute <ExcludeFromAbstract>()))) { if (_repository.DataBaseTypes != DataBaseTypes.Sqllight) { if (_repository.DataBaseTypes == DataBaseTypes.Mssql) { var constraine = Properties.Resources.DropContraine .Replace("@tb", $"'{tableName.Name}'") .Replace("@col", $"'{col.ColumnName}'") .Replace("@schema", $"'{tableName.Schema ?? ""}'") .Replace("@TableName", "@" + counter++) .Replace("@ColumnName", "@" + counter++) .Replace("@fullName", "@" + counter++) .Replace("@DROP_COMMAND", "@" + counter++) .Replace("@FOREIGN_KEY_NAME", "@" + counter++); colRemove.Sql.Append(constraine); } colRemove.Sql.Append(string.Format("\nALTER TABLE {0} DROP COLUMN IF EXISTS [{1}];", tableName.GetName(_repository.DataBaseTypes), col.ColumnName)); } else { colRemove.Sql.Append(string.Format("DROP TABLE IF exists [{0}_temp];\nCREATE TABLE [{0}_temp] AS SELECT {1} FROM [{0}];", tableName.Name, string.Join(",", table.Values.ToList().FindAll(x => props.Any(a => string.Equals(x.ColumnName, a.GetPropertyName(), StringComparison.CurrentCultureIgnoreCase) && (a.GetDbTypeByType(_repository.DataBaseTypes) != null || !a.IsInternalType) && !a.ContainAttribute <ExcludeFromAbstract>())).Select(x => x.ColumnName)))); colRemove.Sql.Append(string.Format("DROP TABLE [{0}];\n", tableName.Name)); colRemove.Sql.Append(string.Format("ALTER TABLE [{0}_temp] RENAME TO [{0}]; ", tableName.Name)); } colRemove.DataLoss = true; } str.Add(colRemove); foreach (var prop in props.Where(x => !x.IsInternalType && !x.ContainAttribute <JsonDocument>() && !x.ContainAttribute <XmlDocument>() && !x.ContainAttribute <XmlDocument>() && !x.ContainAttribute <ExcludeFromAbstract>()).GroupBy(x => x.Name).Select(x => x.First())) { var type = prop.PropertyType.GetActualType(); if (type.GetPrimaryKey() != null) { GetDatabase_Diff(type, str, createdTables); } } str.Add(codeToDataBaseMerge); return(str); }