diff --git a/Signum.Engine/CodeGeneration/EntityCodeGenerator.cs b/Signum.Engine/CodeGeneration/EntityCodeGenerator.cs index 50a2e2244a..3301a163aa 100644 --- a/Signum.Engine/CodeGeneration/EntityCodeGenerator.cs +++ b/Signum.Engine/CodeGeneration/EntityCodeGenerator.cs @@ -8,961 +8,968 @@ namespace Signum.Engine.CodeGeneration; -public class EntityCodeGenerator -{ - public string SolutionName = null!; - public string SolutionFolder = null!; + public class EntityCodeGenerator + { + public string SolutionName = null!; + public string SolutionFolder = null!; - public Dictionary Tables = null!; - public DirectedGraph InverseGraph = null!; + public Dictionary Tables = null!; + public DirectedGraph InverseGraph = null!; - public Schema CurrentSchema = null!; + public Schema CurrentSchema = null!; - public virtual void GenerateEntitiesFromDatabaseTables() - { - CurrentSchema = Schema.Current; + public virtual void GenerateEntitiesFromDatabaseTables() + { + CurrentSchema = Schema.Current; - var tables = GetTables(); + var tables = GetTables(); - this.Tables = tables.ToDictionary(a=>a.Name); + this.Tables = tables.ToDictionary(a=>a.Name); - InverseGraph = DirectedGraph.Generate(tables, t => - t.Columns.Values.Select(a => a.ForeignKey).NotNull().Select(a => a.TargetTable).Distinct().Select(on => this.Tables.GetOrThrow(on))).Inverse(); + InverseGraph = DirectedGraph.Generate(tables, t => + t.Columns.Values.Select(a => a.ForeignKey).NotNull().Select(a => a.TargetTable).Distinct().Select(on => this.Tables.GetOrThrow(on))).Inverse(); - GetSolutionInfo(out SolutionFolder, out SolutionName); + GetSolutionInfo(out SolutionFolder, out SolutionName); - string projectFolder = GetProjectFolder(); + string projectFolder = GetProjectFolder(); - if (!Directory.Exists(projectFolder)) - throw new InvalidOperationException("{0} not found. Override GetProjectFolder".FormatWith(projectFolder)); + if (!Directory.Exists(projectFolder)) + throw new InvalidOperationException("{0} not found. Override GetProjectFolder".FormatWith(projectFolder)); - bool? overwriteFiles = null; + bool? overwriteFiles = null; - foreach (var gr in tables.GroupBy(t => GetFileName(t))) - { - string? str = WriteFile(gr.Key, gr); - if (str != null) + foreach (var gr in tables.GroupBy(t => GetFileName(t))) { - string fileName = Path.Combine(projectFolder, gr.Key); + string? str = WriteFile(gr.Key, gr); + if (str != null) + { + string fileName = Path.Combine(projectFolder, gr.Key); - FileTools.CreateParentDirectory(fileName); + FileTools.CreateParentDirectory(fileName); - if (!File.Exists(fileName) || SafeConsole.Ask(ref overwriteFiles, "Overwrite {0}?".FormatWith(fileName))) - { - File.WriteAllText(fileName, str); + if (!File.Exists(fileName) || SafeConsole.Ask(ref overwriteFiles, "Overwrite {0}?".FormatWith(fileName))) + { + File.WriteAllText(fileName, str); + } } } } - } - protected virtual string GetProjectFolder() - { - return Path.Combine(SolutionFolder, SolutionName + ".Entities"); - } + protected virtual string GetProjectFolder() + { + return Path.Combine(SolutionFolder, SolutionName + ".Entities"); + } - protected virtual List GetTables() - { - return Schema.Current.Settings.IsPostgres ? - PostgresCatalogSchema.GetDatabaseDescription(Schema.Current.DatabaseNames()).Values.ToList() : - SysTablesSchema.GetDatabaseDescription(Schema.Current.DatabaseNames()).Values.ToList(); - } + protected virtual List GetTables() + { + return Schema.Current.Settings.IsPostgres ? + PostgresCatalogSchema.GetDatabaseDescription(Schema.Current.DatabaseNames()).Values.ToList() : + SysTablesSchema.GetDatabaseDescription(Schema.Current.DatabaseNames()).Values.ToList(); + } - protected virtual void GetSolutionInfo(out string solutionFolder, out string solutionName) - { - CodeGenerator.GetSolutionInfo(out solutionFolder, out solutionName); - } + protected virtual void GetSolutionInfo(out string solutionFolder, out string solutionName) + { + CodeGenerator.GetSolutionInfo(out solutionFolder, out solutionName); + } - protected virtual string GetFileName(DiffTable t) - { - var mli = this.GetMListInfo(t); - if (mli != null && !mli.IsVirtual) - return this.GetFileName(this.Tables.GetOrThrow(mli.BackReferenceColumn.ForeignKey!.TargetTable)); + protected virtual string GetFileName(DiffTable t) + { + var mli = this.GetMListInfo(t); + if (mli != null && !mli.IsVirtual) + return this.GetFileName(this.Tables.GetOrThrow(mli.BackReferenceColumn.ForeignKey!.TargetTable)); - string name = t.Name.Schema.IsDefault() ? t.Name.Name : t.Name.ToString().Replace('.', '\\'); + string name = t.Name.Schema.IsDefault() ? t.Name.Name : t.Name.ToString().Replace('.', '\\'); - name = Regex.Replace(name, "[" + Regex.Escape(new string(Path.GetInvalidPathChars())) + "]", ""); + name = Regex.Replace(name, "[" + Regex.Escape(new string(Path.GetInvalidPathChars())) + "]", ""); - return Singularize(name) + ".cs"; - } + return Singularize(name) + ".cs"; + } - protected virtual string? WriteFile(string fileName, IEnumerable tables) - { - StringBuilder sb = new StringBuilder(); - foreach (var item in GetUsingNamespaces(fileName, tables)) - sb.AppendLine("using {0};".FormatWith(item)); + protected virtual string? WriteFile(string fileName, IEnumerable tables) + { + StringBuilder sb = new StringBuilder(); + foreach (var item in GetUsingNamespaces(fileName, tables)) + sb.AppendLine("using {0};".FormatWith(item)); - sb.AppendLine(); + sb.AppendLine(); sb.AppendLine("namespace " + GetNamespace(fileName) + ";"); sb.AppendLine(); - int length = sb.Length; - foreach (var t in tables.OrderByDescending(a => a.Columns.Count).Iterate()) - { - var entity = WriteTableEntity(fileName, t.Value); - if (entity != null) + int length = sb.Length; + foreach (var t in tables.OrderByDescending(a => a.Columns.Count).Iterate()) { - sb.Append(entity); - if (!t.IsLast) + var entity = WriteTableEntity(fileName, t.Value); + if (entity != null) { - sb.AppendLine(); - sb.AppendLine(); + sb.Append(entity); + if (!t.IsLast) + { + sb.AppendLine(); + sb.AppendLine(); + } } } - } - if (sb.Length == length) - return null; + if (sb.Length == length) + return null; - return sb.ToString(); - } + return sb.ToString(); + } - protected virtual List GetUsingNamespaces(string fileName, IEnumerable tables) - { - var result = new List + protected virtual List GetUsingNamespaces(string fileName, IEnumerable tables) { - }; + var result = new List + { + }; - var currentNamespace = GetNamespace(fileName); + var currentNamespace = GetNamespace(fileName); - var fkNamespaces = - (from t in tables - from c in t.Columns.Values - where c.ForeignKey != null - let targetTable = Tables.GetOrThrow(c.ForeignKey!.TargetTable) - select GetNamespace(GetFileName(targetTable))); + var fkNamespaces = + (from t in tables + from c in t.Columns.Values + where c.ForeignKey != null + let targetTable = Tables.GetOrThrow(c.ForeignKey!.TargetTable) + select GetNamespace(GetFileName(targetTable))); - var mListNamespaces = - (from t in tables - from kvp in GetMListFields(t) - let tec = kvp.Value.TrivialElementColumn - let targetTable = tec != null && tec.ForeignKey != null ? Tables.GetOrThrow(tec.ForeignKey.TargetTable) : kvp.Key - select GetNamespace(GetFileName(targetTable))); + var mListNamespaces = + (from t in tables + from kvp in GetMListFields(t) + let tec = kvp.Value.TrivialElementColumn + let targetTable = tec != null && tec.ForeignKey != null ? Tables.GetOrThrow(tec.ForeignKey.TargetTable) : kvp.Key + select GetNamespace(GetFileName(targetTable))); - result.AddRange(fkNamespaces.Concat(mListNamespaces).Where(ns => ns != currentNamespace).Distinct()); + result.AddRange(fkNamespaces.Concat(mListNamespaces).Where(ns => ns != currentNamespace).Distinct()); - return result; - } + return result; + } - protected virtual string GetNamespace(string fileName) - { - var result = SolutionName + ".Entities"; + protected virtual string GetNamespace(string fileName) + { + var result = SolutionName + ".Entities"; - string? folder = fileName.TryBeforeLast('\\'); + string? folder = fileName.TryBeforeLast('\\'); - if (folder != null) - result += "." + folder.Replace('\\', '.'); + if (folder != null) + result += "." + folder.Replace('\\', '.'); - return result; - } + return result; + } - protected virtual void WriteAttributeTag(StringBuilder sb, IEnumerable attributes) - { - foreach (var gr in attributes.Chunk(a => a.Length, 100)) + protected virtual void WriteAttributeTag(StringBuilder sb, IEnumerable attributes) { - sb.AppendLine("[" + gr.ToString(", ") + "]"); + foreach (var gr in attributes.Chunk(a => a.Length, 100)) + { + sb.AppendLine("[" + gr.ToString(", ") + "]"); + } } - } - - protected virtual string? WriteTableEntity(string fileName, DiffTable table) - { - var mListInfo = GetMListInfo(table); - if (mListInfo != null) + protected virtual string? WriteTableEntity(string fileName, DiffTable table) { - if (mListInfo.TrivialElementColumn != null) - return null; - - if (mListInfo.IsVirtual) - return WriteEntity(fileName, table); + var mListInfo = GetMListInfo(table); - var primaryKey = GetPrimaryKeyColumn(table); - - var cols = table.Columns.Values.Where(col => col != primaryKey && col != mListInfo.BackReferenceColumn).ToList(); + if (mListInfo != null) + { + if (mListInfo.TrivialElementColumn != null) + return null; - return WriteEmbeddedEntity(fileName, table, GetEntityName(table), cols); - } + if (mListInfo.IsVirtual) + return WriteEntity(fileName, table); - if (IsEnum(table)) - return WriteEnum(table); + var primaryKey = GetPrimaryKeyColumn(table); - return WriteEntity(fileName, table); - } + var cols = table.Columns.Values.Where(col => col != primaryKey && col != mListInfo.BackReferenceColumn).ToList(); - protected virtual string WriteEntity(string fileName, DiffTable table) - { - var name = GetEntityName(table); + return WriteEmbeddedEntity(fileName, table, GetEntityName(table), cols); + } - StringBuilder sb = new StringBuilder(); - WriteAttributeTag(sb, GetEntityAttributes(table)); - sb.AppendLine("public class {0} : {1}".FormatWith(name, GetEntityBaseClass(table))); - sb.AppendLine("{"); + if (IsEnum(table)) + return WriteEnum(table); - string? multiColumnIndexComment = WriteMultiColumnIndexComment(table, name); - if (multiColumnIndexComment != null) - { - sb.Append(multiColumnIndexComment.Indent(4)); - sb.AppendLine(); + return WriteEntity(fileName, table); } - var primaryKey = GetPrimaryKeyColumn(table); - - var columnGroups = (from col in table.Columns.Values - where col != primaryKey - group col by GetEmbeddedField(table, col) into g - select g).ToList(); - - foreach (var col in columnGroups.SingleOrDefaultEx(g => g.Key == null).EmptyIfNull()) + protected virtual string WriteEntity(string fileName, DiffTable table) { - string field = WriteField(fileName, table, col); + var name = GetEntityName(table); + + StringBuilder sb = new StringBuilder(); + WriteAttributeTag(sb, GetEntityAttributes(table)); + sb.AppendLine("public class {0} : {1}".FormatWith(name, GetEntityBaseClass(table))); + sb.AppendLine("{"); - if (field != null) + string? multiColumnIndexComment = WriteMultiColumnIndexComment(table, name); + if (multiColumnIndexComment != null) { - sb.Append(field.Indent(4)); + sb.Append(multiColumnIndexComment.Indent(4)); sb.AppendLine(); } - } - foreach (var gr in columnGroups.Where(g => g.Key != null)) - { - string embeddedField = WriteEmbeddedField(table, gr.Key); + var primaryKey = GetPrimaryKeyColumn(table); - if (embeddedField != null) + var columnGroups = (from col in table.Columns.Values + where col != primaryKey + group col by GetEmbeddedField(table, col) into g + select g).ToList(); + + foreach (var col in columnGroups.SingleOrDefaultEx(g => g.Key == null).EmptyIfNull()) { - sb.AppendLine(embeddedField.Indent(4)); - sb.AppendLine(); - } - } + string field = WriteField(fileName, table, col); - foreach (KeyValuePair kvp in GetMListFields(table)) - { - string field = WriteFieldMList(fileName, table, kvp.Value, kvp.Key); + if (field != null) + { + sb.Append(field.Indent(4)); + sb.AppendLine(); + } + } - if (field != null) + foreach (var gr in columnGroups.Where(g => g.Key != null)) { - sb.AppendLine(field.Indent(4)); - sb.AppendLine(); + string embeddedField = WriteEmbeddedField(table, gr.Key); + + if (embeddedField != null) + { + sb.AppendLine(embeddedField.Indent(4)); + sb.AppendLine(); + } } - } - string? toString = WriteToString(table); - if (toString != null) - { - sb.Append(toString.Indent(4)); - sb.AppendLine(); - } + foreach (KeyValuePair kvp in GetMListFields(table)) + { + string field = WriteFieldMList(fileName, table, kvp.Value, kvp.Key); - sb.AppendLine("}"); - sb.AppendLine(); + if (field != null) + { + sb.AppendLine(field.Indent(4)); + sb.AppendLine(); + } + } - foreach (var gr in columnGroups.Where(g => g.Key != null)) - { - string embeddedEntity = WriteEmbeddedEntity(fileName, table, GetEmbeddedTypeName(gr.Key), gr.ToList()); - if (embeddedEntity != null) + string? toString = WriteToString(table); + if (toString != null) { - sb.AppendLine(embeddedEntity); + sb.Append(toString.Indent(4)); sb.AppendLine(); } - } - string? operations = WriteOperations(table); - if (operations != null) - { - sb.Append(operations); - } + sb.AppendLine("}"); + sb.AppendLine(); - return sb.ToString(); - } + foreach (var gr in columnGroups.Where(g => g.Key != null)) + { + string embeddedEntity = WriteEmbeddedEntity(fileName, table, GetEmbeddedTypeName(gr.Key), gr.ToList()); + if (embeddedEntity != null) + { + sb.AppendLine(embeddedEntity); + sb.AppendLine(); + } + } - protected virtual string? GetEmbeddedField(DiffTable table, DiffColumn col) - { - return null; - } + string? operations = WriteOperations(table); + if (operations != null) + { + sb.Append(operations); + } - protected virtual string WriteEmbeddedEntity(string fileName, DiffTable table, string name, List columns) - { - StringBuilder sb = new StringBuilder(); - sb.AppendLine("public class {0} : {1}".FormatWith(name, typeof(EmbeddedEntity).Name)); - sb.AppendLine("{"); + return sb.ToString(); + } - string? multiColumnIndexComment = WriteMultiColumnIndexComment(table, name); - if (multiColumnIndexComment != null) + protected virtual string? GetEmbeddedField(DiffTable table, DiffColumn col) { - sb.Append(multiColumnIndexComment.Indent(4)); - sb.AppendLine(); + return null; } - foreach (var col in columns) + protected virtual string WriteEmbeddedEntity(string fileName, DiffTable table, string name, List columns) { - string field = WriteField(fileName, table, col); + StringBuilder sb = new StringBuilder(); + sb.AppendLine("public class {0} : {1}".FormatWith(name, typeof(EmbeddedEntity).Name)); + sb.AppendLine("{"); - if (field != null) + string? multiColumnIndexComment = WriteMultiColumnIndexComment(table, name); + if (multiColumnIndexComment != null) { - sb.Append(field.Indent(4)); + sb.Append(multiColumnIndexComment.Indent(4)); sb.AppendLine(); } - } - - sb.AppendLine("}"); - - return sb.ToString(); - } - protected virtual IEnumerable> GetMListFields(DiffTable table) - { - return from relatedTable in InverseGraph.RelatedTo(table) - let mListInfo2 = GetMListInfo(relatedTable) - where mListInfo2 != null && mListInfo2.BackReferenceColumn.ForeignKey!.TargetTable.Equals(table.Name) - select KeyValuePair.Create(relatedTable, mListInfo2); - } + foreach (var col in columns) + { + string field = WriteField(fileName, table, col); - protected virtual string WriteEnum(DiffTable table) - { - StringBuilder sb = new StringBuilder(); + if (field != null) + { + sb.Append(field.Indent(4)); + sb.AppendLine(); + } + } - WriteAttributeTag(sb, GetEnumAttributes(table)); - sb.AppendLine("public enum {0}".FormatWith(GetEntityName(table))); - sb.AppendLine("{"); + sb.AppendLine("}"); - var dataTable = Executor.ExecuteDataTable("select * from " + table.Name); + return sb.ToString(); + } - var rowsById = dataTable.Rows.Cast().ToDictionary(row=>GetEnumId(table, row)); + protected virtual IEnumerable> GetMListFields(DiffTable table) + { + return from relatedTable in InverseGraph.RelatedTo(table) + let mListInfo2 = GetMListInfo(relatedTable) + where mListInfo2 != null && mListInfo2.BackReferenceColumn.ForeignKey!.TargetTable.Equals(table.Name) + select KeyValuePair.Create(relatedTable, mListInfo2); + } - int lastId = -1; - foreach (var kvp in rowsById.OrderBy(a => a.Key)) + protected virtual string WriteEnum(DiffTable table) { - string description = GetEnumDescription(table, kvp.Value); + StringBuilder sb = new StringBuilder(); - string value = GetEnumValue(table, kvp.Value); + WriteAttributeTag(sb, GetEnumAttributes(table)); + sb.AppendLine("public enum {0}".FormatWith(GetEntityName(table))); + sb.AppendLine("{"); - string explicitId = kvp.Key == lastId + 1 ? "" : " = " + kvp.Key; + var dataTable = Executor.ExecuteDataTable("select * from " + table.Name); - sb.AppendLine(" " + (description != null ? @"[Description(""" + description + @""")]" : null) + value + explicitId + ","); + var rowsById = dataTable.Rows.Cast().ToDictionary(row=>GetEnumId(table, row)); - lastId = kvp.Key; - } + int lastId = -1; + foreach (var kvp in rowsById.OrderBy(a => a.Key)) + { + string description = GetEnumDescription(table, kvp.Value); - sb.AppendLine("}"); + string value = GetEnumValue(table, kvp.Value); - return sb.ToString(); - } + string explicitId = kvp.Key == lastId + 1 ? "" : " = " + kvp.Key; + sb.AppendLine(" " + (description != null ? @"[Description(""" + description + @""")]" : null) + value + explicitId + ","); + lastId = kvp.Key; + } - protected virtual List GetEnumAttributes(DiffTable table) - { - List atts = new List(); + sb.AppendLine("}"); - string? tableNameAttribute = GetTableNameAttribute(table.Name, null); - if (tableNameAttribute != null) - atts.Add(tableNameAttribute); + return sb.ToString(); + } - string? primaryKeyAttribute = GetPrimaryKeyAttribute(table); - if (primaryKeyAttribute != null) - atts.Add(primaryKeyAttribute); - return atts; - } - protected virtual int GetEnumId(DiffTable table, DataRow row) - { - throw new NotImplementedException("Override GetEnumId"); - } + protected virtual List GetEnumAttributes(DiffTable table) + { + List atts = new List(); - protected virtual string GetEnumValue(DiffTable table, DataRow item) - { - throw new NotImplementedException("Override GetEnumValue"); - } + string? tableNameAttribute = GetTableNameAttribute(table.Name, null); + if (tableNameAttribute != null) + atts.Add(tableNameAttribute); - protected virtual string GetEnumDescription(DiffTable table, DataRow item) - { - throw new NotImplementedException("Override GetEnumDescription"); - } + string? primaryKeyAttribute = GetPrimaryKeyAttribute(table); + if (primaryKeyAttribute != null) + atts.Add(primaryKeyAttribute); - protected virtual bool IsEnum(DiffTable objectName) - { - return false; - } + return atts; + } - protected virtual string? WriteMultiColumnIndexComment(DiffTable table, string name) - { - StringBuilder sb = new StringBuilder(); - foreach (var ix in table.Indices.Values.Where(ix => ix.Columns.Count > 1 || ix.FilterDefinition.HasText() || ix.Columns.Any(ic => ic.IsIncluded))) + protected virtual int GetEnumId(DiffTable table, DataRow row) { - var columns = - $"e => new {{ {ix.Columns.Where(a => !a.IsIncluded).ToString(c => "e." + GetFieldName(table, table.Columns.GetOrThrow(c.ColumnName)).FirstUpper(), ", ")} }}"; - - var incColumns = ix.Columns.Any(a => a.IsIncluded) ? null : - $"e => new {{ {ix.Columns.Where(a => a.IsIncluded).ToString(c => "e." + GetFieldName(table, table.Columns.GetOrThrow(c.ColumnName)).FirstUpper(), ", ")} }}"; - - sb.AppendLine("//Add to Logic class"); - sb.AppendLine("//sb.AddUniqueIndex<{0}>({1});".FormatWith(name, - new object?[] { columns, ix.FilterDefinition, incColumns }.NotNull().ToString(", "))); + throw new NotImplementedException("Override GetEnumId"); } - return sb.ToString().DefaultText(null!); - } - - protected virtual string? WriteOperations(DiffTable table) - { - var kind = GetEntityKind(table); - if (!(kind == EntityKind.Main || kind == EntityKind.Shared || kind == EntityKind.String)) - return null; + protected virtual string GetEnumValue(DiffTable table, DataRow item) + { + throw new NotImplementedException("Override GetEnumValue"); + } - StringBuilder sb = new StringBuilder(); - sb.AppendLine("[AutoInit]"); - sb.AppendLine("public static class {0}".FormatWith(GetOperationName(table))); - sb.AppendLine("{"); - sb.AppendLine(" public static readonly ExecuteSymbol<{0}> Save;".FormatWith(GetEntityName(table))); - sb.AppendLine("}"); - return sb.ToString(); - } + protected virtual string GetEnumDescription(DiffTable table, DataRow item) + { + throw new NotImplementedException("Override GetEnumDescription"); + } - protected virtual string GetOperationName(DiffTable objectName) - { - return GetEntityName(objectName).RemoveSuffix("Entity") + "Operation"; - } + protected virtual bool IsEnum(DiffTable objectName) + { + return false; + } - protected virtual MListInfo? GetMListInfo(DiffTable table) - { - var isVirtualMList = IsVirtualMList(table); + protected virtual string? WriteMultiColumnIndexComment(DiffTable table, string name) + { + StringBuilder sb = new StringBuilder(); + foreach (var ix in table.Indices.Values.Where(ix => ix.Columns.Count > 1 || ix.FilterDefinition.HasText() || ix.Columns.Any(ic => ic.IsIncluded))) + { + var columns = + $"e => new {{ {ix.Columns.Where(a => !a.IsIncluded).ToString(c => "e." + GetFieldName(table, table.Columns.GetOrThrow(c.ColumnName)).FirstUpper(), ", ")} }}"; - if (!isVirtualMList && this.InverseGraph.RelatedTo(table).Any()) - return null; + var incColumns = ix.Columns.Any(a => a.IsIncluded) ? null : + $"e => new {{ {ix.Columns.Where(a => a.IsIncluded).ToString(c => "e." + GetFieldName(table, table.Columns.GetOrThrow(c.ColumnName)).FirstUpper(), ", ")} }}"; - var parentColumn = GetMListParentColumn(table); - if (parentColumn == null) - return null; + sb.AppendLine("//Add to Logic class"); + sb.AppendLine("//sb.AddUniqueIndex<{0}>({1});".FormatWith(name, + new object?[] { columns, ix.FilterDefinition, incColumns }.NotNull().ToString(", "))); + } - var orderColumn = GetMListOrderColumn(table); - var trivialColumn = isVirtualMList ? null : GetMListTrivialElementColumn(table, parentColumn, orderColumn); + return sb.ToString().DefaultText(null!); + } - return new MListInfo(parentColumn) + protected virtual string? WriteOperations(DiffTable table) { - TrivialElementColumn = trivialColumn, - PreserveOrderColumn = orderColumn, - IsVirtual = isVirtualMList, - }; - } - - public virtual bool IsVirtualMList(DiffTable table) - { - return false; - } + var kind = GetEntityKind(table); + if (!(kind == EntityKind.Main || kind == EntityKind.Shared || kind == EntityKind.String)) + return null; - protected virtual DiffColumn? GetMListTrivialElementColumn(DiffTable table, DiffColumn parentColumn, DiffColumn? orderColumn) - { - return table.Columns.Values.Where(c => c != parentColumn && c != orderColumn && !c.PrimaryKey).Only(); - } + StringBuilder sb = new StringBuilder(); + sb.AppendLine("[AutoInit]"); + sb.AppendLine("public static class {0}".FormatWith(GetOperationName(table))); + sb.AppendLine("{"); + sb.AppendLine(" public static readonly ExecuteSymbol<{0}> Save;".FormatWith(GetEntityName(table))); + sb.AppendLine("}"); + return sb.ToString(); + } - protected virtual DiffColumn? GetMListOrderColumn(DiffTable table) - { - return table.Columns.TryGetC("Order") ?? table.Columns.TryGetC("Row") ?? table.Columns.TryGetC("Index"); - } + protected virtual string GetOperationName(DiffTable objectName) + { + return GetEntityName(objectName).RemoveSuffix("Entity") + "Operation"; + } - protected virtual DiffColumn? GetMListParentColumn(DiffTable table) - { - return table.Columns.Values.Where(c => c.ForeignKey != null && c.Nullable == false && table.Name.Name.StartsWith(c.ForeignKey.TargetTable.Name)).OrderByDescending(a => a.ForeignKey!.TargetTable.Name.Length).FirstOrDefault(); - } - protected virtual IEnumerable GetEntityAttributes(DiffTable table) - { - List atts = new List { }; + protected virtual MListInfo? GetMListInfo(DiffTable table) + { + var isVirtualMList = IsVirtualMList(table); - atts.Add("EntityKind(EntityKind." + GetEntityKind(table) + ", EntityData." + GetEntityData(table) + ")"); + if (!isVirtualMList && this.InverseGraph.RelatedTo(table).Any()) + return null; - string? tableNameAttribute = GetTableNameAttribute(table.Name, null); - if (tableNameAttribute != null) - atts.Add(tableNameAttribute); + var parentColumn = GetMListParentColumn(table); + if (parentColumn == null) + return null; - string? primaryKeyAttribute = GetPrimaryKeyAttribute(table); - if (primaryKeyAttribute != null) - atts.Add(primaryKeyAttribute); + var orderColumn = GetMListOrderColumn(table); + var trivialColumn = isVirtualMList ? null : GetMListTrivialElementColumn(table, parentColumn, orderColumn); - string? ticksColumnAttribute = GetTicksColumnAttribute(table); - if (ticksColumnAttribute != null) - atts.Add(ticksColumnAttribute); + return new MListInfo(parentColumn) + { + TrivialElementColumn = trivialColumn, + PreserveOrderColumn = orderColumn, + IsVirtual = isVirtualMList, + }; + } - return atts; - } + public virtual bool IsVirtualMList(DiffTable table) + { + return false; + } - protected virtual string GetTicksColumnAttribute(DiffTable table) - { - return "TicksColumn(Default = \"0\")"; - } + protected virtual DiffColumn? GetMListTrivialElementColumn(DiffTable table, DiffColumn parentColumn, DiffColumn? orderColumn) + { + return table.Columns.Values.Where(c => c != parentColumn && c != orderColumn && !c.PrimaryKey).Only(); + } + protected virtual DiffColumn? GetMListOrderColumn(DiffTable table) + { + return table.Columns.TryGetC("Order") ?? table.Columns.TryGetC("Row") ?? table.Columns.TryGetC("Index"); + } - protected virtual string? GetPrimaryKeyAttribute(DiffTable table) - { - DiffColumn? primaryKey = GetPrimaryKeyColumn(table); + protected virtual DiffColumn? GetMListParentColumn(DiffTable table) + { + return table.Columns.Values.Where(c => c.ForeignKey != null && c.Nullable == false && table.Name.Name.StartsWith(c.ForeignKey.TargetTable.Name)).OrderByDescending(a => a.ForeignKey!.TargetTable.Name.Length).FirstOrDefault(); + } + protected virtual IEnumerable GetEntityAttributes(DiffTable table) + { + List atts = new List { }; - if (primaryKey == null) - return null; + atts.Add("EntityKind(EntityKind." + GetEntityKind(table) + ", EntityData." + GetEntityData(table) + ")"); - var def = CurrentSchema.Settings.DefaultPrimaryKeyAttribute; + string? tableNameAttribute = GetTableNameAttribute(table.Name, null); + if (tableNameAttribute != null) + atts.Add(tableNameAttribute); - Type type = GetValueType(primaryKey); + string? primaryKeyAttribute = GetPrimaryKeyAttribute(table); + if (primaryKeyAttribute != null) + atts.Add(primaryKeyAttribute); - List parts = new List(); + string? ticksColumnAttribute = GetTicksColumnAttribute(table); + if (ticksColumnAttribute != null) + atts.Add(ticksColumnAttribute); - if (primaryKey.Name != def.Name) - parts.Add("Name = \"" + primaryKey.Name + "\""); + return atts; + } - if (primaryKey.Identity != def.Identity) + protected virtual string GetTicksColumnAttribute(DiffTable table) { - parts.Add("Identity = " + primaryKey.Identity.ToString().ToLower()); - parts.Add("IdentityBehaviour = " + primaryKey.Identity.ToString().ToLower()); + return "TicksColumn(Default = \"0\")"; } - parts.AddRange(GetSqlDbTypeParts(primaryKey, type)); - if (type != def.Type || parts.Any()) - parts.Insert(0, "typeof(" + type.TypeName() + ")"); + protected virtual string? GetPrimaryKeyAttribute(DiffTable table) + { + DiffColumn? primaryKey = GetPrimaryKeyColumn(table); - if(parts.Any()) - return "PrimaryKey(" + parts.ToString(", ") + ")"; + if (primaryKey == null) + return null; - return null; - } + var def = CurrentSchema.Settings.DefaultPrimaryKeyAttribute; - protected virtual DiffColumn? GetPrimaryKeyColumn(DiffTable table) - { - return table.Columns.Values.SingleOrDefaultEx(a => a.PrimaryKey); - } + Type type = GetValueType(primaryKey); - protected virtual string? GetTableNameAttribute(ObjectName objectName, MListInfo? mListInfo) - { - StringBuilder sb = new StringBuilder(); - sb.Append("TableName(\"" + objectName.Name + "\""); - if (objectName.Schema != SchemaName.Default(CurrentSchema.Settings.IsPostgres)) - sb.Append(", SchemaName = \"" + objectName.Schema.Name + "\""); + List parts = new List(); - if (objectName.Schema.Database != null) - { - sb.Append(", DatabaseName = \"" + objectName.Schema.Database.Name + "\""); + if (primaryKey.Name != def.Name) + parts.Add("Name = \"" + primaryKey.Name + "\""); - if (objectName.Schema.Database.Server != null) + if (primaryKey.Identity != def.Identity) { - sb.Append(", ServerName = \"" + objectName.Schema.Database.Server.Name + "\""); + parts.Add("Identity = " + primaryKey.Identity.ToString().ToLower()); + parts.Add("IdentityBehaviour = " + primaryKey.Identity.ToString().ToLower()); } - } - sb.Append(')'); - return sb.ToString(); - } + parts.AddRange(GetSqlDbTypeParts(primaryKey, type)); - protected virtual EntityData GetEntityData(DiffTable table) - { - return EntityData.Transactional; - } + if (type != def.Type || parts.Any()) + parts.Insert(0, "typeof(" + type.TypeName() + ")"); - protected virtual EntityKind GetEntityKind(DiffTable table) - { - var mListInfo = GetMListInfo(table); + if(parts.Any()) + return "PrimaryKey(" + parts.ToString(", ") + ")"; - if (mListInfo != null && mListInfo.IsVirtual) - return EntityKind.Part; + return null; + } - return EntityKind.Main; - } + protected virtual DiffColumn? GetPrimaryKeyColumn(DiffTable table) + { + return table.Columns.Values.SingleOrDefaultEx(a => a.PrimaryKey); + } - protected virtual string GetEntityName(DiffTable table) - { - var mListInfo = GetMListInfo(table); + protected virtual string? GetTableNameAttribute(ObjectName objectName, MListInfo? mListInfo) + { + StringBuilder sb = new StringBuilder(); + sb.Append("TableName(\"" + objectName.Name + "\""); + if (objectName.Schema != SchemaName.Default(CurrentSchema.Settings.IsPostgres)) + sb.Append(", SchemaName = \"" + objectName.Schema.Name + "\""); - return Singularize(table.Name.Name) + - (IsEnum(table) ? "" : - mListInfo != null && !mListInfo.IsVirtual ? "Embedded" : - "Entity"); - } + if (objectName.Schema.Database != null) + { + sb.Append(", DatabaseName = \"" + objectName.Schema.Database.Name + "\""); - protected virtual string Singularize(string name) - { - return ((EnglishPluralizer)NaturalLanguageTools.Pluralizers["en"]).MakeSingular(name); - } + if (objectName.Schema.Database.Server != null) + { + sb.Append(", ServerName = \"" + objectName.Schema.Database.Server.Name + "\""); + } + } - protected virtual string GetEntityBaseClass(DiffTable table) - { - var mli = GetMListInfo(table); + sb.Append(')'); + return sb.ToString(); + } - if (mli != null && mli.PreserveOrderColumn != null && mli.IsVirtual) - return typeof(Entity).Name + ", " + typeof(ICanBeOrdered).Name; + protected virtual EntityData GetEntityData(DiffTable table) + { + return EntityData.Transactional; + } - return typeof(Entity).Name; - } + protected virtual EntityKind GetEntityKind(DiffTable table) + { + var mListInfo = GetMListInfo(table); - protected virtual string WriteField(string fileName, DiffTable table, DiffColumn col) - { - string? relatedEntity = GetRelatedEntity(table, col); + if (mListInfo != null && mListInfo.IsVirtual) + return EntityKind.Part; + + return EntityKind.Main; + } - string type = GetFieldType(table, col, relatedEntity); + protected virtual string GetEntityName(DiffTable table) + { + var mListInfo = GetMListInfo(table); - string fieldName = GetFieldName(table, col); + return Singularize(table.Name.Name) + + (IsEnum(table) ? "" : + mListInfo != null && !mListInfo.IsVirtual ? "Embedded" : + "Entity"); + } - StringBuilder sb = new StringBuilder(); + protected virtual string Singularize(string name) + { + return ((EnglishPluralizer)NaturalLanguageTools.Pluralizers["en"]).MakeSingular(name); + } - WriteAttributeTag(sb, GetFieldAttributes(table, col, relatedEntity, false)); - WriteAttributeTag(sb, GetPropertyAttributes(table, col, relatedEntity)); - sb.AppendLine("public {0} {1} {{ get; {2}set; }}".FormatWith(type, fieldName.FirstUpper(), IsReadonly(table, col) ? "private" : null)); + protected virtual string GetEntityBaseClass(DiffTable table) + { + var mli = GetMListInfo(table); - return sb.ToString(); - } + if (mli != null && mli.PreserveOrderColumn != null && mli.IsVirtual) + return typeof(Entity).Name + ", " + typeof(ICanBeOrdered).Name; - protected virtual string? GetRelatedEntity(DiffTable table, DiffColumn col) - { - if (col.ForeignKey == null) - return null; + return typeof(Entity).Name; + } - return GetEntityName(Tables.GetOrThrow(col.ForeignKey.TargetTable)); - } + protected virtual string WriteField(string fileName, DiffTable table, DiffColumn col) + { + string? relatedEntity = GetRelatedEntity(table, col); - protected virtual bool IsReadonly(DiffTable table, DiffColumn col) - { - return false; - } + string type = GetFieldType(table, col, relatedEntity); - protected virtual IEnumerable GetPropertyAttributes(DiffTable table, DiffColumn col, string? relatedEntity) - { - List attributes = new List(); + string fieldName = GetFieldName(table, col); - string? stringLengthValidator = GetStringLengthValidator(table, col, relatedEntity); - if(stringLengthValidator != null) - attributes.Add(stringLengthValidator); + StringBuilder sb = new StringBuilder(); - return attributes; - } + WriteAttributeTag(sb, GetFieldAttributes(table, col, relatedEntity, false)); + WriteAttributeTag(sb, GetPropertyAttributes(table, col, relatedEntity)); + sb.AppendLine("public {0} {1} {{ get; {2}set; }}".FormatWith(type, fieldName.FirstUpper(), IsReadonly(table, col) ? "private" : null)); - protected virtual string? GetStringLengthValidator(DiffTable table, DiffColumn col, string? relatedEntity) - { - if (GetValueType(col) != typeof(string)) - return null; + return sb.ToString(); + } - var parts = new List(); + protected virtual string? GetRelatedEntity(DiffTable table, DiffColumn col) + { + if (col.ForeignKey == null) + return null; - var min = GetMinStringLength(col); - if (min != null) - parts.Add("Min = " + min); + return GetEntityName(Tables.GetOrThrow(col.ForeignKey.TargetTable)); + } - if (col.Length != -1) - parts.Add("Max = " + col.Length); + protected virtual bool IsReadonly(DiffTable table, DiffColumn col) + { + return false; + } - return "StringLengthValidator(" + parts.ToString(", ") + ")"; - } + protected virtual IEnumerable GetPropertyAttributes(DiffTable table, DiffColumn col, string? relatedEntity) + { + List attributes = new List(); - protected virtual int? GetMinStringLength(DiffColumn col) - { - return 1; - } + string? stringLengthValidator = GetStringLengthValidator(table, col, relatedEntity); + if(stringLengthValidator != null) + attributes.Add(stringLengthValidator); - protected virtual string GetFieldName(DiffTable table, DiffColumn col) - { - string name = !IdentifierValidatorAttribute.PascalAscii.IsMatch(col.Name) || col.Name.Contains("_") ? col.Name.ToPascal(false, false) : col.Name; + return attributes; + } - if (this.GetRelatedEntity(table, col) != null) + protected virtual string? GetStringLengthValidator(DiffTable table, DiffColumn col, string? relatedEntity) { - if (name.Length > 2 && name.EndsWith("Id", StringComparison.InvariantCultureIgnoreCase)) - name = name.RemoveEnd("Id".Length); + if (GetValueType(col) != typeof(string)) + return null; - if (name.Length > 2 && name.StartsWith("Id", StringComparison.InvariantCultureIgnoreCase)) - name = name.RemoveStart("Id".Length); - } + var parts = new List(); - return name.FirstLower(); - } + var min = GetMinStringLength(col); + if (min != null) + parts.Add("Min = " + min); - protected virtual IEnumerable GetFieldAttributes(DiffTable table, DiffColumn col, string? relatedEntity, bool isMList) - { - List attributes = new List(); + if (col.Length != -1) + parts.Add("Max = " + col.Length); + + return "StringLengthValidator(" + parts.ToString(", ") + ")"; + } - if (col.ForeignKey == null) + protected virtual int? GetMinStringLength(DiffColumn col) { - string? sqlDbType = GetSqlTypeAttribute(table, col); - if (sqlDbType != null) - attributes.Add(sqlDbType); + return 1; } - if (RequiresColumnName(table, col)) - attributes.Add("ColumnName(\"" + col.Name + "\")"); + protected virtual string GetFieldName(DiffTable table, DiffColumn col) + { + string name = !IdentifierValidatorAttribute.PascalAscii.IsMatch(col.Name) || col.Name.Contains("_") ? col.Name.ToPascal(false, false) : col.Name; - if (HasUniqueIndex(table, col)) - attributes.Add("UniqueIndex"); + if (this.GetRelatedEntity(table, col) != null) + { + if (name.Length > 2 && name.EndsWith("Id", StringComparison.InvariantCultureIgnoreCase)) + name = name.RemoveEnd("Id".Length); - return attributes; - } + if (name.Length > 2 && name.StartsWith("Id", StringComparison.InvariantCultureIgnoreCase)) + name = name.RemoveStart("Id".Length); + } - protected virtual bool RequiresColumnName(DiffTable table, DiffColumn col) - { - return GetEmbeddedField(table, col) != null || col.Name != DefaultColumnName(table, col); - } + return name.FirstLower(); + } - protected virtual bool HasUniqueIndex(DiffTable table, DiffColumn col) - { - return table.Indices.Values.Any(ix => - ix.FilterDefinition == null && - ix.Columns.Only()?.Let(ic => ic.ColumnName == col.Name && ic.IsIncluded == false) == true && - ix.IsUnique && - ix.IsPrimary); - } + protected virtual IEnumerable GetFieldAttributes(DiffTable table, DiffColumn col, string? relatedEntity, bool isMList) + { + List attributes = new List(); - protected virtual string DefaultColumnName(DiffTable table, DiffColumn col) - { - string fieldName = GetFieldName(table, col).FirstUpper(); + if (col.ForeignKey == null) + { + string? sqlDbType = GetSqlTypeAttribute(table, col); + if (sqlDbType != null) + attributes.Add(sqlDbType); + } - if (col.ForeignKey == null) - return fieldName; + if (RequiresColumnName(table, col)) + attributes.Add("ColumnName(\"" + col.Name + "\")"); - return fieldName + "ID"; - } + if (HasUniqueIndex(table, col)) + attributes.Add("UniqueIndex"); - protected virtual string? GetSqlTypeAttribute(DiffTable table, DiffColumn col) - { - Type type = GetValueType(col); - List parts = GetSqlDbTypeParts(col, type); + return attributes; + } - if (parts.Any() && SqlTypeAttributeNecessary(parts, table, col)) - return "DbType(" + parts.ToString(", ") + ")"; + protected virtual bool RequiresColumnName(DiffTable table, DiffColumn col) + { + return GetEmbeddedField(table, col) != null || col.Name != DefaultColumnName(table, col); + } - return null; - } + protected virtual bool HasUniqueIndex(DiffTable table, DiffColumn col) + { + return table.Indices.Values.Any(ix => + ix.FilterDefinition == null && + ix.Columns.Only()?.Let(ic => ic.ColumnName == col.Name && ic.IsIncluded == false) == true && + ix.IsUnique && + ix.IsPrimary); + } - protected virtual bool SqlTypeAttributeNecessary(List parts, DiffTable table, DiffColumn col) - { - var part = parts.Only(); - if (part != null && part.StartsWith("Size = ") && GetValueType(col) == typeof(string)) - return false; + protected virtual string DefaultColumnName(DiffTable table, DiffColumn col) + { + string fieldName = GetFieldName(table, col).FirstUpper(); - return true; - } + if (col.ForeignKey == null) + return fieldName; - protected virtual List GetSqlDbTypeParts(DiffColumn col, Type type) - { - List parts = new List(); - var pair = CurrentSchema.Settings.GetSqlDbTypePair(type); - if (pair.DbType.SqlServer != col.DbType.SqlServer) - parts.Add("SqlDbType = SqlDbType." + col.DbType.SqlServer); + return fieldName + "ID"; + } - var defaultSize = CurrentSchema.Settings.GetSqlSize(null, null, pair.DbType); - if (defaultSize != null) + protected virtual string? GetSqlTypeAttribute(DiffTable table, DiffColumn col) { - if (!(defaultSize == col.Precision || defaultSize == col.Length || defaultSize == int.MaxValue && col.Length == -1)) - parts.Add("Size = " + (col.Length == -1 ? "int.MaxValue" : - col.Length != 0 ? col.Length.ToString() : - col.Precision != 0 ? col.Precision.ToString() : "0")); + Type type = GetValueType(col); + List parts = GetSqlDbTypeParts(col, type); + + if (parts.Any() && SqlTypeAttributeNecessary(parts, table, col)) + return "DbType(" + parts.ToString(", ") + ")"; + + return null; } - var defaultScale = CurrentSchema.Settings.GetSqlScale(null, null, col.DbType); - if (defaultScale != null) + protected virtual bool SqlTypeAttributeNecessary(List parts, DiffTable table, DiffColumn col) { - if (!(col.Scale == defaultScale)) - parts.Add("Scale = " + col.Scale); + var part = parts.Only(); + if (part != null && part.StartsWith("Size = ") && GetValueType(col) == typeof(string)) + return false; + + return true; } - if (col.DefaultConstraint != null) - parts.Add("Default = \"" + CleanDefault(col.DefaultConstraint.Definition) + "\""); + protected virtual List GetSqlDbTypeParts(DiffColumn col, Type type) + { + List parts = new List(); + var pair = CurrentSchema.Settings.GetSqlDbTypePair(type); + if (pair.DbType.SqlServer != col.DbType.SqlServer) + parts.Add("SqlDbType = SqlDbType." + col.DbType.SqlServer); - return parts; - } + var defaultSize = CurrentSchema.Settings.GetSqlSize(null, null, pair.DbType); + if (defaultSize != null) + { + if (!(defaultSize == col.Length || defaultSize == int.MaxValue && col.Length == -1)) + parts.Add("Size = " + (col.Length == -1 ? "int.MaxValue" : + col.Length != 0 ? col.Length.ToString() : + col.Precision != 0 ? col.Precision.ToString() : "0")); + } - protected virtual string CleanDefault(string def) - { - if (def.StartsWith("(") && def.EndsWith(")")) - return def[1..^1]; + var defaultPrecision = CurrentSchema.Settings.GetSqlPrecision(null, null, pair.DbType); + if (defaultPrecision != null) + { + if (defaultPrecision != col.Precision) + parts.Add("Precision = " + (col.Precision != 0 ? col.Precision.ToString() : "0")); + } - return def; - } + var defaultScale = CurrentSchema.Settings.GetSqlScale(null, null, col.DbType); + if (defaultScale != null) + { + if (!(col.Scale == defaultScale)) + parts.Add("Scale = " + col.Scale); + } - protected virtual string GetFieldType(DiffTable table, DiffColumn col, string? relatedEntity) - { - var nullable = (col.Nullable ? "?" : ""); + if (col.DefaultConstraint != null) + parts.Add("Default = \"" + CleanDefault(col.DefaultConstraint.Definition) + "\""); + + return parts; + } - if (relatedEntity != null) + protected virtual string CleanDefault(string def) { - if (IsEnum(Tables.GetOrThrow(col.ForeignKey!.TargetTable))) - return relatedEntity + nullable; + if (def.StartsWith("(") && def.EndsWith(")")) + return def[1..^1]; - return (IsLite(table, col) ? "Lite<" + relatedEntity + ">" : relatedEntity) + nullable; + return def; } - return GetValueType(col).TypeName() + nullable; - } + protected virtual string GetFieldType(DiffTable table, DiffColumn col, string? relatedEntity) + { + var nullable = (col.Nullable ? "?" : ""); - protected virtual bool IsLite(DiffTable table, DiffColumn col) - { - return true; - } + if (relatedEntity != null) + { + if (IsEnum(Tables.GetOrThrow(col.ForeignKey!.TargetTable))) + return relatedEntity + nullable; - protected internal virtual Type GetValueType(DiffColumn col) - { - return col.DbType.SqlServer switch - { - SqlDbType.BigInt => typeof(long), - SqlDbType.Binary => typeof(byte[]), - SqlDbType.Bit => typeof(bool), - SqlDbType.Char => typeof(char), - SqlDbType.Date => typeof(DateTime), - SqlDbType.DateTime => typeof(DateTime), - SqlDbType.DateTime2 => typeof(DateTime), - SqlDbType.DateTimeOffset => typeof(DateTimeOffset), - SqlDbType.Decimal => typeof(Decimal), - SqlDbType.Float => typeof(double), - SqlDbType.Image => typeof(byte[]), - SqlDbType.Int => typeof(int), - SqlDbType.Money => typeof(decimal), - SqlDbType.NChar => typeof(string), - SqlDbType.NText => typeof(string), - SqlDbType.NVarChar => typeof(string), - SqlDbType.Real => typeof(float), - SqlDbType.SmallDateTime => typeof(DateTime), - SqlDbType.SmallInt => typeof(short), - SqlDbType.SmallMoney => typeof(decimal), - SqlDbType.Text => typeof(string), - SqlDbType.Time => typeof(TimeSpan), - SqlDbType.Timestamp => typeof(TimeSpan), - SqlDbType.TinyInt => typeof(byte), - SqlDbType.UniqueIdentifier => typeof(Guid), - SqlDbType.VarBinary => typeof(byte[]), - SqlDbType.VarChar => typeof(string), - SqlDbType.Xml => typeof(string), - SqlDbType.Udt => Schema.Current.Settings.UdtSqlName + return (IsLite(table, col) ? "Lite<" + relatedEntity + ">" : relatedEntity) + nullable; + } + + return GetValueType(col).TypeName() + nullable; + } + + protected virtual bool IsLite(DiffTable table, DiffColumn col) + { + return true; + } + + protected internal virtual Type GetValueType(DiffColumn col) + { + return col.DbType.SqlServer switch + { + SqlDbType.BigInt => typeof(long), + SqlDbType.Binary => typeof(byte[]), + SqlDbType.Bit => typeof(bool), + SqlDbType.Char => typeof(char), + SqlDbType.Date => typeof(DateTime), + SqlDbType.DateTime => typeof(DateTime), + SqlDbType.DateTime2 => typeof(DateTime), + SqlDbType.DateTimeOffset => typeof(DateTimeOffset), + SqlDbType.Decimal => typeof(Decimal), + SqlDbType.Float => typeof(double), + SqlDbType.Image => typeof(byte[]), + SqlDbType.Int => typeof(int), + SqlDbType.Money => typeof(decimal), + SqlDbType.NChar => typeof(string), + SqlDbType.NText => typeof(string), + SqlDbType.NVarChar => typeof(string), + SqlDbType.Real => typeof(float), + SqlDbType.SmallDateTime => typeof(DateTime), + SqlDbType.SmallInt => typeof(short), + SqlDbType.SmallMoney => typeof(decimal), + SqlDbType.Text => typeof(string), + SqlDbType.Time => typeof(TimeSpan), + SqlDbType.Timestamp => typeof(TimeSpan), + SqlDbType.TinyInt => typeof(byte), + SqlDbType.UniqueIdentifier => typeof(Guid), + SqlDbType.VarBinary => typeof(byte[]), + SqlDbType.VarChar => typeof(string), + SqlDbType.Xml => typeof(string), + SqlDbType.Udt => Schema.Current.Settings.UdtSqlName .SingleOrDefaultEx(kvp => StringComparer.InvariantCultureIgnoreCase.Equals(kvp.Value, col.UserTypeName)) .Key, - _ => throw new NotImplementedException("Unknown translation for " + col.DbType.SqlServer), - }; - } - - protected virtual string WriteEmbeddedField(DiffTable table, string fieldName) - { - StringBuilder sb = new StringBuilder(); + _ => throw new NotImplementedException("Unknown translation for " + col.DbType.SqlServer), + }; + } - fieldName = fieldName.FirstLower(); - string propertyName = fieldName.FirstUpper(); - string typeName = GetEmbeddedTypeName(fieldName); + protected virtual string WriteEmbeddedField(DiffTable table, string fieldName) + { + StringBuilder sb = new StringBuilder(); - sb.AppendLine("public {0} {1} { get; set; }".FormatWith(typeName, fieldName.FirstUpper())); + fieldName = fieldName.FirstLower(); + string propertyName = fieldName.FirstUpper(); + string typeName = GetEmbeddedTypeName(fieldName); - return sb.ToString(); - } + sb.AppendLine("public {0} {1} { get; set; }".FormatWith(typeName, fieldName.FirstUpper())); - protected virtual string GetEmbeddedTypeName(string fieldName) - { - return fieldName.FirstUpper() + "Embedded"; - } + return sb.ToString(); + } - protected virtual string WriteFieldMList(string fileName, DiffTable table, MListInfo mListInfo, DiffTable relatedTable) - { - string type; - List fieldAttributes; - if(mListInfo.TrivialElementColumn == null ) + protected virtual string GetEmbeddedTypeName(string fieldName) { - type = GetEntityName(relatedTable); - fieldAttributes = new List { }; + return fieldName.FirstUpper() + "Embedded"; } - else + + protected virtual string WriteFieldMList(string fileName, DiffTable table, MListInfo mListInfo, DiffTable relatedTable) { - string relatedEntity = GetRelatedEntity(relatedTable, mListInfo.TrivialElementColumn)!; - type = GetFieldType(relatedTable, mListInfo.TrivialElementColumn, relatedEntity); + string type; + List fieldAttributes; + if(mListInfo.TrivialElementColumn == null ) + { + type = GetEntityName(relatedTable); + fieldAttributes = new List { }; + } + else + { + string relatedEntity = GetRelatedEntity(relatedTable, mListInfo.TrivialElementColumn)!; + type = GetFieldType(relatedTable, mListInfo.TrivialElementColumn, relatedEntity); - fieldAttributes = GetFieldAttributes(relatedTable, mListInfo.TrivialElementColumn, relatedEntity, isMList: true).ToList(); - } + fieldAttributes = GetFieldAttributes(relatedTable, mListInfo.TrivialElementColumn, relatedEntity, isMList: true).ToList(); + } - string? preserveOrder = GetPreserveOrderAttribute(mListInfo); - if (preserveOrder != null) - fieldAttributes.Add(preserveOrder); + string? preserveOrder = GetPreserveOrderAttribute(mListInfo); + if (preserveOrder != null) + fieldAttributes.Add(preserveOrder); - string? primaryKey = mListInfo.IsVirtual ? null : GetPrimaryKeyAttribute(relatedTable); - if (primaryKey != null) - fieldAttributes.Add(primaryKey); + string? primaryKey = mListInfo.IsVirtual ? null : GetPrimaryKeyAttribute(relatedTable); + if (primaryKey != null) + fieldAttributes.Add(primaryKey); - string? tableName = mListInfo.IsVirtual ? null : GetTableNameAttribute(relatedTable.Name, mListInfo); - if (tableName != null) - fieldAttributes.Add(tableName); + string? tableName = mListInfo.IsVirtual ? null : GetTableNameAttribute(relatedTable.Name, mListInfo); + if (tableName != null) + fieldAttributes.Add(tableName); - string? backColumn = mListInfo.IsVirtual ? null : GetBackColumnNameAttribute(mListInfo.BackReferenceColumn); - if (backColumn != null) - fieldAttributes.AddRange(backColumn); + string? backColumn = mListInfo.IsVirtual ? null : GetBackColumnNameAttribute(mListInfo.BackReferenceColumn); + if (backColumn != null) + fieldAttributes.AddRange(backColumn); - StringBuilder sb = new StringBuilder(); + StringBuilder sb = new StringBuilder(); - string fieldName = GetFieldMListName(table, relatedTable, mListInfo); - WriteAttributeTag(sb, fieldAttributes); - sb.AppendLine("[NoRepeatValidator]"); + string fieldName = GetFieldMListName(table, relatedTable, mListInfo); + WriteAttributeTag(sb, fieldAttributes); + sb.AppendLine("[NoRepeatValidator]"); - if (mListInfo.IsVirtual) - sb.AppendLine("[Ignore, QueryableProperty] //Virtual MList "); + if (mListInfo.IsVirtual) + sb.AppendLine("[Ignore, QueryableProperty] //Virtual MList "); - sb.AppendLine("public MList<{0}> {1} {{ get; set; }} = new MList<{0}>();".FormatWith(type, fieldName.FirstUpper())); + sb.AppendLine("public MList<{0}> {1} {{ get; set; }} = new MList<{0}>();".FormatWith(type, fieldName.FirstUpper())); - return sb.ToString(); - } + return sb.ToString(); + } - protected virtual string? GetPreserveOrderAttribute(MListInfo mListInfo) - { - if(mListInfo.PreserveOrderColumn == null) - return null; + protected virtual string? GetPreserveOrderAttribute(MListInfo mListInfo) + { + if(mListInfo.PreserveOrderColumn == null) + return null; - if (mListInfo.IsVirtual) - return "PreserveOrder"; + if (mListInfo.IsVirtual) + return "PreserveOrder"; - var parts = new List - { - "\"" + mListInfo.PreserveOrderColumn.Name + "\"" - }; + var parts = new List + { + "\"" + mListInfo.PreserveOrderColumn.Name + "\"" + }; - Type type = GetValueType(mListInfo.PreserveOrderColumn); + Type type = GetValueType(mListInfo.PreserveOrderColumn); - parts.AddRange(GetSqlDbTypeParts(mListInfo.PreserveOrderColumn, type)); + parts.AddRange(GetSqlDbTypeParts(mListInfo.PreserveOrderColumn, type)); - return @"PreserveOrder({0})".FormatWith(parts.ToString(", ")); - } + return @"PreserveOrder({0})".FormatWith(parts.ToString(", ")); + } - protected virtual string? GetBackColumnNameAttribute(DiffColumn backReference) - { - if (backReference.Name == "ParentID") - return null; + protected virtual string? GetBackColumnNameAttribute(DiffColumn backReference) + { + if (backReference.Name == "ParentID") + return null; - return "BackReferenceColumnName(\"{0}\")".FormatWith(backReference.Name); - } + return "BackReferenceColumnName(\"{0}\")".FormatWith(backReference.Name); + } - protected virtual string GetFieldMListName(DiffTable table, DiffTable relatedTable, MListInfo mListInfo) - { - return NaturalLanguageTools.Pluralize(relatedTable.Name.Name.RemovePrefix(table.Name.Name)); - } + protected virtual string GetFieldMListName(DiffTable table, DiffTable relatedTable, MListInfo mListInfo) + { + return NaturalLanguageTools.Pluralize(relatedTable.Name.Name.RemovePrefix(table.Name.Name)); + } - protected virtual string? WriteToString(DiffTable table) - { - var toStringColumn = GetToStringColumn(table); - if (toStringColumn == null) - return null; + protected virtual string? WriteToString(DiffTable table) + { + var toStringColumn = GetToStringColumn(table); + if (toStringColumn == null) + return null; - var fieldName = toStringColumn.PrimaryKey ? "Id" : GetFieldName(table, toStringColumn).FirstUpper(); - var fixer = toStringColumn.PrimaryKey || GetFieldType(table, toStringColumn, GetRelatedEntity(table, toStringColumn)) != "string" ? " + \"\"" : ""; - var body = fieldName + fixer; + var fieldName = toStringColumn.PrimaryKey ? "Id" : GetFieldName(table, toStringColumn).FirstUpper(); + var fixer = toStringColumn.PrimaryKey || GetFieldType(table, toStringColumn, GetRelatedEntity(table, toStringColumn)) != "string" ? " + \"\"" : ""; + var body = fieldName + fixer; - return WriteToStringWithBody(table, body); - } + return WriteToStringWithBody(table, body); + } - protected virtual string WriteToStringWithBody(DiffTable table, string body) - { - StringBuilder sb = new StringBuilder(); - sb.AppendLine("[AutoExpressionField]"); - sb.AppendLine($"public override string ToString() => As.Expression(() => {body});"); - return sb.ToString(); - } + protected virtual string WriteToStringWithBody(DiffTable table, string body) + { + StringBuilder sb = new StringBuilder(); + sb.AppendLine("[AutoExpressionField]"); + sb.AppendLine($"public override string ToString() => As.Expression(() => {body});"); + return sb.ToString(); + } - protected virtual DiffColumn? GetToStringColumn(DiffTable table) - { - return table.Columns.TryGetC("Name") ?? table.Columns.Values.FirstOrDefault(a => a.PrimaryKey); + protected virtual DiffColumn? GetToStringColumn(DiffTable table) + { + return table.Columns.TryGetC("Name") ?? table.Columns.Values.FirstOrDefault(a => a.PrimaryKey); + } } -} -public class MListInfo -{ - public MListInfo(DiffColumn backReferenceColumn) + public class MListInfo { - this.BackReferenceColumn = backReferenceColumn; - } + public MListInfo(DiffColumn backReferenceColumn) + { + this.BackReferenceColumn = backReferenceColumn; + } - public readonly DiffColumn BackReferenceColumn; - public DiffColumn? TrivialElementColumn; - public DiffColumn? PreserveOrderColumn; - public bool IsVirtual; -} + public readonly DiffColumn BackReferenceColumn; + public DiffColumn? TrivialElementColumn; + public DiffColumn? PreserveOrderColumn; + public bool IsVirtual; + } diff --git a/Signum.Engine/Connection/Connector.cs b/Signum.Engine/Connection/Connector.cs index 3ecb3eed96..7e7be2b6a9 100644 --- a/Signum.Engine/Connection/Connector.cs +++ b/Signum.Engine/Connection/Connector.cs @@ -8,206 +8,206 @@ namespace Signum.Engine; -public abstract class Connector -{ - static readonly Variable currentConnector = Statics.ThreadVariable("connection"); - - public SqlBuilder SqlBuilder; - - public static IDisposable Override(Connector connector) + public abstract class Connector { - Connector oldConnection = currentConnector.Value; + static readonly Variable currentConnector = Statics.ThreadVariable("connection"); - currentConnector.Value = connector; + public SqlBuilder SqlBuilder; - return new Disposable(() => currentConnector.Value = oldConnection); - } + public static IDisposable Override(Connector connector) + { + Connector oldConnection = currentConnector.Value; - public static Connector Current - { - get { return currentConnector.Value ?? Default; } - } + currentConnector.Value = connector; - public static Connector Default { get; set; } = null!; + return new Disposable(() => currentConnector.Value = oldConnection); + } - static readonly Variable scopeTimeout = Statics.ThreadVariable("scopeTimeout"); - public static int? ScopeTimeout { get { return scopeTimeout.Value; } } - public static IDisposable CommandTimeoutScope(int? timeoutSeconds) - { - var old = scopeTimeout.Value; - scopeTimeout.Value = timeoutSeconds; - return new Disposable(() => scopeTimeout.Value = old); - } + public static Connector Current + { + get { return currentConnector.Value ?? Default; } + } - public Connector(Schema schema) - { - this.Schema = schema; - this.IsolationLevel = IsolationLevel.Unspecified; - this.SqlBuilder = new SqlBuilder(this); - } + public static Connector Default { get; set; } = null!; - public Schema Schema { get; private set; } + static readonly Variable scopeTimeout = Statics.ThreadVariable("scopeTimeout"); + public static int? ScopeTimeout { get { return scopeTimeout.Value; } } + public static IDisposable CommandTimeoutScope(int? timeoutSeconds) + { + var old = scopeTimeout.Value; + scopeTimeout.Value = timeoutSeconds; + return new Disposable(() => scopeTimeout.Value = old); + } - static readonly Variable logger = Statics.ThreadVariable("connectionlogger"); - public static TextWriter? CurrentLogger - { - get { return logger.Value; } - set { logger.Value = value; } - } + public Connector(Schema schema) + { + this.Schema = schema; + this.IsolationLevel = IsolationLevel.Unspecified; + this.SqlBuilder = new SqlBuilder(this); + } - protected static void Log(SqlPreCommandSimple pcs) - { - var log = logger.Value; - if (log != null) + public Schema Schema { get; private set; } + + static readonly Variable logger = Statics.ThreadVariable("connectionlogger"); + public static TextWriter? CurrentLogger { - log.WriteLine(pcs.Sql); - if (pcs.Parameters != null) - log.WriteLine(pcs.Parameters - .ToString(p => "{0} {1}: {2}".FormatWith( - p.ParameterName, - Connector.Current.GetSqlDbType(p), - p.Value?.Let(v => v.ToString())), "\r\n")); - log.WriteLine(); + get { return logger.Value; } + set { logger.Value = value; } } - } - public abstract string GetSqlDbType(DbParameter p); + protected static void Log(SqlPreCommandSimple pcs) + { + var log = logger.Value; + if (log != null) + { + log.WriteLine(pcs.Sql); + if (pcs.Parameters != null) + log.WriteLine(pcs.Parameters + .ToString(p => "{0} {1}: {2}".FormatWith( + p.ParameterName, + Connector.Current.GetSqlDbType(p), + p.Value?.Let(v => v.ToString())), "\r\n")); + log.WriteLine(); + } + } - protected internal abstract object? ExecuteScalar(SqlPreCommandSimple preCommand, CommandType commandType); - protected internal abstract int ExecuteNonQuery(SqlPreCommandSimple preCommand, CommandType commandType); - protected internal abstract DataTable ExecuteDataTable(SqlPreCommandSimple preCommand, CommandType commandType); - protected internal abstract DbDataReaderWithCommand UnsafeExecuteDataReader(SqlPreCommandSimple preCommand, CommandType commandType); - protected internal abstract Task UnsafeExecuteDataReaderAsync(SqlPreCommandSimple preCommand, CommandType commandType, CancellationToken token); - protected internal abstract void BulkCopy(DataTable dt, List columns, ObjectName destinationTable, SqlBulkCopyOptions options, int? timeout); + public abstract string GetSqlDbType(DbParameter p); - public abstract Connector ForDatabase(Maps.DatabaseName? database); + protected internal abstract object? ExecuteScalar(SqlPreCommandSimple preCommand, CommandType commandType); + protected internal abstract int ExecuteNonQuery(SqlPreCommandSimple preCommand, CommandType commandType); + protected internal abstract DataTable ExecuteDataTable(SqlPreCommandSimple preCommand, CommandType commandType); + protected internal abstract DbDataReaderWithCommand UnsafeExecuteDataReader(SqlPreCommandSimple preCommand, CommandType commandType); + protected internal abstract Task UnsafeExecuteDataReaderAsync(SqlPreCommandSimple preCommand, CommandType commandType, CancellationToken token); + protected internal abstract void BulkCopy(DataTable dt, List columns, ObjectName destinationTable, SqlBulkCopyOptions options, int? timeout); - public abstract string DatabaseName(); + public abstract Connector ForDatabase(Maps.DatabaseName? database); - public abstract string DataSourceName(); + public abstract string DatabaseName(); - public abstract int MaxNameLength { get; } + public abstract string DataSourceName(); - public abstract void SaveTransactionPoint(DbTransaction transaction, string savePointName); + public abstract int MaxNameLength { get; } - public abstract void RollbackTransactionPoint(DbTransaction Transaction, string savePointName); + public abstract void SaveTransactionPoint(DbTransaction transaction, string savePointName); - public abstract DbParameter CloneParameter(DbParameter p); + public abstract void RollbackTransactionPoint(DbTransaction Transaction, string savePointName); - public abstract DbConnection CreateConnection(); + public abstract DbParameter CloneParameter(DbParameter p); - public IsolationLevel IsolationLevel { get; set; } + public abstract DbConnection CreateConnection(); - public abstract ParameterBuilder ParameterBuilder { get; protected set; } + public IsolationLevel IsolationLevel { get; set; } - public abstract void CleanDatabase(DatabaseName? database); + public abstract ParameterBuilder ParameterBuilder { get; protected set; } - public abstract bool AllowsMultipleQueries { get; } + public abstract void CleanDatabase(DatabaseName? database); - public abstract bool SupportsScalarSubquery { get; } - public abstract bool SupportsScalarSubqueryInAggregates { get; } + public abstract bool AllowsMultipleQueries { get; } + public abstract bool SupportsScalarSubquery { get; } + public abstract bool SupportsScalarSubqueryInAggregates { get; } - public static string? TryExtractDatabaseNameWithPostfix(ref string connectionString, string catalogPostfix) - { - string toFind = "+" + catalogPostfix; - string? result = connectionString.TryBefore(toFind).TryAfterLast("="); - if (result == null) - return null; + public static string? TryExtractDatabaseNameWithPostfix(ref string connectionString, string catalogPostfix) + { + string toFind = "+" + catalogPostfix; - connectionString = connectionString.Replace(toFind, ""); // Remove toFind + string? result = connectionString.TryBefore(toFind).TryAfterLast("="); + if (result == null) + return null; - return result + catalogPostfix; - } + connectionString = connectionString.Replace(toFind, ""); // Remove toFind - public static string ExtractCatalogPostfix(ref string connectionString, string catalogPostfix) - { - string toFind = "+" + catalogPostfix; + return result + catalogPostfix; + } - int index = connectionString.IndexOf(toFind); - if (index == -1) - throw new InvalidOperationException("CatalogPostfix '{0}' not found in the connection string".FormatWith(toFind)); + public static string ExtractCatalogPostfix(ref string connectionString, string catalogPostfix) + { + string toFind = "+" + catalogPostfix; - connectionString = connectionString.Substring(0, index) + connectionString.Substring(index + toFind.Length); // Remove toFind + int index = connectionString.IndexOf(toFind); + if (index == -1) + throw new InvalidOperationException("CatalogPostfix '{0}' not found in the connection string".FormatWith(toFind)); - return catalogPostfix; - } + connectionString = connectionString.Substring(0, index) + connectionString.Substring(index + toFind.Length); // Remove toFind - public abstract bool HasTables(); + return catalogPostfix; + } - public abstract bool AllowsSetSnapshotIsolation { get; } + public abstract bool HasTables(); - public abstract bool AllowsIndexWithWhere(string where); + public abstract bool AllowsSetSnapshotIsolation { get; } - public abstract bool AllowsConvertToDate { get; } + public abstract bool AllowsIndexWithWhere(string where); - public abstract bool AllowsConvertToTime { get; } + public abstract bool AllowsConvertToDate { get; } - public abstract bool SupportsSqlDependency { get; } + public abstract bool AllowsConvertToTime { get; } - public abstract bool SupportsFormat { get; } + public abstract bool SupportsSqlDependency { get; } - public abstract bool SupportsTemporalTables { get; } + public abstract bool SupportsFormat { get; } - public abstract bool RequiresRetry { get; } -} + public abstract bool SupportsTemporalTables { get; } -public abstract class ParameterBuilder -{ - public static string GetParameterName(string name) - { - return "@" + name; + public abstract bool RequiresRetry { get; } } - public DbParameter CreateReferenceParameter(string parameterName, PrimaryKey? id, IColumn column) + public abstract class ParameterBuilder { - return CreateParameter(parameterName, column.DbType, null, column.Nullable.ToBool(), id == null ? null : id.Value.Object); - } + public static string GetParameterName(string name) + { + return "@" + name; + } - public DbParameter CreateParameter(string parameterName, object? value, Type type) - { - var pair = Schema.Current.Settings.GetSqlDbTypePair(type.UnNullify()); + public DbParameter CreateReferenceParameter(string parameterName, PrimaryKey? id, IColumn column) + { + return CreateParameter(parameterName, column.DbType, null, column.Nullable.ToBool(), id == null ? null : id.Value.Object); + } - return CreateParameter(parameterName, pair.DbType, pair.UserDefinedTypeName, type == null || type.IsByRef || type.IsNullable(), value); - } + public DbParameter CreateParameter(string parameterName, object? value, Type type) + { + var pair = Schema.Current.Settings.GetSqlDbTypePair(type.UnNullify()); - public abstract DbParameter CreateParameter(string parameterName, AbstractDbType dbType, string? udtTypeName, bool nullable, object? value); - public abstract MemberInitExpression ParameterFactory(Expression parameterName, AbstractDbType dbType, string? udtTypeName, bool nullable, Expression value); + return CreateParameter(parameterName, pair.DbType, pair.UserDefinedTypeName, type == null || type.IsByRef || type.IsNullable(), value); + } + + public abstract DbParameter CreateParameter(string parameterName, AbstractDbType dbType, string? udtTypeName, bool nullable, object? value); + public abstract MemberInitExpression ParameterFactory(Expression parameterName, AbstractDbType dbType, int? size, byte? precision, byte? scale, string? udtTypeName, bool nullable, Expression value); - protected static MethodInfo miAsserDateTime = ReflectionTools.GetMethodInfo(() => AssertDateTime(null)); + protected static MethodInfo miAsserDateTime = ReflectionTools.GetMethodInfo(() => AssertDateTime(null)); protected static MethodInfo miToDateTimeKind = ReflectionTools.GetMethodInfo(() => DateOnly.MinValue.ToDateTime(DateTimeKind.Utc)); protected static MethodInfo miToTimeSpan = ReflectionTools.GetMethodInfo(() => TimeOnly.MaxValue.ToTimeSpan()); - protected static DateTime? AssertDateTime(DateTime? dateTime) - { - - if (dateTime.HasValue) + protected static DateTime? AssertDateTime(DateTime? dateTime) { - if (Schema.Current.TimeZoneMode == TimeZoneMode.Utc && dateTime.Value.Kind != DateTimeKind.Utc) - throw new InvalidOperationException("Attempt to use a non-Utc date in the database"); - //Problematic with Time machine - //if (Schema.Current.TimeZoneMode != TimeZoneMode.Utc && dateTime.Value.Kind == DateTimeKind.Utc) - // throw new InvalidOperationException("Attempt to use a Utc date in the database"); - } + if (dateTime.HasValue) + { + if (Schema.Current.TimeZoneMode == TimeZoneMode.Utc && dateTime.Value.Kind != DateTimeKind.Utc) + throw new InvalidOperationException("Attempt to use a non-Utc date in the database"); + + //Problematic with Time machine + //if (Schema.Current.TimeZoneMode != TimeZoneMode.Utc && dateTime.Value.Kind == DateTimeKind.Utc) + // throw new InvalidOperationException("Attempt to use a Utc date in the database"); + } - return dateTime; + return dateTime; + } } -} -public class DbDataReaderWithCommand : IDisposable -{ - public DbDataReaderWithCommand(DbCommand command, DbDataReader reader) + public class DbDataReaderWithCommand : IDisposable { - Command = command; - Reader = reader; - } + public DbDataReaderWithCommand(DbCommand command, DbDataReader reader) + { + Command = command; + Reader = reader; + } - public DbCommand Command { get; private set; } - public DbDataReader Reader { get; private set; } + public DbCommand Command { get; private set; } + public DbDataReader Reader { get; private set; } - public void Dispose() - { - Reader.Dispose(); + public void Dispose() + { + Reader.Dispose(); + } } -} diff --git a/Signum.Engine/Connection/PostgreSqlConnector.cs b/Signum.Engine/Connection/PostgreSqlConnector.cs index 3e2f6b4f76..e943c89595 100644 --- a/Signum.Engine/Connection/PostgreSqlConnector.cs +++ b/Signum.Engine/Connection/PostgreSqlConnector.cs @@ -10,367 +10,367 @@ namespace Signum.Engine; -public static class PostgresVersionDetector -{ - public static Version Detect(string connectionString) + public static class PostgresVersionDetector { - return SqlServerRetry.Retry(() => + public static Version Detect(string connectionString) { - using (NpgsqlConnection con = new NpgsqlConnection(connectionString)) + return SqlServerRetry.Retry(() => { - var sql = @"SHOW server_version;"; - - using (NpgsqlCommand cmd = new NpgsqlCommand(sql, con)) + using (NpgsqlConnection con = new NpgsqlConnection(connectionString)) { - NpgsqlDataAdapter da = new NpgsqlDataAdapter(cmd); + var sql = @"SHOW server_version;"; + + using (NpgsqlCommand cmd = new NpgsqlCommand(sql, con)) + { + NpgsqlDataAdapter da = new NpgsqlDataAdapter(cmd); - DataTable result = new DataTable(); - da.Fill(result); + DataTable result = new DataTable(); + da.Fill(result); - var version = (string)result.Rows[0]["server_version"]!; + var version = (string)result.Rows[0]["server_version"]!; - return new Version(version.TryBefore("(") ?? version); + return new Version(version.TryBefore("(") ?? version); + } } - } - }); + }); + } } -} - -public class PostgreSqlConnector : Connector -{ - public override ParameterBuilder ParameterBuilder { get; protected set; } - - public Version? PostgresVersion { get; set; } - public PostgreSqlConnector(string connectionString, Schema schema, Version? postgresVersion) : base(schema.Do(s => s.Settings.IsPostgres = true)) + public class PostgreSqlConnector : Connector { - this.ConnectionString = connectionString; - this.ParameterBuilder = new PostgreSqlParameterBuilder(); - this.PostgresVersion = postgresVersion; - } - - public override int MaxNameLength => 63; + public override ParameterBuilder ParameterBuilder { get; protected set; } - public int? CommandTimeout { get; set; } = null; - public string ConnectionString { get; set; } + public Version? PostgresVersion { get; set; } - public override bool AllowsMultipleQueries => true; + public PostgreSqlConnector(string connectionString, Schema schema, Version? postgresVersion) : base(schema.Do(s => s.Settings.IsPostgres = true)) + { + this.ConnectionString = connectionString; + this.ParameterBuilder = new PostgreSqlParameterBuilder(); + this.PostgresVersion = postgresVersion; + } - public override bool SupportsScalarSubquery => true; + public override int MaxNameLength => 63; - public override bool SupportsScalarSubqueryInAggregates => true; + public int? CommandTimeout { get; set; } = null; + public string ConnectionString { get; set; } - public override bool AllowsSetSnapshotIsolation => false; + public override bool AllowsMultipleQueries => true; - public override bool AllowsConvertToDate => true; + public override bool SupportsScalarSubquery => true; - public override bool AllowsConvertToTime => true; + public override bool SupportsScalarSubqueryInAggregates => true; - public override bool SupportsSqlDependency => false; + public override bool AllowsSetSnapshotIsolation => false; - public override bool SupportsFormat => true; + public override bool AllowsConvertToDate => true; - public override bool SupportsTemporalTables => true; + public override bool AllowsConvertToTime => true; - public override bool RequiresRetry => false; + public override bool SupportsSqlDependency => false; - public override bool AllowsIndexWithWhere(string where) => true; + public override bool SupportsFormat => true; - public override Connector ForDatabase(Maps.DatabaseName? database) - { - if (database == null) - return this; + public override bool SupportsTemporalTables => true; - throw new NotImplementedException("ForDatabase " + database); - } + public override bool RequiresRetry => false; - public override void CleanDatabase(DatabaseName? database) - { - PostgreSqlConnectorScripts.RemoveAllScript(database).ExecuteNonQuery(); - } - - public override DbParameter CloneParameter(DbParameter p) - { - NpgsqlParameter sp = (NpgsqlParameter)p; - return new NpgsqlParameter(sp.ParameterName, sp.Value) { IsNullable = sp.IsNullable, NpgsqlDbType = sp.NpgsqlDbType }; - } + public override bool AllowsIndexWithWhere(string where) => true; - public override DbConnection CreateConnection() - { - return new NpgsqlConnection(ConnectionString); - } - - public override string DatabaseName() - { - return new NpgsqlConnection(ConnectionString).Database!; - } - - public override string DataSourceName() - { - return new NpgsqlConnection(ConnectionString).DataSource; - } - - public override string GetSqlDbType(DbParameter p) - { - return ((NpgsqlParameter)p).NpgsqlDbType.ToString().ToUpperInvariant(); - } + public override Connector ForDatabase(Maps.DatabaseName? database) + { + if (database == null) + return this; - public override void RollbackTransactionPoint(DbTransaction transaction, string savePointName) - { - ((NpgsqlTransaction)transaction).Rollback(savePointName); - } + throw new NotImplementedException("ForDatabase " + database); + } - public override void SaveTransactionPoint(DbTransaction transaction, string savePointName) - { - ((NpgsqlTransaction)transaction).Save(savePointName); - } + public override void CleanDatabase(DatabaseName? database) + { + PostgreSqlConnectorScripts.RemoveAllScript(database).ExecuteNonQuery(); + } - T EnsureConnectionRetry(Func action) - { - if (Transaction.HasTransaction) - return action(null); + public override DbParameter CloneParameter(DbParameter p) + { + NpgsqlParameter sp = (NpgsqlParameter)p; + return new NpgsqlParameter(sp.ParameterName, sp.Value) { IsNullable = sp.IsNullable, NpgsqlDbType = sp.NpgsqlDbType }; + } - using (NpgsqlConnection con = new NpgsqlConnection(this.ConnectionString)) + public override DbConnection CreateConnection() { - con.Open(); + return new NpgsqlConnection(ConnectionString); + } - return action(con); + public override string DatabaseName() + { + return new NpgsqlConnection(ConnectionString).Database!; } - } - NpgsqlCommand NewCommand(SqlPreCommandSimple preCommand, NpgsqlConnection? overridenConnection, CommandType commandType) - { - NpgsqlCommand cmd = new NpgsqlCommand { CommandType = commandType }; + public override string DataSourceName() + { + return new NpgsqlConnection(ConnectionString).DataSource; + } - int? timeout = Connector.ScopeTimeout ?? CommandTimeout; - if (timeout.HasValue) - cmd.CommandTimeout = timeout.Value; + public override string GetSqlDbType(DbParameter p) + { + return ((NpgsqlParameter)p).NpgsqlDbType.ToString().ToUpperInvariant(); + } - if (overridenConnection != null) - cmd.Connection = overridenConnection; - else + public override void RollbackTransactionPoint(DbTransaction transaction, string savePointName) { - cmd.Connection = (NpgsqlConnection)Transaction.CurrentConnection!; - cmd.Transaction = (NpgsqlTransaction)Transaction.CurrentTransaccion!; + ((NpgsqlTransaction)transaction).Rollback(savePointName); } - cmd.CommandText = preCommand.Sql; + public override void SaveTransactionPoint(DbTransaction transaction, string savePointName) + { + ((NpgsqlTransaction)transaction).Save(savePointName); + } - if (preCommand.Parameters != null) + T EnsureConnectionRetry(Func action) { - foreach (NpgsqlParameter param in preCommand.Parameters) + if (Transaction.HasTransaction) + return action(null); + + using (NpgsqlConnection con = new NpgsqlConnection(this.ConnectionString)) { - cmd.Parameters.Add(param); + con.Open(); + + return action(con); } } - Log(preCommand); + NpgsqlCommand NewCommand(SqlPreCommandSimple preCommand, NpgsqlConnection? overridenConnection, CommandType commandType) + { + NpgsqlCommand cmd = new NpgsqlCommand { CommandType = commandType }; - return cmd; - } + int? timeout = Connector.ScopeTimeout ?? CommandTimeout; + if (timeout.HasValue) + cmd.CommandTimeout = timeout.Value; - protected internal override void BulkCopy(DataTable dt, List columns, ObjectName destinationTable, SqlBulkCopyOptions options, int? timeout) - { - EnsureConnectionRetry(con => - { - con = con ?? (NpgsqlConnection)Transaction.CurrentConnection!; + if (overridenConnection != null) + cmd.Connection = overridenConnection; + else + { + cmd.Connection = (NpgsqlConnection)Transaction.CurrentConnection!; + cmd.Transaction = (NpgsqlTransaction)Transaction.CurrentTransaccion!; + } - bool isPostgres = true; + cmd.CommandText = preCommand.Sql; - var columnsSql = dt.Columns.Cast().ToString(a => a.ColumnName.SqlEscape(isPostgres), ", "); - using (var writer = con.BeginBinaryImport($"COPY {destinationTable} ({columnsSql}) FROM STDIN (FORMAT BINARY)")) + if (preCommand.Parameters != null) { - for (int i = 0; i < dt.Rows.Count; i++) + foreach (NpgsqlParameter param in preCommand.Parameters) { - var row = dt.Rows[i]; - writer.StartRow(); - for (int j = 0; j < dt.Columns.Count; j++) - { - var col = dt.Columns[j]; - writer.Write(row[col], columns[j].DbType.PostgreSql); - } + cmd.Parameters.Add(param); } - - writer.Complete(); - return 0; } - }); - } - protected internal override DataTable ExecuteDataTable(SqlPreCommandSimple preCommand, CommandType commandType) - { - return EnsureConnectionRetry(con => + Log(preCommand); + + return cmd; + } + + protected internal override void BulkCopy(DataTable dt, List columns, ObjectName destinationTable, SqlBulkCopyOptions options, int? timeout) { - using (NpgsqlCommand cmd = NewCommand(preCommand, con, commandType)) - using (HeavyProfiler.Log("SQL", () => preCommand.sp_executesql())) + EnsureConnectionRetry(con => { - try - { - NpgsqlDataAdapter da = new NpgsqlDataAdapter(cmd); + con = con ?? (NpgsqlConnection)Transaction.CurrentConnection!; - DataTable result = new DataTable(); - da.Fill(result); - return result; - } - catch (Exception ex) + bool isPostgres = true; + + var columnsSql = dt.Columns.Cast().ToString(a => a.ColumnName.SqlEscape(isPostgres), ", "); + using (var writer = con.BeginBinaryImport($"COPY {destinationTable} ({columnsSql}) FROM STDIN (FORMAT BINARY)")) { - var nex = HandleException(ex, preCommand); - if (nex == ex) - throw; + for (int i = 0; i < dt.Rows.Count; i++) + { + var row = dt.Rows[i]; + writer.StartRow(); + for (int j = 0; j < dt.Columns.Count; j++) + { + var col = dt.Columns[j]; + writer.Write(row[col], columns[j].DbType.PostgreSql); + } + } - throw nex; + writer.Complete(); + return 0; } - } - }); - } + }); + } - protected internal override int ExecuteNonQuery(SqlPreCommandSimple preCommand, CommandType commandType) - { - return EnsureConnectionRetry(con => + protected internal override DataTable ExecuteDataTable(SqlPreCommandSimple preCommand, CommandType commandType) { - using (NpgsqlCommand cmd = NewCommand(preCommand, con, commandType)) - using (HeavyProfiler.Log("SQL", () => preCommand.sp_executesql())) + return EnsureConnectionRetry(con => { - try + using (NpgsqlCommand cmd = NewCommand(preCommand, con, commandType)) + using (HeavyProfiler.Log("SQL", () => preCommand.sp_executesql())) { - int result = cmd.ExecuteNonQuery(); - return result; + try + { + NpgsqlDataAdapter da = new NpgsqlDataAdapter(cmd); + + DataTable result = new DataTable(); + da.Fill(result); + return result; + } + catch (Exception ex) + { + var nex = HandleException(ex, preCommand); + if (nex == ex) + throw; + + throw nex; + } } - catch (Exception ex) + }); + } + + protected internal override int ExecuteNonQuery(SqlPreCommandSimple preCommand, CommandType commandType) + { + return EnsureConnectionRetry(con => + { + using (NpgsqlCommand cmd = NewCommand(preCommand, con, commandType)) + using (HeavyProfiler.Log("SQL", () => preCommand.sp_executesql())) { - var nex = HandleException(ex, preCommand); - if (nex == ex) - throw; + try + { + int result = cmd.ExecuteNonQuery(); + return result; + } + catch (Exception ex) + { + var nex = HandleException(ex, preCommand); + if (nex == ex) + throw; - throw nex; + throw nex; + } } - } - }); - } + }); + } - protected internal override object? ExecuteScalar(SqlPreCommandSimple preCommand, CommandType commandType) - { - return EnsureConnectionRetry(con => + protected internal override object? ExecuteScalar(SqlPreCommandSimple preCommand, CommandType commandType) { - using (NpgsqlCommand cmd = NewCommand(preCommand, con, commandType)) - using (HeavyProfiler.Log("SQL", () => preCommand.sp_executesql())) + return EnsureConnectionRetry(con => { - try + using (NpgsqlCommand cmd = NewCommand(preCommand, con, commandType)) + using (HeavyProfiler.Log("SQL", () => preCommand.sp_executesql())) { - object? result = cmd.ExecuteScalar(); + try + { + object? result = cmd.ExecuteScalar(); - if (result == null || result == DBNull.Value) - return null; + if (result == null || result == DBNull.Value) + return null; - return result; - } - catch (Exception ex) - { - var nex = HandleException(ex, preCommand); - if (nex == ex) - throw; + return result; + } + catch (Exception ex) + { + var nex = HandleException(ex, preCommand); + if (nex == ex) + throw; - throw nex; + throw nex; + } } - } - }); - } + }); + } - protected internal override DbDataReaderWithCommand UnsafeExecuteDataReader(SqlPreCommandSimple preCommand, CommandType commandType) - { - try + protected internal override DbDataReaderWithCommand UnsafeExecuteDataReader(SqlPreCommandSimple preCommand, CommandType commandType) { - var cmd = NewCommand(preCommand, null, commandType); + try + { + var cmd = NewCommand(preCommand, null, commandType); - var reader = cmd.ExecuteReader(); + var reader = cmd.ExecuteReader(); - return new DbDataReaderWithCommand(cmd, reader); - } - catch (Exception ex) - { - var nex = HandleException(ex, preCommand); - if (nex == ex) - throw; + return new DbDataReaderWithCommand(cmd, reader); + } + catch (Exception ex) + { + var nex = HandleException(ex, preCommand); + if (nex == ex) + throw; - throw nex; + throw nex; + } } - } - protected internal override async Task UnsafeExecuteDataReaderAsync(SqlPreCommandSimple preCommand, CommandType commandType, CancellationToken token) - { - try + protected internal override async Task UnsafeExecuteDataReaderAsync(SqlPreCommandSimple preCommand, CommandType commandType, CancellationToken token) { - var cmd = NewCommand(preCommand, null, commandType); + try + { + var cmd = NewCommand(preCommand, null, commandType); - var reader = await cmd.ExecuteReaderAsync(token); + var reader = await cmd.ExecuteReaderAsync(token); - return new DbDataReaderWithCommand(cmd, reader); - } - catch (Exception ex) - { - var nex = HandleException(ex, preCommand); - if (nex == ex) - throw; + return new DbDataReaderWithCommand(cmd, reader); + } + catch (Exception ex) + { + var nex = HandleException(ex, preCommand); + if (nex == ex) + throw; - throw nex; + throw nex; + } } - } - public Exception HandleException(Exception ex, SqlPreCommandSimple command) - { - var nex = ReplaceException(ex, command); - nex.Data["Sql"] = command.sp_executesql(); - return nex; - } + public Exception HandleException(Exception ex, SqlPreCommandSimple command) + { + var nex = ReplaceException(ex, command); + nex.Data["Sql"] = command.sp_executesql(); + return nex; + } - Exception ReplaceException(Exception ex, SqlPreCommandSimple command) - { - //if (ex is Npgsql.PostgresException se) - //{ - // switch (se.Number) - // { - // case -2: return new TimeoutException(ex.Message, ex); - // case 2601: return new UniqueKeyException(ex); - // case 547: return new ForeignKeyException(ex); - // default: return ex; - // } - //} - - //if (ex is SqlTypeException ste && ex.Message.Contains("DateTime")) - //{ - // var mins = command.Parameters.Where(a => DateTime.MinValue.Equals(a.Value)); - - // if (mins.Any()) - // { - // return new ArgumentOutOfRangeException("{0} {1} not initialized and equal to DateTime.MinValue".FormatWith( - // mins.CommaAnd(a => a.ParameterName), - // mins.Count() == 1 ? "is" : "are"), ex); - // } - //} - - return ex; - } + Exception ReplaceException(Exception ex, SqlPreCommandSimple command) + { + //if (ex is Npgsql.PostgresException se) + //{ + // switch (se.Number) + // { + // case -2: return new TimeoutException(ex.Message, ex); + // case 2601: return new UniqueKeyException(ex); + // case 547: return new ForeignKeyException(ex); + // default: return ex; + // } + //} + + //if (ex is SqlTypeException ste && ex.Message.Contains("DateTime")) + //{ + // var mins = command.Parameters.Where(a => DateTime.MinValue.Equals(a.Value)); + + // if (mins.Any()) + // { + // return new ArgumentOutOfRangeException("{0} {1} not initialized and equal to DateTime.MinValue".FormatWith( + // mins.CommaAnd(a => a.ParameterName), + // mins.Count() == 1 ? "is" : "are"), ex); + // } + //} + + return ex; + } - public override string ToString() => $"PostgreSqlConnector({PostgresVersion}, Database: {this.DatabaseName()}, DataSource: {this.DataSourceName()})"; + public override string ToString() => $"PostgreSqlConnector({PostgresVersion}, Database: {this.DatabaseName()}, DataSource: {this.DataSourceName()})"; - public override bool HasTables() - { - return (from ns in Database.View() - where !ns.IsInternal() - from t in ns.Tables() - select t).Any(); + public override bool HasTables() + { + return (from ns in Database.View() + where !ns.IsInternal() + from t in ns.Tables() + select t).Any(); + } } -} -public static class PostgreSqlConnectorScripts -{ - public static SqlPreCommandSimple RemoveAllScript(DatabaseName? databaseName) + public static class PostgreSqlConnectorScripts { - if (databaseName != null) - throw new NotSupportedException(); + public static SqlPreCommandSimple RemoveAllScript(DatabaseName? databaseName) + { + if (databaseName != null) + throw new NotSupportedException(); - return new SqlPreCommandSimple(@"-- Copyright © 2019 + return new SqlPreCommandSimple(@"-- Copyright © 2019 -- mirabilos -- -- Provided that these terms and disclaimer and all copyright notices @@ -392,161 +392,170 @@ public static SqlPreCommandSimple RemoveAllScript(DatabaseName? databaseName) DO $$ DECLARE - r RECORD; + r RECORD; BEGIN - -- triggers - FOR r IN (SELECT pns.nspname, pc.relname, pt.tgname - FROM pg_trigger pt, pg_class pc, pg_namespace pns - WHERE pns.oid=pc.relnamespace AND pc.oid=pt.tgrelid - AND pns.nspname NOT IN ('information_schema', 'pg_catalog', 'pg_toast') - AND pt.tgisinternal=false - ) LOOP - EXECUTE format('DROP TRIGGER %I ON %I.%I;', - r.tgname, r.nspname, r.relname); - END LOOP; - -- constraints #1: foreign key - FOR r IN (SELECT pns.nspname, pc.relname, pcon.conname - FROM pg_constraint pcon, pg_class pc, pg_namespace pns - WHERE pns.oid=pc.relnamespace AND pc.oid=pcon.conrelid - AND pns.nspname NOT IN ('information_schema', 'pg_catalog', 'pg_toast') - AND pcon.contype='f' - ) LOOP - EXECUTE format('ALTER TABLE ONLY %I.%I DROP CONSTRAINT %I;', - r.nspname, r.relname, r.conname); - END LOOP; - -- constraints #2: the rest - FOR r IN (SELECT pns.nspname, pc.relname, pcon.conname - FROM pg_constraint pcon, pg_class pc, pg_namespace pns - WHERE pns.oid=pc.relnamespace AND pc.oid=pcon.conrelid - AND pns.nspname NOT IN ('information_schema', 'pg_catalog', 'pg_toast') - AND pcon.contype<>'f' - ) LOOP - EXECUTE format('ALTER TABLE ONLY %I.%I DROP CONSTRAINT %I;', - r.nspname, r.relname, r.conname); - END LOOP; - -- indicēs - FOR r IN (SELECT pns.nspname, pc.relname - FROM pg_class pc, pg_namespace pns - WHERE pns.oid=pc.relnamespace - AND pns.nspname NOT IN ('information_schema', 'pg_catalog', 'pg_toast') - AND pc.relkind='i' - ) LOOP - EXECUTE format('DROP INDEX %I.%I;', - r.nspname, r.relname); - END LOOP; - -- normal and materialised views - FOR r IN (SELECT pns.nspname, pc.relname - FROM pg_class pc, pg_namespace pns - WHERE pns.oid=pc.relnamespace - AND pns.nspname NOT IN ('information_schema', 'pg_catalog', 'pg_toast') - AND pc.relname NOT LIKE 'pg_%' - AND pc.relkind IN ('v', 'm') - ) LOOP - EXECUTE format('DROP VIEW %I.%I;', - r.nspname, r.relname); - END LOOP; - -- tables - FOR r IN (SELECT pns.nspname, pc.relname - FROM pg_class pc, pg_namespace pns - WHERE pns.oid=pc.relnamespace - AND pns.nspname NOT IN ('information_schema', 'pg_catalog', 'pg_toast') - AND pc.relkind='r' - ) LOOP - EXECUTE format('DROP TABLE %I.%I;', - r.nspname, r.relname); - END LOOP; - -- sequences - FOR r IN (SELECT pns.nspname, pc.relname - FROM pg_class pc, pg_namespace pns - WHERE pns.oid=pc.relnamespace - AND pns.nspname NOT IN ('information_schema', 'pg_catalog', 'pg_toast') - AND pc.relkind='S' - ) LOOP - EXECUTE format('DROP SEQUENCE %I.%I;', - r.nspname, r.relname); - END LOOP; - -- extensions (see below), only if necessary - FOR r IN (SELECT pns.nspname, pe.extname - FROM pg_extension pe, pg_namespace pns - WHERE pns.oid=pe.extnamespace - AND pns.nspname NOT IN ('information_schema', 'pg_catalog', 'pg_toast') - ) LOOP - EXECUTE format('DROP EXTENSION %I;', r.extname); - END LOOP; - -- functions / procedures - FOR r IN (SELECT pns.nspname, pp.proname, pp.oid - FROM pg_proc pp, pg_namespace pns - WHERE pns.oid=pp.pronamespace - AND pns.nspname NOT IN ('information_schema', 'pg_catalog', 'pg_toast') - ) LOOP - EXECUTE format('DROP FUNCTION %I.%I(%s);', - r.nspname, r.proname, - pg_get_function_identity_arguments(r.oid)); - END LOOP; - -- nōn-default schemata we own; assume to be run by a not-superuser - FOR r IN (SELECT pns.nspname - FROM pg_namespace pns, pg_roles pr - WHERE pr.oid=pns.nspowner - AND pns.nspname NOT IN ('information_schema', 'pg_catalog', 'pg_toast', 'public') - AND pr.rolname=current_user - ) LOOP - EXECUTE format('DROP SCHEMA %I;', r.nspname); - END LOOP; - -- voilà - RAISE NOTICE 'Database cleared!'; + -- triggers + FOR r IN (SELECT pns.nspname, pc.relname, pt.tgname + FROM pg_trigger pt, pg_class pc, pg_namespace pns + WHERE pns.oid=pc.relnamespace AND pc.oid=pt.tgrelid + AND pns.nspname NOT IN ('information_schema', 'pg_catalog', 'pg_toast') + AND pt.tgisinternal=false + ) LOOP + EXECUTE format('DROP TRIGGER %I ON %I.%I;', + r.tgname, r.nspname, r.relname); + END LOOP; + -- constraints #1: foreign key + FOR r IN (SELECT pns.nspname, pc.relname, pcon.conname + FROM pg_constraint pcon, pg_class pc, pg_namespace pns + WHERE pns.oid=pc.relnamespace AND pc.oid=pcon.conrelid + AND pns.nspname NOT IN ('information_schema', 'pg_catalog', 'pg_toast') + AND pcon.contype='f' + ) LOOP + EXECUTE format('ALTER TABLE ONLY %I.%I DROP CONSTRAINT %I;', + r.nspname, r.relname, r.conname); + END LOOP; + -- constraints #2: the rest + FOR r IN (SELECT pns.nspname, pc.relname, pcon.conname + FROM pg_constraint pcon, pg_class pc, pg_namespace pns + WHERE pns.oid=pc.relnamespace AND pc.oid=pcon.conrelid + AND pns.nspname NOT IN ('information_schema', 'pg_catalog', 'pg_toast') + AND pcon.contype<>'f' + ) LOOP + EXECUTE format('ALTER TABLE ONLY %I.%I DROP CONSTRAINT %I;', + r.nspname, r.relname, r.conname); + END LOOP; + -- indicēs + FOR r IN (SELECT pns.nspname, pc.relname + FROM pg_class pc, pg_namespace pns + WHERE pns.oid=pc.relnamespace + AND pns.nspname NOT IN ('information_schema', 'pg_catalog', 'pg_toast') + AND pc.relkind='i' + ) LOOP + EXECUTE format('DROP INDEX %I.%I;', + r.nspname, r.relname); + END LOOP; + -- normal and materialised views + FOR r IN (SELECT pns.nspname, pc.relname + FROM pg_class pc, pg_namespace pns + WHERE pns.oid=pc.relnamespace + AND pns.nspname NOT IN ('information_schema', 'pg_catalog', 'pg_toast') + AND pc.relname NOT LIKE 'pg_%' + AND pc.relkind IN ('v', 'm') + ) LOOP + EXECUTE format('DROP VIEW %I.%I;', + r.nspname, r.relname); + END LOOP; + -- tables + FOR r IN (SELECT pns.nspname, pc.relname + FROM pg_class pc, pg_namespace pns + WHERE pns.oid=pc.relnamespace + AND pns.nspname NOT IN ('information_schema', 'pg_catalog', 'pg_toast') + AND pc.relkind='r' + ) LOOP + EXECUTE format('DROP TABLE %I.%I;', + r.nspname, r.relname); + END LOOP; + -- sequences + FOR r IN (SELECT pns.nspname, pc.relname + FROM pg_class pc, pg_namespace pns + WHERE pns.oid=pc.relnamespace + AND pns.nspname NOT IN ('information_schema', 'pg_catalog', 'pg_toast') + AND pc.relkind='S' + ) LOOP + EXECUTE format('DROP SEQUENCE %I.%I;', + r.nspname, r.relname); + END LOOP; + -- extensions (see below), only if necessary + FOR r IN (SELECT pns.nspname, pe.extname + FROM pg_extension pe, pg_namespace pns + WHERE pns.oid=pe.extnamespace + AND pns.nspname NOT IN ('information_schema', 'pg_catalog', 'pg_toast') + ) LOOP + EXECUTE format('DROP EXTENSION %I;', r.extname); + END LOOP; + -- functions / procedures + FOR r IN (SELECT pns.nspname, pp.proname, pp.oid + FROM pg_proc pp, pg_namespace pns + WHERE pns.oid=pp.pronamespace + AND pns.nspname NOT IN ('information_schema', 'pg_catalog', 'pg_toast') + ) LOOP + EXECUTE format('DROP FUNCTION %I.%I(%s);', + r.nspname, r.proname, + pg_get_function_identity_arguments(r.oid)); + END LOOP; + -- nōn-default schemata we own; assume to be run by a not-superuser + FOR r IN (SELECT pns.nspname + FROM pg_namespace pns, pg_roles pr + WHERE pr.oid=pns.nspowner + AND pns.nspname NOT IN ('information_schema', 'pg_catalog', 'pg_toast', 'public') + AND pr.rolname=current_user + ) LOOP + EXECUTE format('DROP SCHEMA %I;', r.nspname); + END LOOP; + -- voilà + RAISE NOTICE 'Database cleared!'; END; $$;"); + } } -} -public class PostgreSqlParameterBuilder : ParameterBuilder -{ - public override DbParameter CreateParameter(string parameterName, AbstractDbType dbType, string? udtTypeName, bool nullable, object? value) + public class PostgreSqlParameterBuilder : ParameterBuilder { - if (dbType.IsDate()) + public override DbParameter CreateParameter(string parameterName, AbstractDbType dbType, string? udtTypeName, bool nullable, object? value) { - if (value is DateTime dt) - AssertDateTime(dt); - } + if (dbType.IsDate()) + { + if (value is DateTime dt) + AssertDateTime(dt); + } - var result = new Npgsql.NpgsqlParameter(parameterName, value ?? DBNull.Value) - { - IsNullable = nullable - }; + var result = new Npgsql.NpgsqlParameter(parameterName, value ?? DBNull.Value) + { + IsNullable = nullable + }; - result.NpgsqlDbType = dbType.PostgreSql; - if (udtTypeName != null) - result.DataTypeName = udtTypeName; + result.NpgsqlDbType = dbType.PostgreSql; + if (udtTypeName != null) + result.DataTypeName = udtTypeName; - return result; - } + return result; + } - public override MemberInitExpression ParameterFactory(Expression parameterName, AbstractDbType dbType, string? udtTypeName, bool nullable, Expression value) - { - Expression valueExpr = Expression.Convert( - !dbType.IsDate() ? value : - value.Type.UnNullify() == typeof(DateTime) ? Expression.Call(miAsserDateTime, Expression.Convert(value, typeof(DateTime?))) : + public override MemberInitExpression ParameterFactory(Expression parameterName, AbstractDbType dbType, int? size, byte? precision, byte? scale, string? udtTypeName, bool nullable, Expression value) + { + Expression valueExpr = Expression.Convert( + !dbType.IsDate() ? value : + value.Type.UnNullify() == typeof(DateTime) ? Expression.Call(miAsserDateTime, Expression.Convert(value, typeof(DateTime?))) : value.Type.UnNullify() == typeof(DateOnly) ? Expression.Call(miToDateTimeKind, Expression.Convert(value, typeof(DateOnly?)), Expression.Constant(Schema.Current.DateTimeKind)) : - value, - typeof(object)); + value, + typeof(object)); - if (nullable) - valueExpr = Expression.Condition(Expression.Equal(value, Expression.Constant(null, value.Type)), - Expression.Constant(DBNull.Value, typeof(object)), - valueExpr); + if (nullable) + valueExpr = Expression.Condition(Expression.Equal(value, Expression.Constant(null, value.Type)), + Expression.Constant(DBNull.Value, typeof(object)), + valueExpr); - NewExpression newExpr = Expression.New(typeof(NpgsqlParameter).GetConstructor(new[] { typeof(string), typeof(object) })!, parameterName, valueExpr); + NewExpression newExpr = Expression.New(typeof(NpgsqlParameter).GetConstructor(new[] { typeof(string), typeof(object) })!, parameterName, valueExpr); - List mb = new List() - { - Expression.Bind(typeof(NpgsqlParameter).GetProperty(nameof(NpgsqlParameter.IsNullable))!, Expression.Constant(nullable)), - Expression.Bind(typeof(NpgsqlParameter).GetProperty(nameof(NpgsqlParameter.NpgsqlDbType))!, Expression.Constant(dbType.PostgreSql)), - }; + List mb = new List() + { + Expression.Bind(typeof(NpgsqlParameter).GetProperty(nameof(NpgsqlParameter.IsNullable))!, Expression.Constant(nullable)), + Expression.Bind(typeof(NpgsqlParameter).GetProperty(nameof(NpgsqlParameter.NpgsqlDbType))!, Expression.Constant(dbType.PostgreSql)), + }; - if (udtTypeName != null) - mb.Add(Expression.Bind(typeof(NpgsqlParameter).GetProperty(nameof(NpgsqlParameter.DataTypeName))!, Expression.Constant(udtTypeName))); + if (size != null) + mb.Add(Expression.Bind(typeof(NpgsqlParameter).GetProperty(nameof(NpgsqlParameter.Size))!, Expression.Constant(size))); - return Expression.MemberInit(newExpr, mb); + if (precision != null) + mb.Add(Expression.Bind(typeof(NpgsqlParameter).GetProperty(nameof(NpgsqlParameter.Precision))!, Expression.Constant(precision))); + + if (scale != null) + mb.Add(Expression.Bind(typeof(NpgsqlParameter).GetProperty(nameof(NpgsqlParameter.Scale))!, Expression.Constant(scale))); + + if (udtTypeName != null) + mb.Add(Expression.Bind(typeof(NpgsqlParameter).GetProperty(nameof(NpgsqlParameter.DataTypeName))!, Expression.Constant(udtTypeName))); + + return Expression.MemberInit(newExpr, mb); + } } -} diff --git a/Signum.Engine/Connection/SqlServerConnector.cs b/Signum.Engine/Connection/SqlServerConnector.cs index f0a1e544cf..e6fdca1c72 100644 --- a/Signum.Engine/Connection/SqlServerConnector.cs +++ b/Signum.Engine/Connection/SqlServerConnector.cs @@ -8,237 +8,182 @@ namespace Signum.Engine; -public enum SqlServerVersion -{ - SqlServer2005, - SqlServer2008, - SqlServer2012, - SqlServer2014, - SqlServer2016, - SqlServer2017, - SqlServer2019, - - AzureSQL, -} - -public static class SqlServerVersionDetector -{ - public enum EngineEdition + public enum SqlServerVersion { - Personal = 1, - Standard = 2, - Enterprise = 3, - Express = 4, - Azure = 5, + SqlServer2005, + SqlServer2008, + SqlServer2012, + SqlServer2014, + SqlServer2016, + SqlServer2017, + SqlServer2019, + + AzureSQL, } - public static SqlServerVersion? Detect(string connectionString) + public static class SqlServerVersionDetector { - return SqlServerRetry.Retry(() => + public enum EngineEdition { - using (var con = new SqlConnection(connectionString)) + Personal = 1, + Standard = 2, + Enterprise = 3, + Express = 4, + Azure = 5, + } + + public static SqlServerVersion? Detect(string connectionString) + { + return SqlServerRetry.Retry(() => { - con.Open(); - var sql = -@"SELECT -SERVERPROPERTY ('ProductVersion') as ProductVersion, -SERVERPROPERTY('ProductLevel') as ProductLevel, -SERVERPROPERTY('Edition') as Edition, -SERVERPROPERTY('EngineEdition') as EngineEdition"; - - using (var cmd = new SqlCommand(sql, con)) + using (var con = new SqlConnection(connectionString)) { - var da = new SqlDataAdapter(cmd); + con.Open(); + var sql = + @"SELECT + SERVERPROPERTY ('ProductVersion') as ProductVersion, + SERVERPROPERTY('ProductLevel') as ProductLevel, + SERVERPROPERTY('Edition') as Edition, + SERVERPROPERTY('EngineEdition') as EngineEdition"; + + using (var cmd = new SqlCommand(sql, con)) + { + var da = new SqlDataAdapter(cmd); - var result = new DataTable(); - da.Fill(result); + var result = new DataTable(); + da.Fill(result); - if ((int)result.Rows[0]["EngineEdition"] == (int)EngineEdition.Azure) - return SqlServerVersion.AzureSQL; + if ((int)result.Rows[0]["EngineEdition"] == (int)EngineEdition.Azure) + return SqlServerVersion.AzureSQL; - var version = (string)result.Rows[0]["ProductVersion"]; + var version = (string)result.Rows[0]["ProductVersion"]; - return version.Before(".") switch - { - "8" => throw new InvalidOperationException("SQL Server 2000 is not supported"), - "9" => SqlServerVersion.SqlServer2005, - "10" => SqlServerVersion.SqlServer2008, - "11" => SqlServerVersion.SqlServer2012, - "12" => SqlServerVersion.SqlServer2014, - "13" => SqlServerVersion.SqlServer2016, - "14" => SqlServerVersion.SqlServer2017, - "15" => SqlServerVersion.SqlServer2019, - _ => (SqlServerVersion?)null, - }; + return version.Before(".") switch + { + "8" => throw new InvalidOperationException("SQL Server 2000 is not supported"), + "9" => SqlServerVersion.SqlServer2005, + "10" => SqlServerVersion.SqlServer2008, + "11" => SqlServerVersion.SqlServer2012, + "12" => SqlServerVersion.SqlServer2014, + "13" => SqlServerVersion.SqlServer2016, + "14" => SqlServerVersion.SqlServer2017, + "15" => SqlServerVersion.SqlServer2019, + _ => (SqlServerVersion?)null, + }; + } } - } - }); + }); + } } -} - -public class SqlServerConnector : Connector -{ - public ResetLazy> DateFirstLazy = new ResetLazy>(() => Tuple.Create((byte)Executor.ExecuteScalar("SELECT @@DATEFIRST")!)); - public byte DateFirst => DateFirstLazy.Value.Item1; - - public SqlServerVersion Version { get; set; } - public SqlServerConnector(string connectionString, Schema schema, SqlServerVersion version) : base(schema) + public class SqlServerConnector : Connector { - this.ConnectionString = connectionString; - this.ParameterBuilder = new SqlParameterBuilder(); + public ResetLazy> DateFirstLazy = new ResetLazy>(() => Tuple.Create((byte)Executor.ExecuteScalar("SELECT @@DATEFIRST")!)); + public byte DateFirst => DateFirstLazy.Value.Item1; - this.Version = version; - } + public SqlServerVersion Version { get; set; } - public int? CommandTimeout { get; set; } = null; + public SqlServerConnector(string connectionString, Schema schema, SqlServerVersion version) : base(schema) + { + this.ConnectionString = connectionString; + this.ParameterBuilder = new SqlParameterBuilder(); - public string ConnectionString { get; set; } + this.Version = version; + } - public override bool SupportsScalarSubquery { get { return true; } } - public override bool SupportsScalarSubqueryInAggregates { get { return false; } } + public int? CommandTimeout { get; set; } = null; - T EnsureConnectionRetry(Func action) - { - if (Transaction.HasTransaction) - return action(null); + public string ConnectionString { get; set; } - return SqlServerRetry.Retry(() => - { - using (var con = new SqlConnection(this.ConnectionString)) - { - con.Open(); + public override bool SupportsScalarSubquery { get { return true; } } + public override bool SupportsScalarSubqueryInAggregates { get { return false; } } - return action(con); - } - }); - } + T EnsureConnectionRetry(Func action) + { + if (Transaction.HasTransaction) + return action(null); - SqlCommand NewCommand(SqlPreCommandSimple preCommand, SqlConnection? overridenConnection, CommandType commandType) - { - var cmd = new SqlCommand { CommandType = commandType }; + return SqlServerRetry.Retry(() => + { + using (var con = new SqlConnection(this.ConnectionString)) + { + con.Open(); - int? timeout = Connector.ScopeTimeout ?? CommandTimeout; - if (timeout.HasValue) - cmd.CommandTimeout = timeout.Value; + return action(con); + } + }); + } - if (overridenConnection != null) - cmd.Connection = overridenConnection; - else + SqlCommand NewCommand(SqlPreCommandSimple preCommand, SqlConnection? overridenConnection, CommandType commandType) { - cmd.Connection = (SqlConnection)Transaction.CurrentConnection!; - cmd.Transaction = (SqlTransaction)Transaction.CurrentTransaccion!; - } + var cmd = new SqlCommand { CommandType = commandType }; - cmd.CommandText = preCommand.Sql; + int? timeout = Connector.ScopeTimeout ?? CommandTimeout; + if (timeout.HasValue) + cmd.CommandTimeout = timeout.Value; - if (preCommand.Parameters != null) - { - foreach (SqlParameter param in preCommand.Parameters) + if (overridenConnection != null) + cmd.Connection = overridenConnection; + else { - cmd.Parameters.Add(CloneParameter(param)); + cmd.Connection = (SqlConnection)Transaction.CurrentConnection!; + cmd.Transaction = (SqlTransaction)Transaction.CurrentTransaccion!; } - } - - Log(preCommand); - return cmd; - } + cmd.CommandText = preCommand.Sql; - protected internal override object? ExecuteScalar(SqlPreCommandSimple preCommand, CommandType commandType) - { - return EnsureConnectionRetry(con => - { - using (SqlCommand cmd = NewCommand(preCommand, con, commandType)) - using (HeavyProfiler.Log("SQL", () => preCommand.sp_executesql())) + if (preCommand.Parameters != null) { - try + foreach (SqlParameter param in preCommand.Parameters) { - object result = cmd.ExecuteScalar(); - - if (result == null || result == DBNull.Value) - return null; - - return result; - } - catch (Exception ex) - { - var nex = HandleException(ex, preCommand); - if (nex == ex) - throw; - - throw nex; + cmd.Parameters.Add(CloneParameter(param)); } } - }); - } - protected internal override int ExecuteNonQuery(SqlPreCommandSimple preCommand, CommandType commandType) - { - return EnsureConnectionRetry(con => + Log(preCommand); + + return cmd; + } + + protected internal override object? ExecuteScalar(SqlPreCommandSimple preCommand, CommandType commandType) { - using (SqlCommand cmd = NewCommand(preCommand, con, commandType)) - using (HeavyProfiler.Log("SQL", () => preCommand.sp_executesql())) + return EnsureConnectionRetry(con => { - try - { - int result = cmd.ExecuteNonQuery(); - return result; - } - catch (Exception ex) + using (SqlCommand cmd = NewCommand(preCommand, con, commandType)) + using (HeavyProfiler.Log("SQL", () => preCommand.sp_executesql())) { - var nex = HandleException(ex, preCommand); - if (nex == ex) - throw; + try + { + object result = cmd.ExecuteScalar(); - throw nex; + if (result == null || result == DBNull.Value) + return null; + + return result; + } + catch (Exception ex) + { + var nex = HandleException(ex, preCommand); + if (nex == ex) + throw; + + throw nex; + } } - } - }); - } + }); + } - public void ExecuteDataReaderDependency(SqlPreCommandSimple preCommand, OnChangeEventHandler change, Action reconect, Action forEach, CommandType commandType) - { - bool reconected = false; - retry: - try + protected internal override int ExecuteNonQuery(SqlPreCommandSimple preCommand, CommandType commandType) { - EnsureConnectionRetry(con => + return EnsureConnectionRetry(con => { using (SqlCommand cmd = NewCommand(preCommand, con, commandType)) - using (HeavyProfiler.Log("SQL-Dependency")) using (HeavyProfiler.Log("SQL", () => preCommand.sp_executesql())) { try { - if (change != null) - { - var dep = new SqlDependency(cmd); - dep.OnChange += change; - } - - using (var reader = cmd.ExecuteReader()) - { - var fr = new FieldReader(reader); - int row = -1; - try - { - while (reader.Read()) - { - row++; - forEach(fr); - } - - return 0; - } - catch (Exception ex) - { - FieldReaderException fieldEx = fr.CreateFieldReaderException(ex); - fieldEx.Command = preCommand; - fieldEx.Row = row; - throw fieldEx; - } - } + int result = cmd.ExecuteNonQuery(); + return result; } catch (Exception ex) { @@ -251,307 +196,362 @@ public void ExecuteDataReaderDependency(SqlPreCommandSimple preCommand, OnChange } }); } - catch (InvalidOperationException ioe) + + public void ExecuteDataReaderDependency(SqlPreCommandSimple preCommand, OnChangeEventHandler change, Action reconect, Action forEach, CommandType commandType) { - if (ioe.Message.Contains("SqlDependency.Start()") && !reconected) + bool reconected = false; + retry: + try { - reconect(); + EnsureConnectionRetry(con => + { + using (SqlCommand cmd = NewCommand(preCommand, con, commandType)) + using (HeavyProfiler.Log("SQL-Dependency")) + using (HeavyProfiler.Log("SQL", () => preCommand.sp_executesql())) + { + try + { + if (change != null) + { + var dep = new SqlDependency(cmd); + dep.OnChange += change; + } - reconected = true; + using (var reader = cmd.ExecuteReader()) + { + var fr = new FieldReader(reader); + int row = -1; + try + { + while (reader.Read()) + { + row++; + forEach(fr); + } - goto retry; + return 0; + } + catch (Exception ex) + { + FieldReaderException fieldEx = fr.CreateFieldReaderException(ex); + fieldEx.Command = preCommand; + fieldEx.Row = row; + throw fieldEx; + } + } + } + catch (Exception ex) + { + var nex = HandleException(ex, preCommand); + if (nex == ex) + throw; + + throw nex; + } + } + }); } + catch (InvalidOperationException ioe) + { + if (ioe.Message.Contains("SqlDependency.Start()") && !reconected) + { + reconect(); + + reconected = true; + + goto retry; + } - throw; + throw; + } } - } - protected internal override DbDataReaderWithCommand UnsafeExecuteDataReader(SqlPreCommandSimple preCommand, CommandType commandType) - { - try + protected internal override DbDataReaderWithCommand UnsafeExecuteDataReader(SqlPreCommandSimple preCommand, CommandType commandType) { - var cmd = NewCommand(preCommand, null, commandType); + try + { + var cmd = NewCommand(preCommand, null, commandType); - var reader = cmd.ExecuteReader(); + var reader = cmd.ExecuteReader(); - return new DbDataReaderWithCommand(cmd, reader); - } - catch (Exception ex) - { - var nex = HandleException(ex, preCommand); - if (nex == ex) - throw; + return new DbDataReaderWithCommand(cmd, reader); + } + catch (Exception ex) + { + var nex = HandleException(ex, preCommand); + if (nex == ex) + throw; - throw nex; + throw nex; + } } - } - protected internal override async Task UnsafeExecuteDataReaderAsync(SqlPreCommandSimple preCommand, CommandType commandType, CancellationToken token) - { - try + protected internal override async Task UnsafeExecuteDataReaderAsync(SqlPreCommandSimple preCommand, CommandType commandType, CancellationToken token) { - var cmd = NewCommand(preCommand, null, commandType); + try + { + var cmd = NewCommand(preCommand, null, commandType); - var reader = await cmd.ExecuteReaderAsync(token); + var reader = await cmd.ExecuteReaderAsync(token); - return new DbDataReaderWithCommand(cmd, reader); - } - catch (Exception ex) - { - var nex = HandleException(ex, preCommand); - if (nex == ex) - throw; + return new DbDataReaderWithCommand(cmd, reader); + } + catch (Exception ex) + { + var nex = HandleException(ex, preCommand); + if (nex == ex) + throw; - throw nex; + throw nex; + } } - } - protected internal override DataTable ExecuteDataTable(SqlPreCommandSimple preCommand, CommandType commandType) - { - return EnsureConnectionRetry(con => + protected internal override DataTable ExecuteDataTable(SqlPreCommandSimple preCommand, CommandType commandType) { - using (SqlCommand cmd = NewCommand(preCommand, con, commandType)) - using (HeavyProfiler.Log("SQL", () => preCommand.sp_executesql())) + return EnsureConnectionRetry(con => { - try + using (SqlCommand cmd = NewCommand(preCommand, con, commandType)) + using (HeavyProfiler.Log("SQL", () => preCommand.sp_executesql())) { - var da = new SqlDataAdapter(cmd); + try + { + var da = new SqlDataAdapter(cmd); - var result = new DataTable(); - da.Fill(result); - return result; - } - catch (Exception ex) - { - var nex = HandleException(ex, preCommand); - if (nex == ex) - throw; + var result = new DataTable(); + da.Fill(result); + return result; + } + catch (Exception ex) + { + var nex = HandleException(ex, preCommand); + if (nex == ex) + throw; - throw nex; + throw nex; + } } - } - }); - } - - public static Exception HandleException(Exception ex, SqlPreCommandSimple command) - { - var nex = ReplaceException(ex, command); - nex.Data["Sql"] = command.sp_executesql(); - return nex; - } + }); + } - static Exception ReplaceException(Exception ex, SqlPreCommandSimple command) - { - if (ex is SqlException se) + public static Exception HandleException(Exception ex, SqlPreCommandSimple command) { - return se.Number switch - { - -2 => new TimeoutException(ex.Message, ex), - 2601 => new UniqueKeyException(ex), - 547 => new ForeignKeyException(ex), - _ => ex, - }; + var nex = ReplaceException(ex, command); + nex.Data["Sql"] = command.sp_executesql(); + return nex; } - if (ex is SqlTypeException ste && ex.Message.Contains("DateTime")) + static Exception ReplaceException(Exception ex, SqlPreCommandSimple command) { - var mins = command.Parameters!.Where(a => DateTime.MinValue.Equals(a.Value)); + if (ex is SqlException se) + { + return se.Number switch + { + -2 => new TimeoutException(ex.Message, ex), + 2601 => new UniqueKeyException(ex), + 547 => new ForeignKeyException(ex), + _ => ex, + }; + } - if (mins.Any()) + if (ex is SqlTypeException ste && ex.Message.Contains("DateTime")) { - return new ArgumentOutOfRangeException("{0} {1} not initialized and equal to DateTime.MinValue".FormatWith( - mins.CommaAnd(a => a.ParameterName), - mins.Count() == 1 ? "is" : "are"), ex); + var mins = command.Parameters!.Where(a => DateTime.MinValue.Equals(a.Value)); + + if (mins.Any()) + { + return new ArgumentOutOfRangeException("{0} {1} not initialized and equal to DateTime.MinValue".FormatWith( + mins.CommaAnd(a => a.ParameterName), + mins.Count() == 1 ? "is" : "are"), ex); + } } - } - return ex; - } + return ex; + } - protected internal override void BulkCopy(DataTable dt, List columns, ObjectName destinationTable, SqlBulkCopyOptions options, int? timeout) - { - EnsureConnectionRetry(con => + protected internal override void BulkCopy(DataTable dt, List columns, ObjectName destinationTable, SqlBulkCopyOptions options, int? timeout) { - using (var bulkCopy = new SqlBulkCopy( - options.HasFlag(SqlBulkCopyOptions.UseInternalTransaction) ? con : (SqlConnection)Transaction.CurrentConnection!, - options, - options.HasFlag(SqlBulkCopyOptions.UseInternalTransaction) ? null : (SqlTransaction)Transaction.CurrentTransaccion!)) - using (HeavyProfiler.Log("SQL", () => destinationTable.ToString() + " Rows:" + dt.Rows.Count)) + EnsureConnectionRetry(con => { - bulkCopy.BulkCopyTimeout = timeout ?? Connector.ScopeTimeout ?? this.CommandTimeout ?? bulkCopy.BulkCopyTimeout; + using (var bulkCopy = new SqlBulkCopy( + options.HasFlag(SqlBulkCopyOptions.UseInternalTransaction) ? con : (SqlConnection)Transaction.CurrentConnection!, + options, + options.HasFlag(SqlBulkCopyOptions.UseInternalTransaction) ? null : (SqlTransaction)Transaction.CurrentTransaccion!)) + using (HeavyProfiler.Log("SQL", () => destinationTable.ToString() + " Rows:" + dt.Rows.Count)) + { + bulkCopy.BulkCopyTimeout = timeout ?? Connector.ScopeTimeout ?? this.CommandTimeout ?? bulkCopy.BulkCopyTimeout; - foreach (var c in dt.Columns.Cast()) - bulkCopy.ColumnMappings.Add(new SqlBulkCopyColumnMapping(c.ColumnName, c.ColumnName)); + foreach (var c in dt.Columns.Cast()) + bulkCopy.ColumnMappings.Add(new SqlBulkCopyColumnMapping(c.ColumnName, c.ColumnName)); - bulkCopy.DestinationTableName = destinationTable.ToString(); - bulkCopy.WriteToServer(dt); - return 0; - } - }); - } + bulkCopy.DestinationTableName = destinationTable.ToString(); + bulkCopy.WriteToServer(dt); + return 0; + } + }); + } - public override string DatabaseName() - { - return new SqlConnection(ConnectionString).Database; - } + public override string DatabaseName() + { + return new SqlConnection(ConnectionString).Database; + } - public override string DataSourceName() - { - return new SqlConnection(ConnectionString).DataSource; - } + public override string DataSourceName() + { + return new SqlConnection(ConnectionString).DataSource; + } - public override void SaveTransactionPoint(DbTransaction transaction, string savePointName) - { - ((SqlTransaction)transaction).Save(savePointName); - } + public override void SaveTransactionPoint(DbTransaction transaction, string savePointName) + { + ((SqlTransaction)transaction).Save(savePointName); + } - public override void RollbackTransactionPoint(DbTransaction transaction, string savePointName) - { - ((SqlTransaction)transaction).Rollback(savePointName); - } + public override void RollbackTransactionPoint(DbTransaction transaction, string savePointName) + { + ((SqlTransaction)transaction).Rollback(savePointName); + } - public override string GetSqlDbType(DbParameter p) - { - return ((SqlParameter)p).SqlDbType.ToString().ToUpperInvariant(); - } + public override string GetSqlDbType(DbParameter p) + { + return ((SqlParameter)p).SqlDbType.ToString().ToUpperInvariant(); + } - public override DbParameter CloneParameter(DbParameter p) - { - return (SqlParameter)((ICloneable)p).Clone(); - } + public override DbParameter CloneParameter(DbParameter p) + { + return (SqlParameter)((ICloneable)p).Clone(); + } - public override DbConnection CreateConnection() - { - return new SqlConnection(ConnectionString); - } + public override DbConnection CreateConnection() + { + return new SqlConnection(ConnectionString); + } - public override ParameterBuilder ParameterBuilder { get; protected set; } + public override ParameterBuilder ParameterBuilder { get; protected set; } - public override void CleanDatabase(DatabaseName? database) - { - SqlConnectorScripts.RemoveAllScript(database).ExecuteLeaves(); - ShrinkDatabase(database?.ToString() ?? DatabaseName()); - } + public override void CleanDatabase(DatabaseName? database) + { + SqlConnectorScripts.RemoveAllScript(database).ExecuteLeaves(); + ShrinkDatabase(database?.ToString() ?? DatabaseName()); + } - public override bool AllowsMultipleQueries - { - get { return true; } - } + public override bool AllowsMultipleQueries + { + get { return true; } + } - public override Connector ForDatabase(Maps.DatabaseName? database) - { - if (database == null) - return this; + public override Connector ForDatabase(Maps.DatabaseName? database) + { + if (database == null) + return this; - return new SqlServerConnector(Replace(ConnectionString, database), this.Schema, this.Version); - } + return new SqlServerConnector(Replace(ConnectionString, database), this.Schema, this.Version); + } - private static string Replace(string connectionString, DatabaseName item) - { - var csb = new SqlConnectionStringBuilder(connectionString) + private static string Replace(string connectionString, DatabaseName item) { - InitialCatalog = item.Name - }; - return csb.ToString(); - } + var csb = new SqlConnectionStringBuilder(connectionString) + { + InitialCatalog = item.Name + }; + return csb.ToString(); + } - public override bool AllowsSetSnapshotIsolation => this.Version >= SqlServerVersion.SqlServer2008; + public override bool AllowsSetSnapshotIsolation => this.Version >= SqlServerVersion.SqlServer2008; - public override bool AllowsIndexWithWhere(string Where) - { - return Version > SqlServerVersion.SqlServer2005 && !ComplexWhereKeywords.Any(Where.Contains); - } + public override bool AllowsIndexWithWhere(string Where) + { + return Version > SqlServerVersion.SqlServer2005 && !ComplexWhereKeywords.Any(Where.Contains); + } - public override bool RequiresRetry => this.Version == SqlServerVersion.AzureSQL; + public override bool RequiresRetry => this.Version == SqlServerVersion.AzureSQL; - public static List ComplexWhereKeywords = new() { "OR" }; + public static List ComplexWhereKeywords = new() { "OR" }; - public SqlPreCommand ShrinkDatabase(string databaseName) - { - return new[] + public SqlPreCommand ShrinkDatabase(string databaseName) { - this.Version == SqlServerVersion.SqlServer2005 ? - new SqlPreCommandSimple("BACKUP LOG {0} WITH TRUNCATE_ONLY".FormatWith(databaseName)): - new [] - { - new SqlPreCommandSimple("ALTER DATABASE {0} SET RECOVERY SIMPLE WITH NO_WAIT".FormatWith(databaseName)), - new[]{ - new SqlPreCommandSimple("DECLARE @fileID BIGINT"), - new SqlPreCommandSimple("SET @fileID = (SELECT FILE_IDEX((SELECT TOP(1)name FROM sys.database_files WHERE type = 1)))"), - new SqlPreCommandSimple("DBCC SHRINKFILE(@fileID, 1)"), - }.Combine(Spacing.Simple)!.PlainSqlCommand(), - new SqlPreCommandSimple("ALTER DATABASE {0} SET RECOVERY FULL WITH NO_WAIT".FormatWith(databaseName)), - }.Combine(Spacing.Simple), - new SqlPreCommandSimple("DBCC SHRINKDATABASE ( {0} , TRUNCATEONLY )".FormatWith(databaseName)) - }.Combine(Spacing.Simple)!; - } + return new[] + { + this.Version == SqlServerVersion.SqlServer2005 ? + new SqlPreCommandSimple("BACKUP LOG {0} WITH TRUNCATE_ONLY".FormatWith(databaseName)): + new [] + { + new SqlPreCommandSimple("ALTER DATABASE {0} SET RECOVERY SIMPLE WITH NO_WAIT".FormatWith(databaseName)), + new[]{ + new SqlPreCommandSimple("DECLARE @fileID BIGINT"), + new SqlPreCommandSimple("SET @fileID = (SELECT FILE_IDEX((SELECT TOP(1)name FROM sys.database_files WHERE type = 1)))"), + new SqlPreCommandSimple("DBCC SHRINKFILE(@fileID, 1)"), + }.Combine(Spacing.Simple)!.PlainSqlCommand(), + new SqlPreCommandSimple("ALTER DATABASE {0} SET RECOVERY FULL WITH NO_WAIT".FormatWith(databaseName)), + }.Combine(Spacing.Simple), + new SqlPreCommandSimple("DBCC SHRINKDATABASE ( {0} , TRUNCATEONLY )".FormatWith(databaseName)) + }.Combine(Spacing.Simple)!; + } - public override bool AllowsConvertToDate - { - get { return Version >= SqlServerVersion.SqlServer2008; } - } + public override bool AllowsConvertToDate + { + get { return Version >= SqlServerVersion.SqlServer2008; } + } - public override bool AllowsConvertToTime - { - get { return Version >= SqlServerVersion.SqlServer2008; } - } + public override bool AllowsConvertToTime + { + get { return Version >= SqlServerVersion.SqlServer2008; } + } - public override bool SupportsSqlDependency - { - get { return Version != SqlServerVersion.AzureSQL && Version >= SqlServerVersion.SqlServer2008; } - } + public override bool SupportsSqlDependency + { + get { return Version != SqlServerVersion.AzureSQL && Version >= SqlServerVersion.SqlServer2008; } + } - public override bool SupportsFormat - { - get { return Version >= SqlServerVersion.SqlServer2012; } - } + public override bool SupportsFormat + { + get { return Version >= SqlServerVersion.SqlServer2012; } + } - public override bool SupportsTemporalTables - { - get { return Version >= SqlServerVersion.SqlServer2016; } - } + public override bool SupportsTemporalTables + { + get { return Version >= SqlServerVersion.SqlServer2016; } + } - public override int MaxNameLength => 128; + public override int MaxNameLength => 128; - public override string ToString() => $"SqlServerConnector({Version}, Database: {this.DatabaseName()}, DataSource: {this.DataSourceName()})"; + public override string ToString() => $"SqlServerConnector({Version}, Database: {this.DatabaseName()}, DataSource: {this.DataSourceName()})"; - public override bool HasTables() - { - return Database.View().Any(); + public override bool HasTables() + { + return Database.View().Any(); + } } -} -public class SqlParameterBuilder : ParameterBuilder -{ - public override DbParameter CreateParameter(string parameterName, AbstractDbType dbType, string? udtTypeName, bool nullable, object? value) + public class SqlParameterBuilder : ParameterBuilder { - if (dbType.IsDate()) + public override DbParameter CreateParameter(string parameterName, AbstractDbType dbType, string? udtTypeName, bool nullable, object? value) { - if (value is DateTime dt) - AssertDateTime(dt); + if (dbType.IsDate()) + { + if (value is DateTime dt) + AssertDateTime(dt); else if (value is DateOnly d) value = d.ToDateTime(); - } + } else if(dbType.IsTime()) { if (value is TimeOnly to) value = to.ToTimeSpan(); } - var result = new SqlParameter(parameterName, value ?? DBNull.Value) - { - IsNullable = nullable - }; + var result = new SqlParameter(parameterName, value ?? DBNull.Value) + { + IsNullable = nullable + }; - result.SqlDbType = dbType.SqlServer; - if (udtTypeName != null) - result.UdtTypeName = udtTypeName; + result.SqlDbType = dbType.SqlServer; + if (udtTypeName != null) + result.UdtTypeName = udtTypeName; - return result; - } + return result; + } - public override MemberInitExpression ParameterFactory(Expression parameterName, AbstractDbType dbType, string? udtTypeName, bool nullable, Expression value) - { + public override MemberInitExpression ParameterFactory(Expression parameterName, AbstractDbType dbType, int? size, byte? precision, byte? scale, string? udtTypeName, bool nullable, Expression value) + { var uType = value.Type.UnNullify(); var exp = @@ -563,30 +563,39 @@ public override MemberInitExpression ParameterFactory(Expression parameterName, Expression valueExpr = Expression.Convert(exp, typeof(object)); - if (nullable) - valueExpr = Expression.Condition(Expression.Equal(value, Expression.Constant(null, value.Type)), - Expression.Constant(DBNull.Value, typeof(object)), - valueExpr); + if (nullable) + valueExpr = Expression.Condition(Expression.Equal(value, Expression.Constant(null, value.Type)), + Expression.Constant(DBNull.Value, typeof(object)), + valueExpr); - NewExpression newExpr = Expression.New(typeof(SqlParameter).GetConstructor(new[] { typeof(string), typeof(object) })!, parameterName, valueExpr); + NewExpression newExpr = Expression.New(typeof(SqlParameter).GetConstructor(new[] { typeof(string), typeof(object) })!, parameterName, valueExpr); - List mb = new() - { - Expression.Bind(typeof(SqlParameter).GetProperty("IsNullable")!, Expression.Constant(nullable)), - Expression.Bind(typeof(SqlParameter).GetProperty("SqlDbType")!, Expression.Constant(dbType.SqlServer)), - }; + List mb = new() + { + Expression.Bind(typeof(SqlParameter).GetProperty(nameof(SqlParameter.IsNullable))!, Expression.Constant(nullable)), + Expression.Bind(typeof(SqlParameter).GetProperty(nameof(SqlParameter.SqlDbType))!, Expression.Constant(dbType.SqlServer)), + }; - if (udtTypeName != null) - mb.Add(Expression.Bind(typeof(SqlParameter).GetProperty("UdtTypeName")!, Expression.Constant(udtTypeName))); + if (size != null) + mb.Add(Expression.Bind(typeof(SqlParameter).GetProperty(nameof(SqlParameter.Size))!, Expression.Constant(size))); - return Expression.MemberInit(newExpr, mb); + if (precision != null) + mb.Add(Expression.Bind(typeof(SqlParameter).GetProperty(nameof(SqlParameter.Precision))!, Expression.Constant(precision))); + + if (scale != null) + mb.Add(Expression.Bind(typeof(SqlParameter).GetProperty(nameof(SqlParameter.Scale))!, Expression.Constant(scale))); + + if (udtTypeName != null) + mb.Add(Expression.Bind(typeof(SqlParameter).GetProperty(nameof(SqlParameter.UdtTypeName))!, Expression.Constant(udtTypeName))); + + return Expression.MemberInit(newExpr, mb); + } } -} -public static class SqlConnectorScripts -{ - public static readonly string RemoveAllConstraintsScript = + public static class SqlConnectorScripts + { + public static readonly string RemoveAllConstraintsScript = @"declare @schema nvarchar(128), @tbl nvarchar(128), @constraint nvarchar(128) DECLARE @sql nvarchar(255) @@ -596,17 +605,17 @@ from information_schema.table_constraints tc join information_schema.referential_constraints rc on rc.unique_constraint_name = tc.constraint_name join information_schema.constraint_column_usage cu on cu.constraint_name = rc.constraint_name open cur -fetch next from cur into @schema, @tbl, @constraint -while @@fetch_status <> -1 -begin - select @sql = 'ALTER TABLE [' + @schema + '].[' + @tbl + '] DROP CONSTRAINT [' + @constraint + '];' - exec sp_executesql @sql fetch next from cur into @schema, @tbl, @constraint -end + while @@fetch_status <> -1 + begin + select @sql = 'ALTER TABLE [' + @schema + '].[' + @tbl + '] DROP CONSTRAINT [' + @constraint + '];' + exec sp_executesql @sql + fetch next from cur into @schema, @tbl, @constraint + end close cur deallocate cur"; - public static readonly string RemoveAllTablesScript = + public static readonly string RemoveAllTablesScript = @"declare @schema nvarchar(128), @tbl nvarchar(128) DECLARE @sql nvarchar(255) @@ -614,17 +623,17 @@ DECLARE @sql nvarchar(255) select distinct table_schema, table_name from information_schema.tables where table_type = 'BASE TABLE' open cur -fetch next from cur into @schema, @tbl -while @@fetch_status <> -1 -begin - select @sql = 'DROP TABLE [' + @schema + '].[' + @tbl + '];' - exec sp_executesql @sql fetch next from cur into @schema, @tbl -end + while @@fetch_status <> -1 + begin + select @sql = 'DROP TABLE [' + @schema + '].[' + @tbl + '];' + exec sp_executesql @sql + fetch next from cur into @schema, @tbl + end close cur deallocate cur"; - public static readonly string RemoveAllViewsScript = + public static readonly string RemoveAllViewsScript = @"declare @schema nvarchar(128), @view nvarchar(128) DECLARE @sql nvarchar(255) @@ -633,17 +642,17 @@ DECLARE @sql nvarchar(255) from information_schema.tables where table_type = 'VIEW' and table_schema not in ({0}) open cur -fetch next from cur into @schema, @view -while @@fetch_status <> -1 -begin - select @sql = 'DROP VIEW [' + @schema + '].[' + @view + '];' - exec sp_executesql @sql fetch next from cur into @schema, @view -end + while @@fetch_status <> -1 + begin + select @sql = 'DROP VIEW [' + @schema + '].[' + @view + '];' + exec sp_executesql @sql + fetch next from cur into @schema, @view + end close cur deallocate cur"; - public static readonly string RemoveAllProceduresScript = + public static readonly string RemoveAllProceduresScript = @"declare @schema nvarchar(128), @proc nvarchar(128), @type nvarchar(128) DECLARE @sql nvarchar(255) @@ -651,17 +660,17 @@ DECLARE @sql nvarchar(255) select routine_schema, routine_name, routine_type from information_schema.routines open cur -fetch next from cur into @schema, @proc, @type -while @@fetch_status <> -1 -begin - select @sql = 'DROP '+ @type +' [' + @schema + '].[' + @proc + '];' - exec sp_executesql @sql fetch next from cur into @schema, @proc, @type -end + while @@fetch_status <> -1 + begin + select @sql = 'DROP '+ @type +' [' + @schema + '].[' + @proc + '];' + exec sp_executesql @sql + fetch next from cur into @schema, @proc, @type + end close cur deallocate cur"; - public static readonly string RemoveAllSchemasScript = + public static readonly string RemoveAllSchemasScript = @"declare @schema nvarchar(128) DECLARE @sql nvarchar(255) @@ -670,17 +679,17 @@ select schema_name from information_schema.schemata where schema_name not in ({0}) open cur -fetch next from cur into @schema -while @@fetch_status <> -1 -begin - select @sql = 'DROP SCHEMA [' + @schema + '];' - exec sp_executesql @sql fetch next from cur into @schema -end + while @@fetch_status <> -1 + begin + select @sql = 'DROP SCHEMA [' + @schema + '];' + exec sp_executesql @sql + fetch next from cur into @schema + end close cur deallocate cur"; - public static readonly string StopSystemVersioning = @"declare @schema nvarchar(128), @tbl nvarchar(128) + public static readonly string StopSystemVersioning = @"declare @schema nvarchar(128), @tbl nvarchar(128) DECLARE @sql nvarchar(255) declare cur cursor fast_forward for @@ -688,37 +697,37 @@ DECLARE @sql nvarchar(255) from sys.tables t join sys.schemas s on t.schema_id = s.schema_id where history_table_id is not null open cur -fetch next from cur into @schema, @tbl -while @@fetch_status <> -1 -begin - select @sql = 'ALTER TABLE [' + @schema + '].[' + @tbl + '] SET (SYSTEM_VERSIONING = OFF);' - exec sp_executesql @sql fetch next from cur into @schema, @tbl -end + while @@fetch_status <> -1 + begin + select @sql = 'ALTER TABLE [' + @schema + '].[' + @tbl + '] SET (SYSTEM_VERSIONING = OFF);' + exec sp_executesql @sql + fetch next from cur into @schema, @tbl + end close cur deallocate cur"; - public static SqlPreCommand RemoveAllScript(DatabaseName? databaseName) - { - var sqlBuilder = Connector.Current.SqlBuilder; - var systemSchemas = sqlBuilder.SystemSchemas.ToString(a => "'" + a + "'", ", "); - var systemSchemasExeptDbo = sqlBuilder.SystemSchemas.Where(s => s != "dbo").ToString(a => "'" + a + "'", ", "); - - return SqlPreCommand.Combine(Spacing.Double, - new SqlPreCommandSimple(Use(databaseName, RemoveAllProceduresScript)), - new SqlPreCommandSimple(Use(databaseName, RemoveAllViewsScript).FormatWith(systemSchemasExeptDbo)), - new SqlPreCommandSimple(Use(databaseName, RemoveAllConstraintsScript)), - Connector.Current.SupportsTemporalTables ? new SqlPreCommandSimple(Use(databaseName, StopSystemVersioning)) : null, - new SqlPreCommandSimple(Use(databaseName, RemoveAllTablesScript)), - new SqlPreCommandSimple(Use(databaseName, RemoveAllSchemasScript.FormatWith(systemSchemas))) - )!; - } + public static SqlPreCommand RemoveAllScript(DatabaseName? databaseName) + { + var sqlBuilder = Connector.Current.SqlBuilder; + var systemSchemas = sqlBuilder.SystemSchemas.ToString(a => "'" + a + "'", ", "); + var systemSchemasExeptDbo = sqlBuilder.SystemSchemas.Where(s => s != "dbo").ToString(a => "'" + a + "'", ", "); + + return SqlPreCommand.Combine(Spacing.Double, + new SqlPreCommandSimple(Use(databaseName, RemoveAllProceduresScript)), + new SqlPreCommandSimple(Use(databaseName, RemoveAllViewsScript).FormatWith(systemSchemasExeptDbo)), + new SqlPreCommandSimple(Use(databaseName, RemoveAllConstraintsScript)), + Connector.Current.SupportsTemporalTables ? new SqlPreCommandSimple(Use(databaseName, StopSystemVersioning)) : null, + new SqlPreCommandSimple(Use(databaseName, RemoveAllTablesScript)), + new SqlPreCommandSimple(Use(databaseName, RemoveAllSchemasScript.FormatWith(systemSchemas))) + )!; + } - static string Use(DatabaseName? databaseName, string script) - { - if (databaseName == null) - return script; + static string Use(DatabaseName? databaseName, string script) + { + if (databaseName == null) + return script; - return "use " + databaseName + "\r\n" + script; + return "use " + databaseName + "\r\n" + script; + } } -} diff --git a/Signum.Engine/Engine/SchemaSynchronizer.cs b/Signum.Engine/Engine/SchemaSynchronizer.cs index 0a4f83e665..7fc52e68e3 100644 --- a/Signum.Engine/Engine/SchemaSynchronizer.cs +++ b/Signum.Engine/Engine/SchemaSynchronizer.cs @@ -8,1413 +8,1419 @@ namespace Signum.Engine; -public static class SchemaSynchronizer -{ - public static Func DropSchema = s => !s.Name.Contains(@"\"); - - public static Action>? SimplifyDiffTables; - - public static SqlPreCommand? SynchronizeTablesScript(Replacements replacements) + public static class SchemaSynchronizer { - Schema s = Schema.Current; - - var sqlBuilder = Connector.Current.SqlBuilder; - - Dictionary modelTables = s.GetDatabaseTables().Where(t => !s.IsExternalDatabase(t.Name.Schema.Database)).ToDictionaryEx(a => a.Name.ToString(), "schema tables"); - var modelTablesHistory = modelTables.Values.Where(a => a.SystemVersioned != null).ToDictionaryEx(a => a.SystemVersioned!.TableName.ToString(), "history schema tables"); - HashSet modelSchemas = modelTables.Values.Select(a => a.Name.Schema).Where(a => !sqlBuilder.SystemSchemas.Contains(a.Name)).ToHashSet(); + public static Func DropSchema = s => !s.Name.Contains(@"\"); - Dictionary databaseTables = Schema.Current.Settings.IsPostgres ? - PostgresCatalogSchema.GetDatabaseDescription(Schema.Current.DatabaseNames()) : - SysTablesSchema.GetDatabaseDescription(s.DatabaseNames()); + public static Action>? SimplifyDiffTables; - var databaseTablesHistory = databaseTables.Extract((key, val) => val.TemporalType == SysTableTemporalType.HistoryTable); - HashSet databaseSchemas = Schema.Current.Settings.IsPostgres ? - PostgresCatalogSchema.GetSchemaNames(s.DatabaseNames()) : - SysTablesSchema.GetSchemaNames(s.DatabaseNames()); + public static SqlPreCommand? SynchronizeTablesScript(Replacements replacements) + { + Schema s = Schema.Current; - SimplifyDiffTables?.Invoke(databaseTables); + var sqlBuilder = Connector.Current.SqlBuilder; - replacements.AskForReplacements(databaseTables.Keys.ToHashSet(), modelTables.Keys.ToHashSet(), Replacements.KeyTables); + Dictionary modelTables = s.GetDatabaseTables().Where(t => !s.IsExternalDatabase(t.Name.Schema.Database)).ToDictionaryEx(a => a.Name.ToString(), "schema tables"); + var modelTablesHistory = modelTables.Values.Where(a => a.SystemVersioned != null).ToDictionaryEx(a => a.SystemVersioned!.TableName.ToString(), "history schema tables"); + HashSet modelSchemas = modelTables.Values.Select(a => a.Name.Schema).Where(a => !sqlBuilder.SystemSchemas.Contains(a.Name)).ToHashSet(); - databaseTables = replacements.ApplyReplacementsToOld(databaseTables, Replacements.KeyTables); + Dictionary databaseTables = Schema.Current.Settings.IsPostgres ? + PostgresCatalogSchema.GetDatabaseDescription(Schema.Current.DatabaseNames()) : + SysTablesSchema.GetDatabaseDescription(s.DatabaseNames()); + + var databaseTablesHistory = databaseTables.Extract((key, val) => val.TemporalType == SysTableTemporalType.HistoryTable); + HashSet databaseSchemas = Schema.Current.Settings.IsPostgres ? + PostgresCatalogSchema.GetSchemaNames(s.DatabaseNames()): + SysTablesSchema.GetSchemaNames(s.DatabaseNames()); - replacements.AskForReplacements(databaseTablesHistory.Keys.ToHashSet(), modelTablesHistory.Keys.ToHashSet(), Replacements.KeyTables); + SimplifyDiffTables?.Invoke(databaseTables); - databaseTablesHistory = replacements.ApplyReplacementsToOld(databaseTablesHistory, Replacements.KeyTables); + replacements.AskForReplacements(databaseTables.Keys.ToHashSet(), modelTables.Keys.ToHashSet(), Replacements.KeyTables); - Dictionary> modelIndices = modelTables.Values - .ToDictionary(t => t, t => t.GeneratAllIndexes().ToDictionaryEx(a => a.IndexName, "Indexes for {0}".FormatWith(t.Name))); + databaseTables = replacements.ApplyReplacementsToOld(databaseTables, Replacements.KeyTables); - //To --> From - Dictionary> preRenameColumnsList = new Dictionary>(); - HashSet primaryKeyTypeChanged = new HashSet(); + replacements.AskForReplacements(databaseTablesHistory.Keys.ToHashSet(), modelTablesHistory.Keys.ToHashSet(), Replacements.KeyTables); - //A -> A_temp + databaseTablesHistory = replacements.ApplyReplacementsToOld(databaseTablesHistory, Replacements.KeyTables); - modelTables.JoinDictionaryForeach(databaseTables, (tn, tab, diff) => - { - var key = Replacements.KeyColumnsForTable(tn); + Dictionary> modelIndices = modelTables.Values + .ToDictionary(t => t, t => t.GeneratAllIndexes().ToDictionaryEx(a => a.IndexName, "Indexes for {0}".FormatWith(t.Name))); - replacements.AskForReplacements( - diff.Columns.Keys.ToHashSet(), - tab.Columns.Keys.ToHashSet(), key); + //To --> From + Dictionary> preRenameColumnsList = new Dictionary>(); + HashSet primaryKeyTypeChanged = new HashSet(); - var incompatibleTypes = diff.Columns.JoinDictionary(tab.Columns, (cn, diff, col) => new { cn, diff, col }).Values.Where(a => !a.diff.CompatibleTypes(a.col) || a.diff.Identity != a.col.Identity).ToList(); + //A -> A_temp - foreach (var inc in incompatibleTypes.Where(kvp => kvp.col.Name == kvp.diff.Name)) + modelTables.JoinDictionaryForeach(databaseTables, (tn, tab, diff) => { - var newColName = inc.diff.Name + "_OLD"; - preRenameColumnsList.GetOrCreate(diff.Name).Add(inc.diff.Name, newColName); - inc.diff.Name = newColName; - } + var key = Replacements.KeyColumnsForTable(tn); - var diffPk = diff.Columns.Values.Where(a => a.PrimaryKey).Only(); - var pk = tab.PrimaryKey; + replacements.AskForReplacements( + diff.Columns.Keys.ToHashSet(), + tab.Columns.Keys.ToHashSet(), key); - if (diffPk != null && diffPk.CompatibleTypes(pk)) - primaryKeyTypeChanged.Add(tab); + var incompatibleTypes = diff.Columns.JoinDictionary(tab.Columns, (cn, diff, col) => new { cn, diff, col }).Values.Where(a => !a.diff.CompatibleTypes(a.col) || a.diff.Identity != a.col.Identity).ToList(); - diff.Columns = replacements.ApplyReplacementsToOld(diff.Columns, key); - diff.Indices = ApplyIndexAutoReplacements(diff, tab, modelIndices[tab]); - - if (diff.TemporalTableName != null) - { - var diffTemp = databaseTablesHistory.GetOrThrow(replacements.Apply(Replacements.KeyTables, diff.TemporalTableName.ToString())); - diffTemp.Columns = replacements.ApplyReplacementsToOld(diffTemp.Columns, key); - diffTemp.Indices = ApplyIndexAutoReplacements(diffTemp, tab, modelIndices[tab]); - } - - }); - - var tableReplacements = replacements.TryGetC(Replacements.KeyTables); - if (tableReplacements != null) - replacements[Replacements.KeyTablesInverse] = tableReplacements.Inverse(); + foreach (var inc in incompatibleTypes.Where(kvp => kvp.col.Name == kvp.diff.Name)) + { + var newColName = inc.diff.Name + "_OLD"; + preRenameColumnsList.GetOrCreate(diff.Name).Add(inc.diff.Name, newColName); + inc.diff.Name = newColName; + } - Func ChangeName = (ObjectName objectName) => - { - string name = replacements.Apply(Replacements.KeyTables, objectName.ToString()); + var diffPk = diff.Columns.Values.Where(a => a.PrimaryKey).Only(); + var pk = tab.PrimaryKey; - return modelTables.TryGetC(name)?.Name ?? objectName; - }; + if (diffPk != null && diffPk.CompatibleTypes(pk)) + primaryKeyTypeChanged.Add(tab); - using (replacements.WithReplacedDatabaseName()) - { - SqlPreCommand? preRenameColumns = preRenameColumnsList - .Select(kvp => kvp.Value.Select(kvp2 => sqlBuilder.RenameColumn(kvp.Key, kvp2.Key, kvp2.Value)).Combine(Spacing.Simple)) - .Combine(Spacing.Double); - - if (preRenameColumns != null) - preRenameColumns.GoAfter = true; - - SqlPreCommand? createSchemas = Synchronizer.SynchronizeScriptReplacing(replacements, "Schemas", Spacing.Double, - modelSchemas.ToDictionary(a => a.ToString()), - databaseSchemas.ToDictionary(a => a.ToString()), - createNew: (_, newSN) => sqlBuilder.CreateSchema(newSN), - removeOld: null, - mergeBoth: (_, newSN, oldSN) => newSN.Equals(oldSN) ? null : sqlBuilder.CreateSchema(newSN) - ); + diff.Columns = replacements.ApplyReplacementsToOld(diff.Columns, key); + diff.Indices = ApplyIndexAutoReplacements(diff, tab, modelIndices[tab]); - //use database without replacements to just remove indexes - SqlPreCommand? dropStatistics = - Synchronizer.SynchronizeScript(Spacing.Double, modelTables, databaseTables, - createNew: null, - removeOld: (tn, dif) => sqlBuilder.DropStatistics(tn, dif.Stats), - mergeBoth: (tn, tab, dif) => + if(diff.TemporalTableName != null) { - var removedColums = dif.Columns.Keys.Except(tab.Columns.Keys).ToHashSet(); + var diffTemp = databaseTablesHistory.GetOrThrow(replacements.Apply(Replacements.KeyTables, diff.TemporalTableName.ToString())); + diffTemp.Columns = replacements.ApplyReplacementsToOld(diffTemp.Columns, key); + diffTemp.Indices = ApplyIndexAutoReplacements(diffTemp, tab, modelIndices[tab]); + } - return sqlBuilder.DropStatistics(tn, dif.Stats.Where(a => a.Columns.Any(removedColums.Contains)).ToList()); - }); + }); + var tableReplacements = replacements.TryGetC(Replacements.KeyTables); + if (tableReplacements != null) + replacements[Replacements.KeyTablesInverse] = tableReplacements.Inverse(); - SqlPreCommand? dropIndices = - Synchronizer.SynchronizeScript(Spacing.Double, modelTables, databaseTables, - createNew: null, - removeOld: (tn, dif) => dif.Indices.Values.Where(ix => !ix.IsPrimary).Select(ix => sqlBuilder.DropIndex(dif.Name, ix)).Combine(Spacing.Simple), - mergeBoth: (tn, tab, dif) => - { - Dictionary modelIxs = modelIndices[tab]; + Func ChangeName = (ObjectName objectName) => + { + string name = replacements.Apply(Replacements.KeyTables, objectName.ToString()); - var removedColums = dif.Columns.Keys.Except(tab.Columns.Keys).ToHashSet(); + return modelTables.TryGetC(name)?.Name ?? objectName; + }; - var changes = Synchronizer.SynchronizeScript(Spacing.Simple, - modelIxs.Where(kvp => !(kvp.Value is PrimaryKeyIndex)).ToDictionary(), - dif.Indices.Where(kvp => !kvp.Value.IsPrimary).ToDictionary(), - createNew: null, - removeOld: (i, dix) => dix.Columns.Any(c => removedColums.Contains(c.ColumnName)) || dix.IsControlledIndex ? sqlBuilder.DropIndex(dif.Name, dix) : null, - mergeBoth: (i, mix, dix) => !dix.IndexEquals(dif, mix) ? sqlBuilder.DropIndex(dif.Name, dix) : null - ); + using (replacements.WithReplacedDatabaseName()) + { + SqlPreCommand? preRenameColumns = preRenameColumnsList + .Select(kvp => kvp.Value.Select(kvp2 => sqlBuilder.RenameColumn(kvp.Key, kvp2.Key, kvp2.Value)).Combine(Spacing.Simple)) + .Combine(Spacing.Double); + + if (preRenameColumns != null) + preRenameColumns.GoAfter = true; + + SqlPreCommand? createSchemas = Synchronizer.SynchronizeScriptReplacing(replacements, "Schemas", Spacing.Double, + modelSchemas.ToDictionary(a => a.ToString()), + databaseSchemas.ToDictionary(a => a.ToString()), + createNew: (_, newSN) => sqlBuilder.CreateSchema(newSN), + removeOld: null, + mergeBoth: (_, newSN, oldSN) => newSN.Equals(oldSN) ? null : sqlBuilder.CreateSchema(newSN) + ); + + //use database without replacements to just remove indexes + SqlPreCommand? dropStatistics = + Synchronizer.SynchronizeScript(Spacing.Double, modelTables, databaseTables, + createNew: null, + removeOld: (tn, dif) => sqlBuilder.DropStatistics(tn, dif.Stats), + mergeBoth: (tn, tab, dif) => + { + var removedColums = dif.Columns.Keys.Except(tab.Columns.Keys).ToHashSet(); - return changes; - }); + return sqlBuilder.DropStatistics(tn, dif.Stats.Where(a => a.Columns.Any(removedColums.Contains)).ToList()); + }); - SqlPreCommand? dropIndicesHistory = - Synchronizer.SynchronizeScript(Spacing.Double, modelTablesHistory, databaseTablesHistory, - createNew: null, - removeOld: (tn, dif) => dif.Indices.Values.Where(ix => !ix.IsPrimary).Select(ix => sqlBuilder.DropIndex(dif.Name, ix)).Combine(Spacing.Simple), - mergeBoth: (tn, tab, dif) => - { - Dictionary modelIxs = modelIndices[tab]; + + SqlPreCommand? dropIndices = + Synchronizer.SynchronizeScript(Spacing.Double, modelTables, databaseTables, + createNew: null, + removeOld: (tn, dif) => dif.Indices.Values.Where(ix => !ix.IsPrimary).Select(ix => sqlBuilder.DropIndex(dif.Name, ix)).Combine(Spacing.Simple), + mergeBoth: (tn, tab, dif) => + { + Dictionary modelIxs = modelIndices[tab]; + + var removedColums = dif.Columns.Keys.Except(tab.Columns.Keys).ToHashSet(); + + var changes = Synchronizer.SynchronizeScript(Spacing.Simple, + modelIxs.Where(kvp => !(kvp.Value is PrimaryKeyIndex)).ToDictionary(), + dif.Indices.Where(kvp =>!kvp.Value.IsPrimary).ToDictionary(), + createNew: null, + removeOld: (i, dix) => dix.Columns.Any(c => removedColums.Contains(c.ColumnName)) || dix.IsControlledIndex ? sqlBuilder.DropIndex(dif.Name, dix) : null, + mergeBoth: (i, mix, dix) => !dix.IndexEquals(dif, mix) ? sqlBuilder.DropIndex(dif.Name, dix) : null + ); + + return changes; + }); - var removedColums = dif.Columns.Keys.Except(tab.Columns.Keys).ToHashSet(); + SqlPreCommand? dropIndicesHistory = + Synchronizer.SynchronizeScript(Spacing.Double, modelTablesHistory, databaseTablesHistory, + createNew: null, + removeOld: (tn, dif) => dif.Indices.Values.Where(ix => !ix.IsPrimary).Select(ix => sqlBuilder.DropIndex(dif.Name, ix)).Combine(Spacing.Simple), + mergeBoth: (tn, tab, dif) => + { + Dictionary modelIxs = modelIndices[tab]; - var changes = Synchronizer.SynchronizeScript(Spacing.Simple, - modelIxs.Where(kvp => kvp.Value.GetType() == typeof(TableIndex)).ToDictionary(), - dif.Indices.Where(kvp => !kvp.Value.IsPrimary).ToDictionary(), - createNew: null, - removeOld: (i, dix) => dix.Columns.Any(c => removedColums.Contains(c.ColumnName)) || dix.IsControlledIndex ? sqlBuilder.DropIndex(dif.Name, dix) : null, - mergeBoth: (i, mix, dix) => !dix.IndexEquals(dif, mix) ? sqlBuilder.DropIndex(dif.Name, dix) : null - ); + var removedColums = dif.Columns.Keys.Except(tab.Columns.Keys).ToHashSet(); - return changes; - }); + var changes = Synchronizer.SynchronizeScript(Spacing.Simple, + modelIxs.Where(kvp => kvp.Value.GetType() == typeof(TableIndex)).ToDictionary(), + dif.Indices.Where(kvp => !kvp.Value.IsPrimary).ToDictionary(), + createNew: null, + removeOld: (i, dix) => dix.Columns.Any(c => removedColums.Contains(c.ColumnName)) || dix.IsControlledIndex ? sqlBuilder.DropIndex(dif.Name, dix) : null, + mergeBoth: (i, mix, dix) => !dix.IndexEquals(dif, mix) ? sqlBuilder.DropIndex(dif.Name, dix) : null + ); - SqlPreCommand? dropForeignKeys = Synchronizer.SynchronizeScript( - Spacing.Double, - modelTables, - databaseTables, - createNew: null, - removeOld: (tn, dif) => dif.Columns.Values.Select(c => c.ForeignKey != null ? sqlBuilder.AlterTableDropConstraint(dif.Name, c.ForeignKey.Name) : null) - .Concat(dif.MultiForeignKeys.Select(fk => sqlBuilder.AlterTableDropConstraint(dif.Name, fk.Name))).Combine(Spacing.Simple), - mergeBoth: (tn, tab, dif) => SqlPreCommand.Combine(Spacing.Simple, - Synchronizer.SynchronizeScript( - Spacing.Simple, - tab.Columns, - dif.Columns, + return changes; + }); + + SqlPreCommand? dropForeignKeys = Synchronizer.SynchronizeScript( + Spacing.Double, + modelTables, + databaseTables, createNew: null, - removeOld: (cn, colDb) => colDb.ForeignKey != null ? sqlBuilder.AlterTableDropConstraint(dif.Name, colDb.ForeignKey.Name) : null, - mergeBoth: (cn, colModel, colDb) => colDb.ForeignKey == null ? null : - colModel.ReferenceTable == null || colModel.AvoidForeignKey || !colModel.ReferenceTable.Name.Equals(ChangeName(colDb.ForeignKey.TargetTable)) || DifferentDatabase(tab.Name, colModel.ReferenceTable.Name) || !colDb.DbType.Equals(colModel.DbType) ? - sqlBuilder.AlterTableDropConstraint(dif.Name, colDb.ForeignKey.Name) : - null), - dif.MultiForeignKeys.Select(fk => sqlBuilder.AlterTableDropConstraint(dif.Name, fk.Name)).Combine(Spacing.Simple)) - ); + removeOld: (tn, dif) => dif.Columns.Values.Select(c => c.ForeignKey != null ? sqlBuilder.AlterTableDropConstraint(dif.Name, c.ForeignKey.Name) : null) + .Concat(dif.MultiForeignKeys.Select(fk => sqlBuilder.AlterTableDropConstraint(dif.Name, fk.Name))).Combine(Spacing.Simple), + mergeBoth: (tn, tab, dif) => SqlPreCommand.Combine(Spacing.Simple, + Synchronizer.SynchronizeScript( + Spacing.Simple, + tab.Columns, + dif.Columns, + createNew: null, + removeOld: (cn, colDb) => colDb.ForeignKey != null ? sqlBuilder.AlterTableDropConstraint(dif.Name, colDb.ForeignKey.Name) : null, + mergeBoth: (cn, colModel, colDb) => colDb.ForeignKey == null ? null : + colModel.ReferenceTable == null || colModel.AvoidForeignKey || !colModel.ReferenceTable.Name.Equals(ChangeName(colDb.ForeignKey.TargetTable)) || DifferentDatabase(tab.Name, colModel.ReferenceTable.Name) || !colDb.DbType.Equals(colModel.DbType) ? + sqlBuilder.AlterTableDropConstraint(dif.Name, colDb.ForeignKey.Name) : + null), + dif.MultiForeignKeys.Select(fk => sqlBuilder.AlterTableDropConstraint(dif.Name, fk.Name)).Combine(Spacing.Simple)) + ); - HashSet hasValueFalse = new HashSet(); - - List delayedUpdates = new List(); - List delayedDrops = new List(); - List delayedAddSystemVersioning = new List(); - - SqlPreCommand? tables = - Synchronizer.SynchronizeScript( - Spacing.Double, - modelTables, - databaseTables, - createNew: (tn, tab) => SqlPreCommand.Combine(Spacing.Double, - sqlBuilder.CreateTableSql(tab) - ), - removeOld: (tn, dif) => sqlBuilder.DropTable(dif), - mergeBoth: (tn, tab, dif) => - { - var rename = !object.Equals(dif.Name, tab.Name) ? sqlBuilder.RenameOrMove(dif, tab, tab.Name) : null; + HashSet hasValueFalse = new HashSet(); + + List delayedUpdates = new List(); + List delayedDrops = new List(); + List delayedAddSystemVersioning = new List(); + + SqlPreCommand? tables = + Synchronizer.SynchronizeScript( + Spacing.Double, + modelTables, + databaseTables, + createNew: (tn, tab) => SqlPreCommand.Combine(Spacing.Double, + sqlBuilder.CreateTableSql(tab) + ), + removeOld: (tn, dif) => sqlBuilder.DropTable(dif), + mergeBoth: (tn, tab, dif) => + { + var rename = !object.Equals(dif.Name, tab.Name) ? sqlBuilder.RenameOrMove(dif, tab, tab.Name) : null; - bool disableEnableSystemVersioning = false; + bool disableEnableSystemVersioning = false; - var disableSystemVersioning = !sqlBuilder.IsPostgres && dif.TemporalType != SysTableTemporalType.None && - (tab.SystemVersioned == null || - !object.Equals(replacements.Apply(Replacements.KeyTables, dif.TemporalTableName!.ToString()), tab.SystemVersioned.TableName.ToString()) || - (disableEnableSystemVersioning = StrongColumnChanges(tab, dif))) ? - sqlBuilder.AlterTableDisableSystemVersioning(tab.Name).Do(a => a.GoAfter = true) : - null; + var disableSystemVersioning = !sqlBuilder.IsPostgres && dif.TemporalType != SysTableTemporalType.None && + (tab.SystemVersioned == null || + !object.Equals(replacements.Apply(Replacements.KeyTables, dif.TemporalTableName!.ToString()), tab.SystemVersioned.TableName.ToString()) || + (disableEnableSystemVersioning = StrongColumnChanges(tab, dif))) ? + sqlBuilder.AlterTableDisableSystemVersioning(tab.Name).Do(a => a.GoAfter = true) : + null; - var dropPeriod = !sqlBuilder.IsPostgres && dif.Period != null && - (tab.SystemVersioned == null || !dif.Period.PeriodEquals(tab.SystemVersioned)) ? - sqlBuilder.AlterTableDropPeriod(tab) : null; + var dropPeriod = !sqlBuilder.IsPostgres && dif.Period != null && + (tab.SystemVersioned == null || !dif.Period.PeriodEquals(tab.SystemVersioned)) ? + sqlBuilder.AlterTableDropPeriod(tab) : null; - var modelPK = modelIndices[tab].Values.OfType().SingleOrDefaultEx(); - var diffPK = dif.Indices.Values.SingleOrDefaultEx(a => a.IsPrimary); + var modelPK = modelIndices[tab].Values.OfType().SingleOrDefaultEx(); + var diffPK = dif.Indices.Values.SingleOrDefaultEx(a => a.IsPrimary); - var dropPrimaryKey = diffPK != null && (modelPK == null || !diffPK.IndexEquals(dif, modelPK)) ? sqlBuilder.DropIndex(tab.Name, diffPK) : null; + var dropPrimaryKey = diffPK != null && (modelPK == null || !diffPK.IndexEquals(dif, modelPK)) ? sqlBuilder.DropIndex(tab.Name, diffPK) : null; - var columns = Synchronizer.SynchronizeScript( - Spacing.Simple, - tab.Columns, - dif.Columns, + var columns = Synchronizer.SynchronizeScript( + Spacing.Simple, + tab.Columns, + dif.Columns, - createNew: (cn, tabCol) => SqlPreCommand.Combine(Spacing.Simple, - tabCol.PrimaryKey && dif.PrimaryKeyName != null ? sqlBuilder.DropPrimaryKeyConstraint(tab.Name) : null, - AlterTableAddColumnDefault(sqlBuilder, tab, tabCol, replacements, - forceDefaultValue: cn.EndsWith("_HasValue") && dif.Columns.Values.Any(c => c.Name.StartsWith(cn.Before("HasValue")) && c.Nullable == false) ? "1" : null, - hasValueFalse: hasValueFalse)), + createNew: (cn, tabCol) => SqlPreCommand.Combine(Spacing.Simple, + tabCol.PrimaryKey && dif.PrimaryKeyName != null ? sqlBuilder.DropPrimaryKeyConstraint(tab.Name) : null, + AlterTableAddColumnDefault(sqlBuilder, tab, tabCol, replacements, + forceDefaultValue: cn.EndsWith("_HasValue") && dif.Columns.Values.Any(c => c.Name.StartsWith(cn.Before("HasValue")) && c.Nullable == false) ? "1" : null, + hasValueFalse: hasValueFalse)), - removeOld: (cn, difCol) => SqlPreCommand.Combine(Spacing.Simple, - difCol.DefaultConstraint != null && difCol.DefaultConstraint.Name != null ? sqlBuilder.AlterTableDropConstraint(tab.Name, difCol.DefaultConstraint!.Name) : null, - sqlBuilder.AlterTableDropColumn(tab, cn)), + removeOld: (cn, difCol) => SqlPreCommand.Combine(Spacing.Simple, + difCol.DefaultConstraint != null && difCol.DefaultConstraint.Name != null ? sqlBuilder.AlterTableDropConstraint(tab.Name, difCol.DefaultConstraint!.Name) : null, + sqlBuilder.AlterTableDropColumn(tab, cn)), - mergeBoth: (cn, tabCol, difCol) => - { - if (difCol.CompatibleTypes(tabCol) && difCol.Identity == tabCol.Identity) + mergeBoth: (cn, tabCol, difCol) => { - return SqlPreCommand.Combine(Spacing.Simple, - - difCol.Name == tabCol.Name ? null : sqlBuilder.RenameColumn(tab.Name, difCol.Name, tabCol.Name), - - difCol.ColumnEquals(tabCol, ignorePrimaryKey: true, ignoreIdentity: false, ignoreGenerateAlways: true) ? - null : - SqlPreCommand.Combine(Spacing.Simple, - tabCol.PrimaryKey && !difCol.PrimaryKey && dif.PrimaryKeyName != null ? sqlBuilder.DropPrimaryKeyConstraint(tab.Name) : null, - UpdateCompatible(sqlBuilder, replacements, tab, dif, tabCol, difCol), - (sqlBuilder.IsPostgres ? - tabCol.DbType.PostgreSql == NpgsqlDbType.Varchar && difCol.DbType.PostgreSql == NpgsqlDbType.Char : - tabCol.DbType.SqlServer == SqlDbType.NVarChar && difCol.DbType.SqlServer == SqlDbType.NChar) ? sqlBuilder.UpdateTrim(tab, tabCol) : null), + if (difCol.CompatibleTypes(tabCol) && difCol.Identity == tabCol.Identity) + { + return SqlPreCommand.Combine(Spacing.Simple, - UpdateByFkChange(tn, difCol, tabCol, ChangeName), + difCol.Name == tabCol.Name ? null : sqlBuilder.RenameColumn(tab.Name, difCol.Name, tabCol.Name), - difCol.DefaultEquals(tabCol) ? null : SqlPreCommand.Combine(Spacing.Simple, - difCol.DefaultConstraint != null ? sqlBuilder.AlterTableDropDefaultConstaint(tab.Name, difCol) : null, - tabCol.Default != null ? sqlBuilder.AlterTableAddDefaultConstraint(tab.Name, sqlBuilder.GetDefaultConstaint(tab, tabCol)!) : null) - ); - } - else - { - var update = difCol.PrimaryKey ? null : UpdateForeignKeyTypeChanged(sqlBuilder, tab, dif, tabCol, difCol, ChangeName, preRenameColumnsList) ?? UpdateCustom(tab, tabCol, difCol); - var drop = sqlBuilder.AlterTableDropColumn(tab, difCol.Name); + difCol.ColumnEquals(tabCol, ignorePrimaryKey: true, ignoreIdentity: false, ignoreGenerateAlways: true) ? + null : + SqlPreCommand.Combine(Spacing.Simple, + tabCol.PrimaryKey && !difCol.PrimaryKey && dif.PrimaryKeyName != null ? sqlBuilder.DropPrimaryKeyConstraint(tab.Name) : null, + UpdateCompatible(sqlBuilder, replacements, tab, dif, tabCol, difCol), + (sqlBuilder.IsPostgres ? + tabCol.DbType.PostgreSql == NpgsqlDbType.Varchar && difCol.DbType.PostgreSql == NpgsqlDbType.Char : + tabCol.DbType.SqlServer == SqlDbType.NVarChar && difCol.DbType.SqlServer == SqlDbType.NChar)? sqlBuilder.UpdateTrim(tab, tabCol) : null), - delayedUpdates.Add(update); - delayedDrops.Add(SqlPreCommand.Combine(Spacing.Simple, - difCol.DefaultConstraint != null ? sqlBuilder.AlterTableDropDefaultConstaint(tab.Name, difCol) : null, - drop - )); + UpdateByFkChange(tn, difCol, tabCol, ChangeName), - if (disableSystemVersioning != null) - { - delayedUpdates.Add(update == null ? null : ForHistoryTable(update, tab)); - delayedDrops.Add(drop == null ? null : ForHistoryTable(drop, tab)); + difCol.DefaultEquals(tabCol) ? null : SqlPreCommand.Combine(Spacing.Simple, + difCol.DefaultConstraint != null ? sqlBuilder.AlterTableDropDefaultConstaint(tab.Name, difCol) : null, + tabCol.Default != null ? sqlBuilder.AlterTableAddDefaultConstraint(tab.Name, sqlBuilder.GetDefaultConstaint(tab, tabCol)!) : null) + ); } + else + { + var update = difCol.PrimaryKey ? null : UpdateForeignKeyTypeChanged(sqlBuilder, tab, dif, tabCol, difCol, ChangeName, preRenameColumnsList) ?? UpdateCustom(tab, tabCol, difCol); + var drop = sqlBuilder.AlterTableDropColumn(tab, difCol.Name); - return SqlPreCommand.Combine(Spacing.Simple, - AlterTableAddColumnDefaultZero(sqlBuilder, tab, tabCol) - ); + delayedUpdates.Add(update); + delayedDrops.Add(SqlPreCommand.Combine(Spacing.Simple, + difCol.DefaultConstraint != null ? sqlBuilder.AlterTableDropDefaultConstaint(tab.Name, difCol) : null, + drop + )); + + if (disableSystemVersioning != null) + { + delayedUpdates.Add(update == null ? null : ForHistoryTable(update, tab)); + delayedDrops.Add(drop == null ? null : ForHistoryTable(drop, tab)); + } + + return SqlPreCommand.Combine(Spacing.Simple, + AlterTableAddColumnDefaultZero(sqlBuilder, tab, tabCol) + ); + } } - } - ); + ); - var createPrimaryKey = modelPK != null && (diffPK == null || !diffPK.IndexEquals(dif, modelPK)) ? sqlBuilder.CreateIndex(modelPK, checkUnique: null) : null; + var createPrimaryKey = modelPK != null && (diffPK == null || !diffPK.IndexEquals(dif, modelPK)) ? sqlBuilder.CreateIndex(modelPK, checkUnique: null) : null; - var columnsHistory = columns != null && disableEnableSystemVersioning ? ForHistoryTable(columns, tab).Replace(new Regex(" IDENTITY "), m => " ") : null;/*HACK*/ + var columnsHistory = columns != null && disableEnableSystemVersioning ? ForHistoryTable(columns, tab).Replace(new Regex(" IDENTITY "), m => " ") : null;/*HACK*/ - var addPeriod = (!sqlBuilder.IsPostgres && tab.SystemVersioned != null && - (dif.Period == null || !dif.Period.PeriodEquals(tab.SystemVersioned))) ? - (SqlPreCommandSimple)sqlBuilder.AlterTableAddPeriod(tab) : null; + var addPeriod = (!sqlBuilder.IsPostgres && tab.SystemVersioned != null && + (dif.Period == null || !dif.Period.PeriodEquals(tab.SystemVersioned))) ? + (SqlPreCommandSimple)sqlBuilder.AlterTableAddPeriod(tab) : null; - var addSystemVersioning = (!sqlBuilder.IsPostgres && tab.SystemVersioned != null && - (dif.Period == null || dif.TemporalTableName == null || - !object.Equals(replacements.Apply(Replacements.KeyTables, dif.TemporalTableName.ToString()), tab.SystemVersioned.TableName.ToString()) || - disableEnableSystemVersioning) ? - sqlBuilder.AlterTableEnableSystemVersioning(tab).Do(a => a.GoBefore = true) : null); + var addSystemVersioning = (!sqlBuilder.IsPostgres && tab.SystemVersioned != null && + (dif.Period == null || dif.TemporalTableName == null || + !object.Equals(replacements.Apply(Replacements.KeyTables, dif.TemporalTableName.ToString()), tab.SystemVersioned.TableName.ToString()) || + disableEnableSystemVersioning) ? + sqlBuilder.AlterTableEnableSystemVersioning(tab).Do(a => a.GoBefore = true) : null); - SqlPreCommand? combinedAddPeriod = null; - if (addPeriod != null && columns is SqlPreCommandConcat cols) - { - var periodRows = cols.Leaves().Where(pcs => pcs.Sql.Contains(" ADD ") && pcs.Sql.Contains("GENERATED ALWAYS AS ROW")).ToList(); - if (periodRows.Count == 2) + SqlPreCommand? combinedAddPeriod = null; + if (addPeriod != null && columns is SqlPreCommandConcat cols) { - combinedAddPeriod = new SqlPreCommandSimple($@"ALTER TABLE {tn} ADD + var periodRows = cols.Leaves().Where(pcs => pcs.Sql.Contains(" ADD ") && pcs.Sql.Contains("GENERATED ALWAYS AS ROW")).ToList(); + if (periodRows.Count == 2) + { + combinedAddPeriod = new SqlPreCommandSimple($@"ALTER TABLE {tn} ADD {periodRows[0].Sql.After(" ADD ").BeforeLast(";")}, {periodRows[1].Sql.After(" ADD ").BeforeLast(";")}, {addPeriod.Sql.After(" ADD ")} "); - addPeriod = null; - columns = cols.Leaves().Except(periodRows).Combine(cols.Spacing); + addPeriod = null; + columns = cols.Leaves().Except(periodRows).Combine(cols.Spacing); + } } - } - - delayedAddSystemVersioning.Add(SqlPreCommand.Combine(Spacing.Simple, addPeriod, addSystemVersioning)); - - return SqlPreCommand.Combine(Spacing.Simple, - rename, - disableSystemVersioning, - dropPeriod, - dropPrimaryKey, - combinedAddPeriod, - columns, - columnsHistory, - createPrimaryKey); - }); - if (tables != null) - tables.GoAfter = true; + delayedAddSystemVersioning.Add(SqlPreCommand.Combine(Spacing.Simple, addPeriod, addSystemVersioning)); - SqlPreCommand? historyTables = Synchronizer.SynchronizeScript(Spacing.Double, modelTablesHistory, databaseTablesHistory, - createNew: null, - removeOld: (tn, dif) => sqlBuilder.DropTable(dif.Name), - mergeBoth: (tn, tab, dif) => !object.Equals(dif.Name, tab.SystemVersioned!.TableName) ? sqlBuilder.RenameOrMove(dif, tab, tab.SystemVersioned!.TableName) : null); + return SqlPreCommand.Combine(Spacing.Simple, + rename, + disableSystemVersioning, + dropPeriod, + dropPrimaryKey, + combinedAddPeriod, + columns, + columnsHistory, + createPrimaryKey); + }); - SqlPreCommand? syncEnums = SynchronizeEnumsScript(replacements); + if (tables != null) + tables.GoAfter = true; - bool? createMissingFreeIndexes = null; + SqlPreCommand? historyTables = Synchronizer.SynchronizeScript(Spacing.Double, modelTablesHistory, databaseTablesHistory, + createNew: null, + removeOld: (tn, dif) => sqlBuilder.DropTable(dif.Name), + mergeBoth: (tn, tab, dif) => !object.Equals(dif.Name, tab.SystemVersioned!.TableName) ? sqlBuilder.RenameOrMove(dif, tab, tab.SystemVersioned!.TableName) : null); - SqlPreCommand? addForeingKeys = Synchronizer.SynchronizeScript( - Spacing.Double, - modelTables, - databaseTables, - createNew: (tn, tab) => sqlBuilder.AlterTableForeignKeys(tab), - removeOld: null, - mergeBoth: (tn, tab, dif) => Synchronizer.SynchronizeScript( - Spacing.Simple, - tab.Columns, - dif.Columns, + SqlPreCommand? syncEnums = SynchronizeEnumsScript(replacements); - createNew: (cn, colModel) => colModel.ReferenceTable == null || colModel.AvoidForeignKey || DifferentDatabase(tab.Name, colModel.ReferenceTable.Name) ? null : - sqlBuilder.AlterTableAddConstraintForeignKey(tab, colModel.Name, colModel.ReferenceTable), + bool? createMissingFreeIndexes = null; + SqlPreCommand? addForeingKeys = Synchronizer.SynchronizeScript( + Spacing.Double, + modelTables, + databaseTables, + createNew: (tn, tab) => sqlBuilder.AlterTableForeignKeys(tab), removeOld: null, + mergeBoth: (tn, tab, dif) => Synchronizer.SynchronizeScript( + Spacing.Simple, + tab.Columns, + dif.Columns, - mergeBoth: (cn, tabCol, difCol) => - { - if (tabCol.ReferenceTable == null || tabCol.AvoidForeignKey || DifferentDatabase(tab.Name, tabCol.ReferenceTable.Name)) - return null; + createNew: (cn, colModel) => colModel.ReferenceTable == null || colModel.AvoidForeignKey || DifferentDatabase(tab.Name, colModel.ReferenceTable.Name) ? null : + sqlBuilder.AlterTableAddConstraintForeignKey(tab, colModel.Name, colModel.ReferenceTable), - if (difCol.ForeignKey == null || !tabCol.ReferenceTable.Name.Equals(ChangeName(difCol.ForeignKey.TargetTable)) || !difCol.DbType.Equals(tabCol.DbType)) - return sqlBuilder.AlterTableAddConstraintForeignKey(tab, tabCol.Name, tabCol.ReferenceTable); - - var name = sqlBuilder.ForeignKeyName(tab.Name.Name, tabCol.Name); - return SqlPreCommand.Combine(Spacing.Simple, - name != difCol.ForeignKey.Name.Name ? sqlBuilder.RenameForeignKey(tab.Name, difCol.ForeignKey.Name.OnSchema(tab.Name.Schema), name) : null, - (difCol.ForeignKey.IsDisabled || difCol.ForeignKey.IsNotTrusted) && !replacements.SchemaOnly ? sqlBuilder.EnableForeignKey(tab.Name, name) : null); - }) - ); + removeOld: null, + mergeBoth: (cn, tabCol, difCol) => + { + if (tabCol.ReferenceTable == null || tabCol.AvoidForeignKey || DifferentDatabase(tab.Name, tabCol.ReferenceTable.Name)) + return null; - SqlPreCommand? addIndices = - Synchronizer.SynchronizeScript(Spacing.Double, modelTables, databaseTables, - createNew: (tn, tab) => modelIndices[tab].Values.Where(a => !(a is PrimaryKeyIndex)).Select(index => sqlBuilder.CreateIndex(index, null)).Combine(Spacing.Simple), - removeOld: null, - mergeBoth: (tn, tab, dif) => - { - var columnReplacements = replacements.TryGetC(Replacements.KeyColumnsForTable(tn)); + if (difCol.ForeignKey == null || !tabCol.ReferenceTable.Name.Equals(ChangeName(difCol.ForeignKey.TargetTable)) || !difCol.DbType.Equals(tabCol.DbType)) + return sqlBuilder.AlterTableAddConstraintForeignKey(tab, tabCol.Name, tabCol.ReferenceTable); - Func isNew = c => !dif.Columns.ContainsKey(columnReplacements?.TryGetC(c.Name) ?? c.Name); + var name = sqlBuilder.ForeignKeyName(tab.Name.Name, tabCol.Name); + return SqlPreCommand.Combine(Spacing.Simple, + name != difCol.ForeignKey.Name.Name ? sqlBuilder.RenameForeignKey(tab.Name, difCol.ForeignKey.Name.OnSchema(tab.Name.Schema), name) : null, + (difCol.ForeignKey.IsDisabled || difCol.ForeignKey.IsNotTrusted) && !replacements.SchemaOnly ? sqlBuilder.EnableForeignKey(tab.Name, name) : null); + }) + ); - Dictionary modelIxs = modelIndices[tab]; - var controlledIndexes = Synchronizer.SynchronizeScript(Spacing.Simple, - modelIxs.Where(kvp => !(kvp.Value is PrimaryKeyIndex)).ToDictionary(), - dif.Indices.Where(kvp => !kvp.Value.IsPrimary).ToDictionary(), - createNew: (i, mix) => mix is UniqueTableIndex || mix.Columns.Any(isNew) || (replacements.Interactive ? SafeConsole.Ask(ref createMissingFreeIndexes, "Create missing non-unique index {0} in {1}?".FormatWith(mix.IndexName, tab.Name)) : true) ? sqlBuilder.CreateIndex(mix, checkUnique: replacements) : null, - removeOld: null, - mergeBoth: (i, mix, dix) => !dix.IndexEquals(dif, mix) ? sqlBuilder.CreateIndex(mix, checkUnique: replacements) : - mix.IndexName != dix.IndexName ? sqlBuilder.RenameIndex(tab.Name, dix.IndexName, mix.IndexName) : null); - - return SqlPreCommand.Combine(Spacing.Simple, controlledIndexes); - }); + SqlPreCommand? addIndices = + Synchronizer.SynchronizeScript(Spacing.Double, modelTables, databaseTables, + createNew: (tn, tab) => modelIndices[tab].Values.Where(a => !(a is PrimaryKeyIndex)).Select(index => sqlBuilder.CreateIndex(index, null)).Combine(Spacing.Simple), + removeOld: null, + mergeBoth: (tn, tab, dif) => + { + var columnReplacements = replacements.TryGetC(Replacements.KeyColumnsForTable(tn)); - SqlPreCommand? addIndicesHistory = - Synchronizer.SynchronizeScript(Spacing.Double, modelTablesHistory, databaseTablesHistory, - createNew: (tn, tab) => modelIndices[tab].Values.Where(a => a.GetType() == typeof(TableIndex)).Select(mix => sqlBuilder.CreateIndexBasic(mix, forHistoryTable: true)).Combine(Spacing.Simple), - removeOld: null, - mergeBoth: (tn, tab, dif) => - { - var columnReplacements = replacements.TryGetC(Replacements.KeyColumnsForTable(tn)); + Func isNew = c => !dif.Columns.ContainsKey(columnReplacements?.TryGetC(c.Name) ?? c.Name); - Func isNew = c => !dif.Columns.ContainsKey(columnReplacements?.TryGetC(c.Name) ?? c.Name); + Dictionary modelIxs = modelIndices[tab]; - Dictionary modelIxs = modelIndices[tab]; + var controlledIndexes = Synchronizer.SynchronizeScript(Spacing.Simple, + modelIxs.Where(kvp => !(kvp.Value is PrimaryKeyIndex)).ToDictionary(), + dif.Indices.Where(kvp => !kvp.Value.IsPrimary).ToDictionary(), + createNew: (i, mix) => mix is UniqueTableIndex || mix.Columns.Any(isNew) || (replacements.Interactive ? SafeConsole.Ask(ref createMissingFreeIndexes, "Create missing non-unique index {0} in {1}?".FormatWith(mix.IndexName, tab.Name)) : true) ? sqlBuilder.CreateIndex(mix, checkUnique: replacements) : null, + removeOld: null, + mergeBoth: (i, mix, dix) => !dix.IndexEquals(dif, mix) ? sqlBuilder.CreateIndex(mix, checkUnique: replacements) : + mix.IndexName != dix.IndexName ? sqlBuilder.RenameIndex(tab.Name, dix.IndexName, mix.IndexName) : null); - var controlledIndexes = Synchronizer.SynchronizeScript(Spacing.Simple, - modelIxs.Where(kvp => kvp.Value.GetType() == typeof(TableIndex)).ToDictionary(), - dif.Indices.Where(kvp => !kvp.Value.IsPrimary).ToDictionary(), - createNew: (i, mix) => mix is UniqueTableIndex || mix.Columns.Any(isNew) || (replacements.Interactive ? SafeConsole.Ask(ref createMissingFreeIndexes, "Create missing non-unique index {0} in {1}?".FormatWith(mix.IndexName, tab.Name)) : true) ? sqlBuilder.CreateIndexBasic(mix, forHistoryTable: true) : null, - removeOld: null, - mergeBoth: (i, mix, dix) => !dix.IndexEquals(dif, mix) ? sqlBuilder.CreateIndexBasic(mix, forHistoryTable: true) : - mix.GetIndexName(tab.SystemVersioned!.TableName) != dix.IndexName ? sqlBuilder.RenameIndex(tab.SystemVersioned!.TableName, dix.IndexName, mix.GetIndexName(tab.SystemVersioned!.TableName)) : null); + return SqlPreCommand.Combine(Spacing.Simple, controlledIndexes); + }); - return SqlPreCommand.Combine(Spacing.Simple, controlledIndexes); - }); + SqlPreCommand? addIndicesHistory = + Synchronizer.SynchronizeScript(Spacing.Double, modelTablesHistory, databaseTablesHistory, + createNew: (tn, tab) => modelIndices[tab].Values.Where(a => a.GetType() == typeof(TableIndex)).Select(mix => sqlBuilder.CreateIndexBasic(mix, forHistoryTable: true)).Combine(Spacing.Simple), + removeOld: null, + mergeBoth: (tn, tab, dif) => + { + var columnReplacements = replacements.TryGetC(Replacements.KeyColumnsForTable(tn)); + Func isNew = c => !dif.Columns.ContainsKey(columnReplacements?.TryGetC(c.Name) ?? c.Name); + Dictionary modelIxs = modelIndices[tab]; - SqlPreCommand? dropSchemas = Synchronizer.SynchronizeScriptReplacing(replacements, "Schemas", Spacing.Double, - modelSchemas.ToDictionary(a => a.ToString()), - databaseSchemas.ToDictionary(a => a.ToString()), - createNew: null, - removeOld: (_, oldSN) => DropSchema(oldSN) ? sqlBuilder.DropSchema(oldSN) : null, - mergeBoth: (_, newSN, oldSN) => newSN.Equals(oldSN) ? null : sqlBuilder.DropSchema(oldSN) - ); + var controlledIndexes = Synchronizer.SynchronizeScript(Spacing.Simple, + modelIxs.Where(kvp => kvp.Value.GetType() == typeof(TableIndex)).ToDictionary(), + dif.Indices.Where(kvp => !kvp.Value.IsPrimary).ToDictionary(), + createNew: (i, mix) => mix is UniqueTableIndex || mix.Columns.Any(isNew) || (replacements.Interactive ? SafeConsole.Ask(ref createMissingFreeIndexes, "Create missing non-unique index {0} in {1}?".FormatWith(mix.IndexName, tab.Name)) : true) ? sqlBuilder.CreateIndexBasic(mix, forHistoryTable: true) : null, + removeOld: null, + mergeBoth: (i, mix, dix) => !dix.IndexEquals(dif, mix) ? sqlBuilder.CreateIndexBasic(mix, forHistoryTable: true) : + mix.GetIndexName(tab.SystemVersioned!.TableName) != dix.IndexName ? sqlBuilder.RenameIndex(tab.SystemVersioned!.TableName, dix.IndexName, mix.GetIndexName(tab.SystemVersioned!.TableName)) : null); - return SqlPreCommand.Combine(Spacing.Triple, - preRenameColumns, - createSchemas, - dropStatistics, + return SqlPreCommand.Combine(Spacing.Simple, controlledIndexes); + }); - dropIndices, dropIndicesHistory, - dropForeignKeys, + - tables, historyTables, - delayedUpdates.Combine(Spacing.Double), delayedDrops.Combine(Spacing.Double), delayedAddSystemVersioning.Combine(Spacing.Double), - syncEnums, - addForeingKeys, - addIndices, addIndicesHistory, + SqlPreCommand? dropSchemas = Synchronizer.SynchronizeScriptReplacing(replacements, "Schemas", Spacing.Double, + modelSchemas.ToDictionary(a => a.ToString()), + databaseSchemas.ToDictionary(a => a.ToString()), + createNew: null, + removeOld: (_, oldSN) => DropSchema(oldSN) ? sqlBuilder.DropSchema(oldSN) : null, + mergeBoth: (_, newSN, oldSN) => newSN.Equals(oldSN) ? null : sqlBuilder.DropSchema(oldSN) + ); - dropSchemas); + return SqlPreCommand.Combine(Spacing.Triple, + preRenameColumns, + createSchemas, + dropStatistics, + + dropIndices, dropIndicesHistory, + dropForeignKeys, + + tables, historyTables, + delayedUpdates.Combine(Spacing.Double), delayedDrops.Combine(Spacing.Double), delayedAddSystemVersioning.Combine(Spacing.Double), + syncEnums, + addForeingKeys, + addIndices, addIndicesHistory, + + dropSchemas); + } } - } - private static SqlPreCommand ForHistoryTable(SqlPreCommand sqlCommand, ITable tab) - { - return sqlCommand.Replace(new Regex(@$"\b{Regex.Escape(tab.Name.Name)}\b"), m => tab.SystemVersioned!.TableName.Name); - } + private static SqlPreCommand ForHistoryTable(SqlPreCommand sqlCommand, ITable tab) + { + return sqlCommand.Replace(new Regex(@$"\b{Regex.Escape(tab.Name.Name)}\b"), m => tab.SystemVersioned!.TableName.Name); + } - private static SqlPreCommand? UpdateForeignKeyTypeChanged(SqlBuilder sqlBuilder, ITable tab, DiffTable dif, IColumn tabCol, DiffColumn difCol, Func changeName, Dictionary> preRenameColumnsList) - { - if (difCol.ForeignKey != null && tabCol.ReferenceTable != null) + private static SqlPreCommand? UpdateForeignKeyTypeChanged(SqlBuilder sqlBuilder, ITable tab, DiffTable dif, IColumn tabCol, DiffColumn difCol, Func changeName, Dictionary> preRenameColumnsList) { - if (changeName(difCol.ForeignKey.TargetTable).Equals(tabCol.ReferenceTable.Name)) + if(difCol.ForeignKey != null && tabCol.ReferenceTable != null) { - AliasGenerator ag = new AliasGenerator(); - var tabAlias = ag.NextTableAlias(tab.Name.Name); - var fkAlias = ag.NextTableAlias(tabCol.ReferenceTable.Name.Name); + if (changeName(difCol.ForeignKey.TargetTable).Equals(tabCol.ReferenceTable.Name)) + { + AliasGenerator ag = new AliasGenerator(); + var tabAlias = ag.NextTableAlias(tab.Name.Name); + var fkAlias = ag.NextTableAlias(tabCol.ReferenceTable.Name.Name); - var oldId = difCol.ForeignKey.Columns.Only()?.Referenced; + var oldId = difCol.ForeignKey.Columns.Only()?.Referenced; - if (oldId == null) - return null; + if (oldId == null) + return null; - oldId = preRenameColumnsList.TryGetC(difCol.ForeignKey.TargetTable)?.TryGetC(oldId) ?? oldId; + oldId = preRenameColumnsList.TryGetC(difCol.ForeignKey.TargetTable)?.TryGetC(oldId) ?? oldId; - return new SqlPreCommandSimple( + return new SqlPreCommandSimple( @$"UPDATE {tabAlias} SET {tabCol.Name} = {fkAlias}.{tabCol.ReferenceTable.PrimaryKey.Name.SqlEscape(sqlBuilder.IsPostgres)} FROM {tab.Name} {tabAlias} JOIN {tabCol.ReferenceTable.Name} {fkAlias} ON {tabAlias}.{difCol.Name} = {fkAlias}.{oldId} "); + } } - } - - return null; - } - - private static SqlPreCommand UpdateCustom(ITable tab, IColumn tabCol, DiffColumn difCol) - { - return new SqlPreCommandSimple($"UPDATE {tab.Name} SET {tabCol.Name} = YourCode({difCol.Name})"); - } - private static string GetZero(IColumn column) - { - return (column.DbType.IsNumber() ? "0" : - column.DbType.IsString() ? "''" : - column.DbType.IsDate() ? "GetDate()" : - column.DbType.IsGuid() ? Guid.Empty.ToString() : - "?"); - } + return null; + } - private static bool StrongColumnChanges(ITable tab, DiffTable dif) - { - return tab.Columns.JoinDictionary(dif.Columns, (cn, tabCol, difCol) => (tabCol, difCol)) - .Select(kvp => kvp.Value) - .Any(t => (!t.tabCol.Nullable.ToBool() && t.difCol.Nullable) || !t.difCol.CompatibleTypes(t.tabCol)); - } + private static SqlPreCommand UpdateCustom(ITable tab, IColumn tabCol, DiffColumn difCol) + { + return new SqlPreCommandSimple($"UPDATE {tab.Name} SET {tabCol.Name} = YourCode({difCol.Name})"); + } - private static SqlPreCommand UpdateCompatible(SqlBuilder sqlBuilder, Replacements replacements, ITable tab, DiffTable dif, IColumn tabCol, DiffColumn difCol) - { - if (!(difCol.Nullable && !tabCol.Nullable.ToBool())) - return sqlBuilder.AlterTableAlterColumn(tab, tabCol, difCol); + private static string GetZero(IColumn column) + { + return (column.DbType.IsNumber() ? "0" : + column.DbType.IsString() ? "''" : + column.DbType.IsDate() ? "GetDate()" : + column.DbType.IsGuid() ? Guid.Empty.ToString() : + "?"); + } - var defaultValue = GetDefaultValue(tab, tabCol, replacements, forNewColumn: false); + private static bool StrongColumnChanges(ITable tab, DiffTable dif) + { + return tab.Columns.JoinDictionary(dif.Columns, (cn, tabCol, difCol) => (tabCol, difCol)) + .Select(kvp => kvp.Value) + .Any(t => (!t.tabCol.Nullable.ToBool() && t.difCol.Nullable) || !t.difCol.CompatibleTypes(t.tabCol)); + } - if (defaultValue == "force") - return sqlBuilder.AlterTableAlterColumn(tab, tabCol, difCol); + private static SqlPreCommand UpdateCompatible(SqlBuilder sqlBuilder, Replacements replacements, ITable tab, DiffTable dif, IColumn tabCol, DiffColumn difCol) + { + if (!(difCol.Nullable && !tabCol.Nullable.ToBool())) + return sqlBuilder.AlterTableAlterColumn(tab, tabCol, difCol); + + var defaultValue = GetDefaultValue(tab, tabCol, replacements, forNewColumn: false); - bool goBefore = difCol.Name != tabCol.Name; + if (defaultValue == "force") + return sqlBuilder.AlterTableAlterColumn(tab, tabCol, difCol); - return SqlPreCommand.Combine(Spacing.Simple, - NotNullUpdate(tab.Name, tabCol, defaultValue, goBefore), - sqlBuilder.AlterTableAlterColumn(tab, tabCol, difCol) - )!; - } + bool goBefore = difCol.Name != tabCol.Name; - private static SqlPreCommandSimple NotNullUpdate(ObjectName tab, IColumn tabCol, string defaultValue, bool goBefore) - { - return new SqlPreCommandSimple($"UPDATE {tab} SET {tabCol.Name} = {defaultValue} WHERE {tabCol.Name} IS NULL") { GoBefore = goBefore }; - } + return SqlPreCommand.Combine(Spacing.Simple, + NotNullUpdate(tab.Name, tabCol, defaultValue, goBefore), + sqlBuilder.AlterTableAlterColumn(tab, tabCol, difCol) + )!; + } - private static bool DifferentDatabase(ObjectName name, ObjectName name2) - { - return !object.Equals(name.Schema.Database, name2.Schema.Database); - } + private static SqlPreCommandSimple NotNullUpdate(ObjectName tab, IColumn tabCol, string defaultValue, bool goBefore) + { + return new SqlPreCommandSimple($"UPDATE {tab} SET {tabCol.Name} = {defaultValue} WHERE {tabCol.Name} IS NULL") { GoBefore = goBefore }; + } - public static Func IgnoreSchema = s => s.Name.Contains("\\"); + private static bool DifferentDatabase(ObjectName name, ObjectName name2) + { + return !object.Equals(name.Schema.Database, name2.Schema.Database); + } - private static SqlPreCommand AlterTableAddColumnDefault(SqlBuilder sqlBuilder, ITable table, IColumn column, Replacements rep, string? forceDefaultValue, HashSet hasValueFalse) - { - if (column.Nullable == IsNullable.Yes || column.Identity || column.Default != null || column is ImplementationColumn) - return sqlBuilder.AlterTableAddColumn(table, column); + public static Func IgnoreSchema = s => s.Name.Contains("\\"); - if (column.Nullable == IsNullable.Forced) + private static SqlPreCommand AlterTableAddColumnDefault(SqlBuilder sqlBuilder, ITable table, IColumn column, Replacements rep, string? forceDefaultValue, HashSet hasValueFalse) { - var hasValueColumn = table.GetHasValueColumn(column); - - if (hasValueColumn != null && hasValueFalse.Contains(hasValueColumn)) + if (column.Nullable == IsNullable.Yes || column.Identity || column.Default != null || column is ImplementationColumn) return sqlBuilder.AlterTableAddColumn(table, column); - var defaultValue = GetDefaultValue(table, column, rep, forNewColumn: true, forceDefaultValue: forceDefaultValue); - if (defaultValue == "force") - return sqlBuilder.AlterTableAddColumn(table, column); + if (column.Nullable == IsNullable.Forced) + { + var hasValueColumn = table.GetHasValueColumn(column); - var where = hasValueColumn != null ? $"{hasValueColumn.Name} = 1" : "??"; + if (hasValueColumn != null && hasValueFalse.Contains(hasValueColumn)) + return sqlBuilder.AlterTableAddColumn(table, column); - return SqlPreCommand.Combine(Spacing.Simple, - sqlBuilder.AlterTableAddColumn(table, column).Do(a => a.GoAfter = true), - new SqlPreCommandSimple($@"UPDATE {table.Name} SET + var defaultValue = GetDefaultValue(table, column, rep, forNewColumn: true, forceDefaultValue: forceDefaultValue); + if (defaultValue == "force") + return sqlBuilder.AlterTableAddColumn(table, column); + + var where = hasValueColumn != null ? $"{hasValueColumn.Name} = 1" : "??"; + + return SqlPreCommand.Combine(Spacing.Simple, + sqlBuilder.AlterTableAddColumn(table, column).Do(a => a.GoAfter = true), + new SqlPreCommandSimple($@"UPDATE {table.Name} SET {column.Name} = {sqlBuilder.Quote(column.DbType, defaultValue)} WHERE {where}"))!; + } + else + { + var defaultValue = GetDefaultValue(table, column, rep, forNewColumn: true, forceDefaultValue: forceDefaultValue); + if (defaultValue == "force") + return sqlBuilder.AlterTableAddColumn(table, column); + + if (column is FieldEmbedded.EmbeddedHasValueColumn hv && defaultValue == "0") + hasValueFalse.Add(hv); + + var tempDefault = new SqlBuilder.DefaultConstraint( + columnName: column.Name, + name: "DF_TEMP_" + column.Name, + quotedDefinition: sqlBuilder.Quote(column.DbType, defaultValue) + ); + + return SqlPreCommand.Combine(Spacing.Simple, + sqlBuilder.AlterTableAddColumn(table, column, tempDefault), + sqlBuilder.IsPostgres ? + sqlBuilder.AlterTableAlterColumnDropDefault(table.Name, column.Name): + sqlBuilder.AlterTableDropConstraint(table.Name, tempDefault.Name))!; + } } - else + + private static SqlPreCommand AlterTableAddColumnDefaultZero(SqlBuilder sqlBuilder, ITable table, IColumn column) { - var defaultValue = GetDefaultValue(table, column, rep, forNewColumn: true, forceDefaultValue: forceDefaultValue); - if (defaultValue == "force") + if (column.Nullable == IsNullable.Yes || column.Identity || column.Default != null || column is ImplementationColumn) return sqlBuilder.AlterTableAddColumn(table, column); - if (column is FieldEmbedded.EmbeddedHasValueColumn hv && defaultValue == "0") - hasValueFalse.Add(hv); + var defaultValue = + column.DbType.IsNumber()? "0" : + column.DbType.IsString()? "''" : + column.DbType.IsDate() ? "GetDate()" : + column.DbType.IsGuid() ? "'00000000-0000-0000-0000-000000000000'" : + "?"; var tempDefault = new SqlBuilder.DefaultConstraint( columnName: column.Name, - name: "DF_TEMP_" + column.Name, + name: "DF_TEMP_COPY_" + column.Name, quotedDefinition: sqlBuilder.Quote(column.DbType, defaultValue) ); return SqlPreCommand.Combine(Spacing.Simple, sqlBuilder.AlterTableAddColumn(table, column, tempDefault), - sqlBuilder.IsPostgres ? - sqlBuilder.AlterTableAlterColumnDropDefault(table.Name, column.Name) : sqlBuilder.AlterTableDropConstraint(table.Name, tempDefault.Name))!; } - } - - private static SqlPreCommand AlterTableAddColumnDefaultZero(SqlBuilder sqlBuilder, ITable table, IColumn column) - { - if (column.Nullable == IsNullable.Yes || column.Identity || column.Default != null || column is ImplementationColumn) - return sqlBuilder.AlterTableAddColumn(table, column); - - var defaultValue = - column.DbType.IsNumber() ? "0" : - column.DbType.IsString() ? "''" : - column.DbType.IsDate() ? "GetDate()" : - column.DbType.IsGuid() ? "'00000000-0000-0000-0000-000000000000'" : - "?"; - - var tempDefault = new SqlBuilder.DefaultConstraint( - columnName: column.Name, - name: "DF_TEMP_COPY_" + column.Name, - quotedDefinition: sqlBuilder.Quote(column.DbType, defaultValue) - ); - - return SqlPreCommand.Combine(Spacing.Simple, - sqlBuilder.AlterTableAddColumn(table, column, tempDefault), - sqlBuilder.AlterTableDropConstraint(table.Name, tempDefault.Name))!; - } - public static string GetDefaultValue(ITable table, IColumn column, Replacements rep, bool forNewColumn, string? forceDefaultValue = null) - { - if (column is SystemVersionedInfo.SqlServerPeriodColumn svc) + public static string GetDefaultValue(ITable table, IColumn column, Replacements rep, bool forNewColumn, string? forceDefaultValue = null) { - var date = svc.SystemVersionColumnType == SystemVersionedInfo.ColumnType.Start ? DateTime.MinValue : DateTime.MaxValue; + if (column is SystemVersionedInfo.SqlServerPeriodColumn svc) + { + var date = svc.SystemVersionColumnType == SystemVersionedInfo.ColumnType.Start ? DateTime.MinValue : DateTime.MaxValue; - return $"CONVERT(datetime2, '{date:yyyy-MM-dd HH:mm:ss.fffffff}')"; - } + return $"CONVERT(datetime2, '{date:yyyy-MM-dd HH:mm:ss.fffffff}')"; + } - string typeDefault = forceDefaultValue ?? - ( - column.DbType.IsBoolean() ? (Schema.Current.Settings.IsPostgres ? "false" : "0") : - column.DbType.IsNumber() ? "0" : - column.DbType.IsString() ? "''" : - column.DbType.IsDate() ? "GetDate()" : - column.DbType.IsGuid() ? "NEWID()" : + string typeDefault = forceDefaultValue ?? + ( + column.DbType.IsBoolean() ? (Schema.Current.Settings.IsPostgres ? "false" : "0") : + column.DbType.IsNumber() ? "0" : + column.DbType.IsString() ? "''" : + column.DbType.IsDate() ? "GetDate()" : + column.DbType.IsGuid() ? "NEWID()" : column.DbType.IsTime() ? "'00:00'" : - "?"); + "?"); - string defaultValue = rep.Interactive ? SafeConsole.AskString($"Default value for '{table.Name.Name}.{column.Name}'? ([Enter] for {typeDefault} or 'force' if there are no {(forNewColumn ? "rows" : "nulls")}) ", stringValidator: str => null) : ""; - if (defaultValue == "force") - return defaultValue; + string defaultValue = rep.Interactive ? SafeConsole.AskString($"Default value for '{table.Name.Name}.{column.Name}'? ([Enter] for {typeDefault} or 'force' if there are no {(forNewColumn ? "rows" : "nulls")}) ", stringValidator: str => null) : ""; + if (defaultValue == "force") + return defaultValue; - if (defaultValue.HasText()) - { - if (column.DbType.IsString() && !defaultValue.Contains("'")) - defaultValue = "'" + defaultValue + "'"; + if (defaultValue.HasText()) + { + if (column.DbType.IsString() && !defaultValue.Contains("'")) + defaultValue = "'" + defaultValue + "'"; - if (column.DbType.IsGuid() && !defaultValue.Contains("'")) - defaultValue = "'" + defaultValue + "'"; + if (column.DbType.IsGuid() && !defaultValue.Contains("'")) + defaultValue = "'" + defaultValue + "'"; - if ((column.DbType.IsDate() || column.DbType.IsTime()) && !defaultValue.Contains("'") && defaultValue != typeDefault) - defaultValue = "'" + defaultValue + "'"; + if ((column.DbType.IsDate() || column.DbType.IsTime()) && !defaultValue.Contains("'") && defaultValue != typeDefault) + defaultValue = "'" + defaultValue + "'"; - if (column.DbType.IsBoolean() && defaultValue != typeDefault) - { - defaultValue = Schema.Current.Settings.IsPostgres ? - (defaultValue == "0" ? "false" : defaultValue == "1" ? "true" : defaultValue) : - (defaultValue.ToLower() == "false" ? "0" : defaultValue.ToLower() == "true" ? "1" : defaultValue); + if(column.DbType.IsBoolean() && defaultValue != typeDefault) + { + defaultValue = Schema.Current.Settings.IsPostgres ? + (defaultValue == "0" ? "false" : defaultValue == "1" ? "true" : defaultValue) : + (defaultValue.ToLower() == "false" ? "0" : defaultValue.ToLower() == "true" ? "1" : defaultValue); + } } - } - if (string.IsNullOrEmpty(defaultValue)) - return typeDefault; + if (string.IsNullOrEmpty(defaultValue)) + return typeDefault; - return defaultValue; - } - - - private static Dictionary ApplyIndexAutoReplacements(DiffTable diff, ITable tab, Dictionary dictionary) - { - List oldOnly = diff.Indices.Keys.Where(n => !dictionary.ContainsKey(n)).ToList(); - List newOnly = dictionary.Keys.Where(n => !diff.Indices.ContainsKey(n)).ToList(); + return defaultValue; + } - if (oldOnly.Count == 0 || newOnly.Count == 0) - return diff.Indices; - Dictionary replacements = new Dictionary(); - foreach (var o in oldOnly) + private static Dictionary ApplyIndexAutoReplacements(DiffTable diff, ITable tab, Dictionary dictionary) { - var oldIx = diff.Indices[o]; + List oldOnly = diff.Indices.Keys.Where(n => !dictionary.ContainsKey(n)).ToList(); + List newOnly = dictionary.Keys.Where(n => !diff.Indices.ContainsKey(n)).ToList(); - var nIx = newOnly.FirstOrDefault(n => + if (oldOnly.Count == 0 || newOnly.Count == 0) + return diff.Indices; + + Dictionary replacements = new Dictionary(); + foreach (var o in oldOnly) { - var newIx = dictionary[n]; - if (oldIx.IsPrimary && newIx is PrimaryKeyIndex) - return true; + var oldIx = diff.Indices[o]; + + var nIx = newOnly.FirstOrDefault(n => + { + var newIx = dictionary[n]; + if (oldIx.IsPrimary && newIx is PrimaryKeyIndex) + return true; - if (oldIx.IsPrimary || newIx is PrimaryKeyIndex) - return false; + if (oldIx.IsPrimary || newIx is PrimaryKeyIndex) + return false; - if (oldIx.IsUnique != (newIx is UniqueTableIndex)) - return false; + if (oldIx.IsUnique != (newIx is UniqueTableIndex)) + return false; - if (oldIx.ViewName != null || (newIx is UniqueTableIndex) && ((UniqueTableIndex)newIx).ViewName != null) - return false; + if (oldIx.ViewName != null || (newIx is UniqueTableIndex) && ((UniqueTableIndex)newIx).ViewName != null) + return false; - var newCols = newIx.Columns.Select(c => diff.Columns.TryGetC(c.Name)?.Name).NotNull().ToHashSet(); - var newIncCols = newIx.IncludeColumns.EmptyIfNull().Select(c => diff.Columns.TryGetC(c.Name)?.Name).NotNull().ToHashSet(); + var newCols = newIx.Columns.Select(c => diff.Columns.TryGetC(c.Name)?.Name).NotNull().ToHashSet(); + var newIncCols = newIx.IncludeColumns.EmptyIfNull().Select(c => diff.Columns.TryGetC(c.Name)?.Name).NotNull().ToHashSet(); - var oldCols = oldIx.Columns.Where(a => a.IsIncluded == false).Select(a => a.ColumnName); - var oldIncCols = oldIx.Columns.Where(a => a.IsIncluded == true).Select(a => a.ColumnName); + var oldCols = oldIx.Columns.Where(a => a.IsIncluded == false).Select(a => a.ColumnName); + var oldIncCols = oldIx.Columns.Where(a => a.IsIncluded == true).Select(a => a.ColumnName); - if (!newCols.SetEquals(oldCols)) - return false; + if (!newCols.SetEquals(oldCols)) + return false; - if (!newIncCols.SetEquals(oldIncCols)) - return false; + if (!newIncCols.SetEquals(oldIncCols)) + return false; - var oldWhere = oldIx.IndexName.TryAfter("__"); - var newWhere = newIx.WhereSignature()?.TryAfter("__"); + var oldWhere = oldIx.IndexName.TryAfter("__"); + var newWhere = newIx.WhereSignature()?.TryAfter("__"); - if (oldWhere != newWhere) - return false; + if (oldWhere != newWhere) + return false; - return true; - }); + return true; + }); - if (nIx != null) - { - replacements.Add(o, nIx); - newOnly.Remove(nIx); + if (nIx != null) + { + replacements.Add(o, nIx); + newOnly.Remove(nIx); + } } - } - if (replacements.IsEmpty()) - return diff.Indices; + if (replacements.IsEmpty()) + return diff.Indices; - return diff.Indices.SelectDictionary(on => replacements.TryGetC(on) ?? on, dif => dif); - } + return diff.Indices.SelectDictionary(on => replacements.TryGetC(on) ?? on, dif => dif); + } - private static SqlPreCommandSimple? UpdateByFkChange(string tn, DiffColumn difCol, IColumn tabCol, Func changeName) - { - if (difCol.ForeignKey == null || tabCol.ReferenceTable == null || tabCol.AvoidForeignKey) - return null; + private static SqlPreCommandSimple? UpdateByFkChange(string tn, DiffColumn difCol, IColumn tabCol, Func changeName) + { + if (difCol.ForeignKey == null || tabCol.ReferenceTable == null || tabCol.AvoidForeignKey) + return null; - ObjectName oldFk = changeName(difCol.ForeignKey.TargetTable); + ObjectName oldFk = changeName(difCol.ForeignKey.TargetTable); - if (oldFk.Equals(tabCol.ReferenceTable.Name)) - return null; + if (oldFk.Equals(tabCol.ReferenceTable.Name)) + return null; - AliasGenerator ag = new AliasGenerator(); + AliasGenerator ag = new AliasGenerator(); var newFk = tabCol.ReferenceTable.Name; var tnAlias = ag.NextTableAlias(tn); var oldFkAlias = ag.NextTableAlias(oldFk.Name); - return new SqlPreCommandSimple( + return new SqlPreCommandSimple( @$"-- Column {tn}.{tabCol.Name} was referencing {oldFk} but not references {newFk}. An update is needed? UPDATE {tnAlias} SET {tabCol.Name} = -- get {newFk} id from {oldFkAlias}.Id FROM {tn} {tnAlias} JOIN {oldFk} {oldFkAlias} ON {tnAlias}.{tabCol.Name} = {oldFkAlias}.Id"); - } - - public static Func? IgnoreTable = null; + } + public static Func? IgnoreTable = null; + - static SqlPreCommand? SynchronizeEnumsScript(Replacements replacements) - { - try + static SqlPreCommand? SynchronizeEnumsScript(Replacements replacements) { - Schema schema = Schema.Current; + try + { + Schema schema = Schema.Current; - List commands = new List(); + List commands = new List(); - foreach (var table in schema.Tables.Values) - { - Type? enumType = EnumEntity.Extract(table.Type); - if (enumType != null) + foreach (var table in schema.Tables.Values) { - Console.Write("."); + Type? enumType = EnumEntity.Extract(table.Type); + if (enumType != null) + { + Console.Write("."); - IEnumerable should = EnumEntity.GetEntities(enumType); - Dictionary shouldByName = should.ToDictionary(a => a.ToString()); + IEnumerable should = EnumEntity.GetEntities(enumType); + Dictionary shouldByName = should.ToDictionary(a => a.ToString()); - List current = Administrator.TryRetrieveAll(table.Type, replacements); - int nullVal = 0; - Dictionary currentByName = current.ToDictionaryEx(a => a.toStr ?? $"NULL{nullVal++}", table.Name.Name); + List current = Administrator.TryRetrieveAll(table.Type, replacements); + int nullVal = 0; + Dictionary currentByName = current.ToDictionaryEx(a => a.toStr ?? $"NULL{nullVal++}", table.Name.Name); - string key = Replacements.KeyEnumsForTable(table.Name.Name); + string key = Replacements.KeyEnumsForTable(table.Name.Name); - replacements.AskForReplacements(currentByName.Keys.ToHashSet(), shouldByName.Keys.ToHashSet(), key); + replacements.AskForReplacements(currentByName.Keys.ToHashSet(), shouldByName.Keys.ToHashSet(), key); - currentByName = replacements.ApplyReplacementsToOld(currentByName, key); + currentByName = replacements.ApplyReplacementsToOld(currentByName, key); - var mix = shouldByName.JoinDictionary(currentByName, (n, s, c) => (s, c)).Where(a => a.Value.s.id != a.Value.c.id).ToDictionary(); + var mix = shouldByName.JoinDictionary(currentByName, (n, s, c) => (s, c)).Where(a => a.Value.s.id != a.Value.c.id).ToDictionary(); - HashSet usedIds = current.Select(a => a.Id).ToHashSet(); + HashSet usedIds = current.Select(a => a.Id).ToHashSet(); - Dictionary middleByName = mix.Where(kvp => usedIds.Contains(kvp.Value.s.Id)).ToDictionary(kvp => kvp.Key, kvp => Clone(kvp.Value.c)); + Dictionary middleByName = mix.Where(kvp => usedIds.Contains(kvp.Value.s.Id)).ToDictionary(kvp => kvp.Key, kvp => Clone(kvp.Value.c)); - if (middleByName.Any()) - { - var moveToAux = SyncEnums(schema, table, - currentByName.Where(a => middleByName.ContainsKey(a.Key)).ToDictionary(), - middleByName); - if (moveToAux != null) - commands.Add(moveToAux); - } + if (middleByName.Any()) + { + var moveToAux = SyncEnums(schema, table, + currentByName.Where(a => middleByName.ContainsKey(a.Key)).ToDictionary(), + middleByName); + if (moveToAux != null) + commands.Add(moveToAux); + } - var com = SyncEnums(schema, table, - currentByName.Where(a => !middleByName.ContainsKey(a.Key)).ToDictionary(), - shouldByName.Where(a => !middleByName.ContainsKey(a.Key)).ToDictionary()); - if (com != null) - commands.Add(com); + var com = SyncEnums(schema, table, + currentByName.Where(a => !middleByName.ContainsKey(a.Key)).ToDictionary(), + shouldByName.Where(a => !middleByName.ContainsKey(a.Key)).ToDictionary()); + if (com != null) + commands.Add(com); - if (middleByName.Any()) - { - var backFromAux = SyncEnums(schema, table, - middleByName, - shouldByName.Where(a => middleByName.ContainsKey(a.Key)).ToDictionary()); - if (backFromAux != null) - commands.Add(backFromAux); + if (middleByName.Any()) + { + var backFromAux = SyncEnums(schema, table, + middleByName, + shouldByName.Where(a => middleByName.ContainsKey(a.Key)).ToDictionary()); + if (backFromAux != null) + commands.Add(backFromAux); + } } } - } - return SqlPreCommand.Combine(Spacing.Double, commands.ToArray()); + return SqlPreCommand.Combine(Spacing.Double, commands.ToArray()); + } + catch (Exception e) + { + return new SqlPreCommandSimple("-- Exception synchronizing enums: " + e.Message); + } } - catch (Exception e) + + private static SqlPreCommand? SyncEnums(Schema schema, Table table, Dictionary current, Dictionary should) { - return new SqlPreCommandSimple("-- Exception synchronizing enums: " + e.Message); + var deletes = Synchronizer.SynchronizeScript(Spacing.Double, should, current, + createNew: null, + removeOld: (str, c) => table.DeleteSqlSync(c, null, comment: c.toStr), + mergeBoth: null); + + var moves = Synchronizer.SynchronizeScript(Spacing.Double, should, current, + createNew: null, + removeOld: null, + mergeBoth: (str, s, c) => + { + if (s.id == c.id) + return table.UpdateSqlSync(c, null, comment: c.toStr); + + var insert = table.InsertSqlSync(s); + + var move = (from t in schema.GetDatabaseTables() + from col in t.Columns.Values + where col.ReferenceTable == table + select new SqlPreCommandSimple("UPDATE {0} SET {1} = {2} WHERE {1} = {3} -- {4} re-indexed" + .FormatWith(t.Name, col.Name, s.Id, c.Id, c.toStr))) + .Combine(Spacing.Simple); + + var delete = table.DeleteSqlSync(c, null, comment: c.toStr); + + return SqlPreCommand.Combine(Spacing.Simple, insert, move, delete); + }); + + var creates = Synchronizer.SynchronizeScript(Spacing.Double, should, current, + createNew: (str, s) => table.InsertSqlSync(s), + removeOld: null, + mergeBoth: null); + + return SqlPreCommand.Combine(Spacing.Double, deletes, moves, creates); } - } - - private static SqlPreCommand? SyncEnums(Schema schema, Table table, Dictionary current, Dictionary should) - { - var deletes = Synchronizer.SynchronizeScript(Spacing.Double, should, current, - createNew: null, - removeOld: (str, c) => table.DeleteSqlSync(c, null, comment: c.toStr), - mergeBoth: null); - - var moves = Synchronizer.SynchronizeScript(Spacing.Double, should, current, - createNew: null, - removeOld: null, - mergeBoth: (str, s, c) => - { - if (s.id == c.id) - return table.UpdateSqlSync(c, null, comment: c.toStr); - - var insert = table.InsertSqlSync(s); - - var move = (from t in schema.GetDatabaseTables() - from col in t.Columns.Values - where col.ReferenceTable == table - select new SqlPreCommandSimple("UPDATE {0} SET {1} = {2} WHERE {1} = {3} -- {4} re-indexed" - .FormatWith(t.Name, col.Name, s.Id, c.Id, c.toStr))) - .Combine(Spacing.Simple); - - var delete = table.DeleteSqlSync(c, null, comment: c.toStr); - - return SqlPreCommand.Combine(Spacing.Simple, insert, move, delete); - }); - - var creates = Synchronizer.SynchronizeScript(Spacing.Double, should, current, - createNew: (str, s) => table.InsertSqlSync(s), - removeOld: null, - mergeBoth: null); - - return SqlPreCommand.Combine(Spacing.Double, deletes, moves, creates); - } - - private static Entity Clone(Entity current) - { - var instance = (Entity)Activator.CreateInstance(current.GetType())!; - instance.toStr = current.toStr; - instance.id = (int)current.id!.Value + 1000000; - return instance; - } - - public static SqlPreCommand? SnapshotIsolation(Replacements replacements) - { - if (replacements.SchemaOnly || Schema.Current.Settings.IsPostgres) - return null; - - var list = Schema.Current.DatabaseNames().Select(a => a?.ToString()).ToList(); - var sqlBuilder = Connector.Current.SqlBuilder; - if (list.Contains(null)) + private static Entity Clone(Entity current) { - list.Remove(null); - list.Add(Connector.Current.DatabaseName()); + var instance = (Entity)Activator.CreateInstance(current.GetType())!; + instance.toStr = current.toStr; + instance.id = (int)current.id!.Value + 1000000; + return instance; } - var results = Database.View() - .Where(d => list.Contains(d.name)) - .Select(d => new + public static SqlPreCommand? SnapshotIsolation(Replacements replacements) + { + if (replacements.SchemaOnly || Schema.Current.Settings.IsPostgres) + return null; + + var list = Schema.Current.DatabaseNames().Select(a => a?.ToString()).ToList(); + var sqlBuilder = Connector.Current.SqlBuilder; + + if (list.Contains(null)) { - name = new DatabaseName(null, d.name, Connector.Current.Schema.Settings.IsPostgres), - d.snapshot_isolation_state, - d.is_read_committed_snapshot_on - }).ToList(); - - var cmd = replacements.WithReplacedDatabaseName().Using(_ => results.Select((a, i) => - SqlPreCommand.Combine(Spacing.Simple, - !a.snapshot_isolation_state || !a.is_read_committed_snapshot_on ? DisconnectUsers(a.name!/*CSBUG*/, "SPID" + i) : null, - !a.snapshot_isolation_state ? sqlBuilder.SetSnapshotIsolation(a.name!/*CSBUG*/, true) : null, - !a.is_read_committed_snapshot_on ? sqlBuilder.MakeSnapshotIsolationDefault(a.name!/*CSBUG*/, true) : null)).Combine(Spacing.Double)); - - if (cmd == null) - return null; + list.Remove(null); + list.Add(Connector.Current.DatabaseName()); + } - return SqlPreCommand.Combine(Spacing.Double, - new SqlPreCommandSimple("use master -- Start Snapshot"), - cmd, - new SqlPreCommandSimple("use {0} -- Stop Snapshot".FormatWith(Connector.Current.DatabaseName()))); - } + var results = Database.View() + .Where(d => list.Contains(d.name)) + .Select(d => new + { + name = new DatabaseName(null, d.name, Connector.Current.Schema.Settings.IsPostgres), + d.snapshot_isolation_state, + d.is_read_committed_snapshot_on + }).ToList(); + + var cmd = replacements.WithReplacedDatabaseName().Using(_ => results.Select((a, i) => + SqlPreCommand.Combine(Spacing.Simple, + !a.snapshot_isolation_state || !a.is_read_committed_snapshot_on ? DisconnectUsers(a.name!/*CSBUG*/, "SPID" + i) : null, + !a.snapshot_isolation_state ? sqlBuilder.SetSnapshotIsolation(a.name!/*CSBUG*/, true) : null, + !a.is_read_committed_snapshot_on ? sqlBuilder.MakeSnapshotIsolationDefault(a.name!/*CSBUG*/, true) : null)).Combine(Spacing.Double)); + + if (cmd == null) + return null; + + return SqlPreCommand.Combine(Spacing.Double, + new SqlPreCommandSimple("use master -- Start Snapshot"), + cmd, + new SqlPreCommandSimple("use {0} -- Stop Snapshot".FormatWith(Connector.Current.DatabaseName()))); + } - public static SqlPreCommandSimple DisconnectUsers(DatabaseName databaseName, string variableName) - { - return new SqlPreCommandSimple(@"DECLARE @{1} VARCHAR(7000) + public static SqlPreCommandSimple DisconnectUsers(DatabaseName databaseName, string variableName) + { + return new SqlPreCommandSimple(@"DECLARE @{1} VARCHAR(7000) SELECT @{1} = COALESCE(@{1},'')+'KILL '+CAST(SPID AS VARCHAR)+'; 'FROM master..SysProcesses WHERE DB_NAME(DBId) = '{0}' EXEC(@{1})".FormatWith(databaseName.Name, variableName)); + } } -} #pragma warning disable CS8618 // Non-nullable field is uninitialized. -public class DiffPeriod -{ - public string StartColumnName; - public string EndColumnName; - - internal bool PeriodEquals(SystemVersionedInfo systemVersioned) + public class DiffPeriod { - return systemVersioned.StartColumnName == StartColumnName && - systemVersioned.EndColumnName == EndColumnName; + public string StartColumnName; + public string EndColumnName; + + internal bool PeriodEquals(SystemVersionedInfo systemVersioned) + { + return systemVersioned.StartColumnName == StartColumnName && + systemVersioned.EndColumnName == EndColumnName; + } } -} -public class DiffTable -{ - public ObjectName Name; + public class DiffTable + { + public ObjectName Name; - public ObjectName? PrimaryKeyName; + public ObjectName? PrimaryKeyName; - public Dictionary Columns; + public Dictionary Columns; - public List SimpleIndices - { - get { return Indices.Values.ToList(); } - set { Indices.AddRange(value, a => a.IndexName, a => a); } - } + public List SimpleIndices + { + get { return Indices.Values.ToList(); } + set { Indices.AddRange(value, a => a.IndexName, a => a); } + } - public List ViewIndices - { - get { return Indices.Values.ToList(); } - set { Indices.AddRange(value, a => a.IndexName, a => a); } - } + public List ViewIndices + { + get { return Indices.Values.ToList(); } + set { Indices.AddRange(value, a => a.IndexName, a => a); } + } - public SysTableTemporalType TemporalType; - public ObjectName? TemporalTableName; - public DiffPeriod? Period; + public SysTableTemporalType TemporalType; + public ObjectName? TemporalTableName; + public DiffPeriod? Period; - public Dictionary Indices = new Dictionary(); + public Dictionary Indices = new Dictionary(); - public List Stats = new List(); + public List Stats = new List(); - public List MultiForeignKeys = new List(); + public List MultiForeignKeys = new List(); - public void ForeignKeysToColumns() - { - foreach (var fk in MultiForeignKeys.Where(a => a.Columns.Count == 1).ToList()) + public void ForeignKeysToColumns() { - this.Columns[fk.Columns.SingleEx().Parent].ForeignKey = fk; - MultiForeignKeys.Remove(fk); + foreach (var fk in MultiForeignKeys.Where(a => a.Columns.Count == 1).ToList()) + { + this.Columns[fk.Columns.SingleEx().Parent].ForeignKey = fk; + MultiForeignKeys.Remove(fk); + } } - } - public override string ToString() - { - return Name.ToString(); - } + public override string ToString() + { + return Name.ToString(); + } - internal void FixSqlColumnLengthSqlServer() - { - foreach (var c in Columns.Values.Where(c => c.Length != -1)) + internal void FixSqlColumnLengthSqlServer() { - var sqlDbType = c.DbType.SqlServer; - if (sqlDbType == SqlDbType.NChar || sqlDbType == SqlDbType.NText || sqlDbType == SqlDbType.NVarChar) - c.Length /= 2; + foreach (var c in Columns.Values.Where(c => c.Length != -1)) + { + var sqlDbType = c.DbType.SqlServer; + if (sqlDbType == SqlDbType.NChar || sqlDbType == SqlDbType.NText || sqlDbType == SqlDbType.NVarChar) + c.Length /= 2; + } } } -} - -public enum SysTableTemporalType -{ - None = 0, - HistoryTable = 1, - SystemVersionTemporalTable = 2 -} -public class DiffStats -{ - public string StatsName; - - public List Columns; -} - -public class DiffIndexColumn -{ - public string ColumnName; - public bool IsIncluded; -} + public enum SysTableTemporalType + { + None = 0, + HistoryTable = 1, + SystemVersionTemporalTable = 2 + } -public class DiffIndex -{ - public bool IsUnique; - public bool IsPrimary; - public string IndexName; - public string ViewName; - public string? FilterDefinition; - public DiffIndexType? Type; + public class DiffStats + { + public string StatsName; - public List Columns; + public List Columns; + } - public override string ToString() + public class DiffIndexColumn { - return "{0} ({1})".FormatWith(IndexName, Columns.ToString(", ")); + public string ColumnName; + public bool IsIncluded; } - internal bool IndexEquals(DiffTable dif, Maps.TableIndex mix) + public class DiffIndex { - if (this.ViewName != (mix as UniqueTableIndex)?.ViewName) - return false; + public bool IsUnique; + public bool IsPrimary; + public string IndexName; + public string ViewName; + public string? FilterDefinition; + public DiffIndexType? Type; - if (this.ColumnsChanged(dif, mix)) - return false; + public List Columns; - if (this.IsPrimary != mix is PrimaryKeyIndex) - return false; + public override string ToString() + { + return "{0} ({1})".FormatWith(IndexName, Columns.ToString(", ")); + } - if (this.Type != GetIndexType(mix)) - return false; + internal bool IndexEquals(DiffTable dif, Maps.TableIndex mix) + { + if (this.ViewName != (mix as UniqueTableIndex)?.ViewName) + return false; - return true; - } + if (this.ColumnsChanged(dif, mix)) + return false; - private static DiffIndexType? GetIndexType(TableIndex mix) - { - if (mix is UniqueTableIndex && ((UniqueTableIndex)mix).ViewName != null) - return null; + if (this.IsPrimary != mix is PrimaryKeyIndex) + return false; - if (mix is PrimaryKeyIndex) - return Schema.Current.Settings.IsPostgres ? DiffIndexType.NonClustered : DiffIndexType.Clustered; + if (this.Type != GetIndexType(mix)) + return false; - return DiffIndexType.NonClustered; - } + return true; + } - bool ColumnsChanged(DiffTable dif, TableIndex mix) - { - bool sameCols = IdenticalColumns(dif, mix.Columns, this.Columns.Where(a => !a.IsIncluded).ToList()); - bool sameIncCols = IdenticalColumns(dif, mix.IncludeColumns, this.Columns.Where(a => a.IsIncluded).ToList()); + private static DiffIndexType? GetIndexType(TableIndex mix) + { + if (mix is UniqueTableIndex && ((UniqueTableIndex)mix).ViewName != null) + return null; - if (sameCols && sameIncCols) - return false; + if (mix is PrimaryKeyIndex) + return Schema.Current.Settings.IsPostgres ? DiffIndexType.NonClustered : DiffIndexType.Clustered; - return true; - } + return DiffIndexType.NonClustered; + } - private static bool IdenticalColumns(DiffTable dif, IColumn[]? modColumns, List diffColumns) - { - if ((modColumns?.Length ?? 0) != diffColumns.Count) - return false; + bool ColumnsChanged(DiffTable dif, TableIndex mix) + { + bool sameCols = IdenticalColumns(dif, mix.Columns, this.Columns.Where(a => !a.IsIncluded).ToList()); + bool sameIncCols = IdenticalColumns(dif, mix.IncludeColumns, this.Columns.Where(a => a.IsIncluded).ToList()); + + if (sameCols && sameIncCols) + return false; - if (diffColumns.Count == 0) return true; + } + + private static bool IdenticalColumns(DiffTable dif, IColumn[]? modColumns, List diffColumns) + { + if ((modColumns?.Length ?? 0) != diffColumns.Count) + return false; - var difColumns = diffColumns.Select(cn => dif.Columns.Values.SingleOrDefault(dc => dc.Name == cn.ColumnName)).ToList(); //Ny old name + if (diffColumns.Count == 0) + return true; - var perfect = difColumns.ZipOrDefault(modColumns!, (dc, mc) => dc != null && mc != null && dc.ColumnEquals(mc, ignorePrimaryKey: true, ignoreIdentity: true, ignoreGenerateAlways: true)).All(a => a); - return perfect; - } + var difColumns = diffColumns.Select(cn => dif.Columns.Values.SingleOrDefault(dc => dc.Name == cn.ColumnName)).ToList(); //Ny old name - public bool IsControlledIndex - { - get { return IndexName.StartsWith("IX_") || IndexName.StartsWith("UIX_"); } + var perfect = difColumns.ZipOrDefault(modColumns!, (dc, mc) => dc != null && mc != null && dc.ColumnEquals(mc, ignorePrimaryKey: true, ignoreIdentity: true, ignoreGenerateAlways: true)).All(a => a); + return perfect; + } + + public bool IsControlledIndex + { + get { return IndexName.StartsWith("IX_") || IndexName.StartsWith("UIX_"); } + } } -} - -public enum DiffIndexType -{ - Heap = 0, - Clustered = 1, - NonClustered = 2, - Xml = 3, - Spatial = 4, - ClusteredColumnstore = 5, - NonClusteredColumnstore = 6, - NonClusteredHash = 7, -} - -public enum GeneratedAlwaysType -{ - None = 0, - AsRowStart = 1, - AsRowEnd = 2 -} - -public class DiffDefaultConstraint -{ - public string? Name; - public string Definition; -} - -public class DiffColumn -{ - public string Name; - public AbstractDbType DbType; - public string? UserTypeName; - public bool Nullable; - public string? Collation; - public int Length; - public int Precision; - public int Scale; - public bool Identity; - public bool PrimaryKey; - - public DiffForeignKey? ForeignKey; - - public DiffDefaultConstraint? DefaultConstraint; - - public GeneratedAlwaysType GeneratedAlwaysType; - - public bool ColumnEquals(IColumn other, bool ignorePrimaryKey, bool ignoreIdentity, bool ignoreGenerateAlways) + + public enum DiffIndexType { - var result = DbType.Equals(other.DbType) - && Collation == other.Collation - && StringComparer.InvariantCultureIgnoreCase.Equals(UserTypeName, other.UserDefinedTypeName) - && Nullable == (other.Nullable.ToBool()) - && SizeEquals(other) - && ScaleEquals(other) - && (ignoreIdentity || Identity == other.Identity) - && (ignorePrimaryKey || PrimaryKey == other.PrimaryKey) - && (ignoreGenerateAlways || GeneratedAlwaysType == other.GetGeneratedAlwaysType()); - - if (!result) - return false; - - return result; + Heap = 0, + Clustered = 1, + NonClustered = 2, + Xml = 3, + Spatial = 4, + ClusteredColumnstore = 5, + NonClusteredColumnstore = 6, + NonClusteredHash = 7, } - public bool ScaleEquals(IColumn other) + public enum GeneratedAlwaysType { - return (other.Scale == null || other.Scale.Value == Scale); + None = 0, + AsRowStart = 1, + AsRowEnd = 2 } - public bool SizeEquals(IColumn other) + public class DiffDefaultConstraint { - return (other.Size == null || other.Size.Value == Precision || other.Size.Value == Length || other.Size.Value == int.MaxValue && Length == -1); + public string? Name; + public string Definition; } - public bool DefaultEquals(IColumn other) + public class DiffColumn { - if (other.Default == null && this.DefaultConstraint == null) - return true; + public string Name; + public AbstractDbType DbType; + public string? UserTypeName; + public bool Nullable; + public string? Collation; + public int Length; + public int Precision; + public int Scale; + public bool Identity; + public bool PrimaryKey; - var result = CleanParenthesis(this.DefaultConstraint?.Definition) == CleanParenthesis(other.Default); + public DiffForeignKey? ForeignKey; - return result; - } + public DiffDefaultConstraint? DefaultConstraint; - private string? CleanParenthesis(string? p) - { - if (p == null) - return null; + public GeneratedAlwaysType GeneratedAlwaysType; - while ( - p.StartsWith("(") && p.EndsWith(")") || - p.StartsWith("'") && p.EndsWith("'")) - p = p.Substring(1, p.Length - 2); + public bool ColumnEquals(IColumn other, bool ignorePrimaryKey, bool ignoreIdentity, bool ignoreGenerateAlways) + { + var result = DbType.Equals(other.DbType) + && Collation == other.Collation + && StringComparer.InvariantCultureIgnoreCase.Equals(UserTypeName, other.UserDefinedTypeName) + && Nullable == (other.Nullable.ToBool()) + && SizeEquals(other) + && PrecisionEquals(other) + && ScaleEquals(other) + && (ignoreIdentity || Identity == other.Identity) + && (ignorePrimaryKey || PrimaryKey == other.PrimaryKey) + && (ignoreGenerateAlways || GeneratedAlwaysType == other.GetGeneratedAlwaysType()); + + if (!result) + return false; + + return result; + } - return p.ToLower(); - } + public bool ScaleEquals(IColumn other) + { + return (other.Scale == null || other.Scale.Value == Scale); + } - public DiffColumn Clone() - { - return new DiffColumn + public bool SizeEquals(IColumn other) { - Name = Name, - ForeignKey = ForeignKey, - DefaultConstraint = DefaultConstraint?.Let(dc => new DiffDefaultConstraint { Name = dc.Name, Definition = dc.Definition }), - Identity = Identity, - Length = Length, - PrimaryKey = PrimaryKey, - Nullable = Nullable, - Precision = Precision, - Scale = Scale, - DbType = DbType, - UserTypeName = UserTypeName, - }; - } + return (other.DbType.IsDecimal() || other.Size == null || other.Size.Value == Precision || other.Size.Value == Length || other.Size.Value == int.MaxValue && Length == -1); + } - public override string ToString() - { - return this.Name; - } + public bool PrecisionEquals(IColumn other) + { + return (!other.DbType.IsDecimal() || other.Precision == null || other.Precision == 0 || other.Precision.Value == Precision); + } - internal bool CompatibleTypes(IColumn tabCol) - { - if (Schema.Current.Settings.IsPostgres) - return CompatibleTypes_Postgres(this.DbType.PostgreSql, tabCol.DbType.PostgreSql); - else - return CompatibleTypes_SqlServer(this.DbType.SqlServer, tabCol.DbType.SqlServer); - } + public bool DefaultEquals(IColumn other) + { + if (other.Default == null && this.DefaultConstraint == null) + return true; - private bool CompatibleTypes_Postgres(NpgsqlDbType fromType, NpgsqlDbType toType) - { - return true; - } + var result = CleanParenthesis(this.DefaultConstraint?.Definition) == CleanParenthesis(other.Default); - private bool CompatibleTypes_SqlServer(SqlDbType fromType, SqlDbType toType) - { - //https://docs.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql - switch (fromType) + return result; + } + + private string? CleanParenthesis(string? p) { - //BLACKLIST!! - case SqlDbType.Binary: - case SqlDbType.VarBinary: - switch (toType) - { - case SqlDbType.Float: - case SqlDbType.Real: - case SqlDbType.NText: - case SqlDbType.Text: - return false; - default: - return true; - } + if (p == null) + return null; - case SqlDbType.Char: - case SqlDbType.VarChar: - return true; + while ( + p.StartsWith("(") && p.EndsWith(")") || + p.StartsWith("'") && p.EndsWith("'")) + p = p.Substring(1, p.Length - 2); - case SqlDbType.NChar: - case SqlDbType.NVarChar: - return toType != SqlDbType.Image; + return p.ToLower(); + } - case SqlDbType.DateTime: - case SqlDbType.SmallDateTime: - switch (toType) - { - case SqlDbType.UniqueIdentifier: - case SqlDbType.Image: - case SqlDbType.NText: - case SqlDbType.Text: - case SqlDbType.Xml: - case SqlDbType.Udt: - return false; - default: - return true; - } + public DiffColumn Clone() + { + return new DiffColumn + { + Name = Name, + ForeignKey = ForeignKey, + DefaultConstraint = DefaultConstraint?.Let(dc => new DiffDefaultConstraint { Name = dc.Name, Definition = dc.Definition }), + Identity = Identity, + Length = Length, + PrimaryKey = PrimaryKey, + Nullable = Nullable, + Precision = Precision, + Scale = Scale, + DbType = DbType, + UserTypeName = UserTypeName, + }; + } - case SqlDbType.Date: - if (toType == SqlDbType.Time) - return false; - goto case SqlDbType.DateTime2; + public override string ToString() + { + return this.Name; + } - case SqlDbType.Time: - if (toType == SqlDbType.Date) - return false; - goto case SqlDbType.DateTime2; + internal bool CompatibleTypes(IColumn tabCol) + { + if (Schema.Current.Settings.IsPostgres) + return CompatibleTypes_Postgres(this.DbType.PostgreSql, tabCol.DbType.PostgreSql); + else + return CompatibleTypes_SqlServer(this.DbType.SqlServer, tabCol.DbType.SqlServer); + } - case SqlDbType.DateTimeOffset: - case SqlDbType.DateTime2: - switch (toType) - { - case SqlDbType.Decimal: - case SqlDbType.Float: - case SqlDbType.Real: - case SqlDbType.BigInt: - case SqlDbType.Int: - case SqlDbType.SmallInt: - case SqlDbType.TinyInt: - case SqlDbType.Money: - case SqlDbType.SmallMoney: - case SqlDbType.Bit: - case SqlDbType.UniqueIdentifier: - case SqlDbType.Image: - case SqlDbType.NText: - case SqlDbType.Text: - case SqlDbType.Xml: - case SqlDbType.Udt: - return false; - default: - return true; - } + private bool CompatibleTypes_Postgres(NpgsqlDbType fromType, NpgsqlDbType toType) + { + return true; + } - case SqlDbType.Decimal: - case SqlDbType.Float: - case SqlDbType.Real: - case SqlDbType.BigInt: - case SqlDbType.Int: - case SqlDbType.SmallInt: - case SqlDbType.TinyInt: - case SqlDbType.Money: - case SqlDbType.SmallMoney: - case SqlDbType.Bit: - switch (toType) - { - case SqlDbType.Date: - case SqlDbType.Time: - case SqlDbType.DateTimeOffset: - case SqlDbType.DateTime2: - case SqlDbType.UniqueIdentifier: - case SqlDbType.Image: - case SqlDbType.NText: - case SqlDbType.Text: - case SqlDbType.Xml: - case SqlDbType.Udt: - return false; - default: - return true; - } + private bool CompatibleTypes_SqlServer(SqlDbType fromType, SqlDbType toType) + { + //https://docs.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql + switch (fromType) + { + //BLACKLIST!! + case SqlDbType.Binary: + case SqlDbType.VarBinary: + switch (toType) + { + case SqlDbType.Float: + case SqlDbType.Real: + case SqlDbType.NText: + case SqlDbType.Text: + return false; + default: + return true; + } - case SqlDbType.Timestamp: - switch (toType) - { - case SqlDbType.NChar: - case SqlDbType.NVarChar: - case SqlDbType.Date: - case SqlDbType.Time: - case SqlDbType.DateTimeOffset: - case SqlDbType.DateTime2: - case SqlDbType.UniqueIdentifier: - case SqlDbType.Image: - case SqlDbType.NText: - case SqlDbType.Text: - case SqlDbType.Xml: - case SqlDbType.Udt: - return false; - default: - return true; - } - case SqlDbType.Variant: - switch (toType) - { - case SqlDbType.Timestamp: - case SqlDbType.Image: - case SqlDbType.NText: - case SqlDbType.Text: - case SqlDbType.Xml: - case SqlDbType.Udt: - return false; - default: - return true; - } + case SqlDbType.Char: + case SqlDbType.VarChar: + return true; - //WHITELIST!! - case SqlDbType.UniqueIdentifier: - switch (toType) - { - case SqlDbType.Binary: - case SqlDbType.VarBinary: - case SqlDbType.Char: - case SqlDbType.VarChar: - case SqlDbType.NChar: - case SqlDbType.NVarChar: - case SqlDbType.UniqueIdentifier: - case SqlDbType.Variant: - return true; - default: - return false; - } - case SqlDbType.Image: - switch (toType) - { - case SqlDbType.Binary: - case SqlDbType.Image: - case SqlDbType.VarBinary: - case SqlDbType.Timestamp: - return true; - default: - return false; - } - case SqlDbType.NText: - case SqlDbType.Text: - switch (toType) - { - case SqlDbType.Char: - case SqlDbType.VarChar: - case SqlDbType.NChar: - case SqlDbType.NVarChar: - case SqlDbType.NText: - case SqlDbType.Text: - case SqlDbType.Xml: - return true; - default: + case SqlDbType.NChar: + case SqlDbType.NVarChar: + return toType != SqlDbType.Image; + + case SqlDbType.DateTime: + case SqlDbType.SmallDateTime: + switch (toType) + { + case SqlDbType.UniqueIdentifier: + case SqlDbType.Image: + case SqlDbType.NText: + case SqlDbType.Text: + case SqlDbType.Xml: + case SqlDbType.Udt: + return false; + default: + return true; + } + + case SqlDbType.Date: + if (toType == SqlDbType.Time) return false; - } - case SqlDbType.Xml: - case SqlDbType.Udt: - switch (toType) - { - case SqlDbType.Binary: - case SqlDbType.VarBinary: - case SqlDbType.Char: - case SqlDbType.VarChar: - case SqlDbType.NChar: - case SqlDbType.NVarChar: - case SqlDbType.Xml: - case SqlDbType.Udt: - return true; - default: + goto case SqlDbType.DateTime2; + + case SqlDbType.Time: + if (toType == SqlDbType.Date) return false; - } - default: - throw new NotImplementedException("Unexpected SqlDbType"); + goto case SqlDbType.DateTime2; + + case SqlDbType.DateTimeOffset: + case SqlDbType.DateTime2: + switch (toType) + { + case SqlDbType.Decimal: + case SqlDbType.Float: + case SqlDbType.Real: + case SqlDbType.BigInt: + case SqlDbType.Int: + case SqlDbType.SmallInt: + case SqlDbType.TinyInt: + case SqlDbType.Money: + case SqlDbType.SmallMoney: + case SqlDbType.Bit: + case SqlDbType.UniqueIdentifier: + case SqlDbType.Image: + case SqlDbType.NText: + case SqlDbType.Text: + case SqlDbType.Xml: + case SqlDbType.Udt: + return false; + default: + return true; + } + + case SqlDbType.Decimal: + case SqlDbType.Float: + case SqlDbType.Real: + case SqlDbType.BigInt: + case SqlDbType.Int: + case SqlDbType.SmallInt: + case SqlDbType.TinyInt: + case SqlDbType.Money: + case SqlDbType.SmallMoney: + case SqlDbType.Bit: + switch (toType) + { + case SqlDbType.Date: + case SqlDbType.Time: + case SqlDbType.DateTimeOffset: + case SqlDbType.DateTime2: + case SqlDbType.UniqueIdentifier: + case SqlDbType.Image: + case SqlDbType.NText: + case SqlDbType.Text: + case SqlDbType.Xml: + case SqlDbType.Udt: + return false; + default: + return true; + } + + case SqlDbType.Timestamp: + switch (toType) + { + case SqlDbType.NChar: + case SqlDbType.NVarChar: + case SqlDbType.Date: + case SqlDbType.Time: + case SqlDbType.DateTimeOffset: + case SqlDbType.DateTime2: + case SqlDbType.UniqueIdentifier: + case SqlDbType.Image: + case SqlDbType.NText: + case SqlDbType.Text: + case SqlDbType.Xml: + case SqlDbType.Udt: + return false; + default: + return true; + } + case SqlDbType.Variant: + switch (toType) + { + case SqlDbType.Timestamp: + case SqlDbType.Image: + case SqlDbType.NText: + case SqlDbType.Text: + case SqlDbType.Xml: + case SqlDbType.Udt: + return false; + default: + return true; + } + + //WHITELIST!! + case SqlDbType.UniqueIdentifier: + switch (toType) + { + case SqlDbType.Binary: + case SqlDbType.VarBinary: + case SqlDbType.Char: + case SqlDbType.VarChar: + case SqlDbType.NChar: + case SqlDbType.NVarChar: + case SqlDbType.UniqueIdentifier: + case SqlDbType.Variant: + return true; + default: + return false; + } + case SqlDbType.Image: + switch (toType) + { + case SqlDbType.Binary: + case SqlDbType.Image: + case SqlDbType.VarBinary: + case SqlDbType.Timestamp: + return true; + default: + return false; + } + case SqlDbType.NText: + case SqlDbType.Text: + switch (toType) + { + case SqlDbType.Char: + case SqlDbType.VarChar: + case SqlDbType.NChar: + case SqlDbType.NVarChar: + case SqlDbType.NText: + case SqlDbType.Text: + case SqlDbType.Xml: + return true; + default: + return false; + } + case SqlDbType.Xml: + case SqlDbType.Udt: + switch (toType) + { + case SqlDbType.Binary: + case SqlDbType.VarBinary: + case SqlDbType.Char: + case SqlDbType.VarChar: + case SqlDbType.NChar: + case SqlDbType.NVarChar: + case SqlDbType.Xml: + case SqlDbType.Udt: + return true; + default: + return false; + } + default: + throw new NotImplementedException("Unexpected SqlDbType"); + } } } -} - -public class DiffForeignKey -{ - public ObjectName Name; - public ObjectName TargetTable; - public bool IsDisabled; - public bool IsNotTrusted; - public List Columns; -} - -public class DiffForeignKeyColumn -{ - public string Parent; - public string Referenced; -} + + public class DiffForeignKey + { + public ObjectName Name; + public ObjectName TargetTable; + public bool IsDisabled; + public bool IsNotTrusted; + public List Columns; + } + + public class DiffForeignKeyColumn + { + public string Parent; + public string Referenced; + } #pragma warning restore CS8618 // Non-nullable field is uninitialized. diff --git a/Signum.Engine/Engine/SqlBuilder.cs b/Signum.Engine/Engine/SqlBuilder.cs index 86aed058ef..e420db33e7 100644 --- a/Signum.Engine/Engine/SqlBuilder.cs +++ b/Signum.Engine/Engine/SqlBuilder.cs @@ -5,689 +5,699 @@ namespace Signum.Engine; -public class SqlBuilder -{ - Connector connector; - bool isPostgres; + public class SqlBuilder + { + Connector connector; + bool isPostgres; - public bool IsPostgres => isPostgres; + public bool IsPostgres => isPostgres; - internal SqlBuilder(Connector connector) - { - this.connector = connector; - this.isPostgres = connector.Schema.Settings.IsPostgres; - } + internal SqlBuilder(Connector connector) + { + this.connector = connector; + this.isPostgres = connector.Schema.Settings.IsPostgres; + } - public List SystemSchemas = new List() - { - "dbo", - "guest", - "INFORMATION_SCHEMA", - "sys", - "db_owner", - "db_accessadmin", - "db_securityadmin", - "db_ddladmin", - "db_backupoperator", - "db_datareader", - "db_datawriter", - "db_denydatareader", - "db_denydatawriter" - }; - - public SqlPreCommandSimple? UseDatabase(string? databaseName = null) - { - if (Schema.Current.Settings.IsPostgres) - return null; + public List SystemSchemas = new List() + { + "dbo", + "guest", + "INFORMATION_SCHEMA", + "sys", + "db_owner", + "db_accessadmin", + "db_securityadmin", + "db_ddladmin", + "db_backupoperator", + "db_datareader", + "db_datawriter", + "db_denydatareader", + "db_denydatawriter" + }; + + public SqlPreCommandSimple? UseDatabase(string? databaseName = null) + { + if (Schema.Current.Settings.IsPostgres) + return null; - return new SqlPreCommandSimple("use {0}".FormatWith((databaseName ?? Connector.Current.DatabaseName()).SqlEscape(Schema.Current.Settings.IsPostgres))); - } + return new SqlPreCommandSimple("use {0}".FormatWith((databaseName ?? Connector.Current.DatabaseName()).SqlEscape(Schema.Current.Settings.IsPostgres))); + } - #region Create Tables - public SqlPreCommand CreateTableSql(ITable t, ObjectName? tableName = null, bool avoidSystemVersioning = false) - { - var primaryKeyConstraint = t.PrimaryKey == null || t.SystemVersioned != null && tableName != null && t.SystemVersioned.TableName.Equals(tableName) ? null : - isPostgres ? - "CONSTRAINT {0} PRIMARY KEY ({1})".FormatWith(PrimaryKeyIndex.GetPrimaryKeyName(t.Name).SqlEscape(isPostgres), t.PrimaryKey.Name.SqlEscape(isPostgres)) : - "CONSTRAINT {0} PRIMARY KEY CLUSTERED ({1} ASC)".FormatWith(PrimaryKeyIndex.GetPrimaryKeyName(t.Name).SqlEscape(isPostgres), t.PrimaryKey.Name.SqlEscape(isPostgres)); + #region Create Tables + public SqlPreCommand CreateTableSql(ITable t, ObjectName? tableName = null, bool avoidSystemVersioning = false) + { + var primaryKeyConstraint = t.PrimaryKey == null || t.SystemVersioned != null && tableName != null && t.SystemVersioned.TableName.Equals(tableName) ? null : + isPostgres ? + "CONSTRAINT {0} PRIMARY KEY ({1})".FormatWith(PrimaryKeyIndex.GetPrimaryKeyName(t.Name).SqlEscape(isPostgres), t.PrimaryKey.Name.SqlEscape(isPostgres)) : + "CONSTRAINT {0} PRIMARY KEY CLUSTERED ({1} ASC)".FormatWith(PrimaryKeyIndex.GetPrimaryKeyName(t.Name).SqlEscape(isPostgres), t.PrimaryKey.Name.SqlEscape(isPostgres)); - var systemPeriod = t.SystemVersioned == null || IsPostgres || avoidSystemVersioning ? null : Period(t.SystemVersioned); + var systemPeriod = t.SystemVersioned == null || IsPostgres || avoidSystemVersioning ? null : Period(t.SystemVersioned); - var columns = t.Columns.Values.Select(c => this.ColumnLine(c, GetDefaultConstaint(t, c), isChange: false, forHistoryTable: avoidSystemVersioning)) - .And(primaryKeyConstraint) - .And(systemPeriod) - .NotNull() - .ToString(",\r\n"); + var columns = t.Columns.Values.Select(c => this.ColumnLine(c, GetDefaultConstaint(t, c), isChange: false, forHistoryTable: avoidSystemVersioning)) + .And(primaryKeyConstraint) + .And(systemPeriod) + .NotNull() + .ToString(",\r\n"); - var systemVersioning = t.SystemVersioned == null || avoidSystemVersioning || IsPostgres ? null : - $"\r\nWITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = {t.SystemVersioned.TableName.OnDatabase(null)}))"; + var systemVersioning = t.SystemVersioned == null || avoidSystemVersioning || IsPostgres ? null : + $"\r\nWITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = {t.SystemVersioned.TableName.OnDatabase(null)}))"; - var result = new SqlPreCommandSimple($"CREATE {(IsPostgres && t.Name.IsTemporal ? "TEMPORARY " : "")}TABLE {tableName ?? t.Name}(\r\n{columns}\r\n)" + systemVersioning + ";"); + var result = new SqlPreCommandSimple($"CREATE {(IsPostgres && t.Name.IsTemporal ? "TEMPORARY " : "")}TABLE {tableName ?? t.Name}(\r\n{columns}\r\n)" + systemVersioning + ";"); - if (!(IsPostgres && t.SystemVersioned != null)) - return result; + if (!(IsPostgres && t.SystemVersioned != null)) + return result; - return new[] - { - result, - new SqlPreCommandSimple($"CREATE TABLE {t.SystemVersioned.TableName}(LIKE {t.Name});"), - new SqlPreCommandSimple(@$"CREATE TRIGGER versioning_trigger + return new[] + { + result, + new SqlPreCommandSimple($"CREATE TABLE {t.SystemVersioned.TableName}(LIKE {t.Name});"), + new SqlPreCommandSimple(@$"CREATE TRIGGER versioning_trigger BEFORE INSERT OR UPDATE OR DELETE ON {t.Name} FOR EACH ROW EXECUTE PROCEDURE versioning( 'sys_period', '{t.SystemVersioned.TableName}', true );") - }.Combine(Spacing.Simple)!; - - } + }.Combine(Spacing.Simple)!; - public SqlPreCommand DropTable(DiffTable diffTable) - { - if (diffTable.TemporalTableName == null) - return DropTable(diffTable.Name); + } - return SqlPreCommandConcat.Combine(Spacing.Simple, - AlterTableDisableSystemVersioning(diffTable.Name), - DropTable(diffTable.Name) - )!; - } + public SqlPreCommand DropTable(DiffTable diffTable) + { + if (diffTable.TemporalTableName == null) + return DropTable(diffTable.Name); - public SqlPreCommandSimple DropTable(ObjectName tableName) - { - return new SqlPreCommandSimple("DROP TABLE {0};".FormatWith(tableName)); - } + return SqlPreCommandConcat.Combine(Spacing.Simple, + AlterTableDisableSystemVersioning(diffTable.Name), + DropTable(diffTable.Name) + )!; + } - public SqlPreCommandSimple DropView(ObjectName viewName) - { - return new SqlPreCommandSimple("DROP VIEW {0};".FormatWith(viewName)); - } + public SqlPreCommandSimple DropTable(ObjectName tableName) + { + return new SqlPreCommandSimple("DROP TABLE {0};".FormatWith(tableName)); + } - public SqlPreCommandSimple CreateExtensionIfNotExist(string extensionName) - { - return new SqlPreCommandSimple($"CREATE EXTENSION IF NOT EXISTS \"{ extensionName }\";"); - } + public SqlPreCommandSimple DropView(ObjectName viewName) + { + return new SqlPreCommandSimple("DROP VIEW {0};".FormatWith(viewName)); + } - SqlPreCommand DropViewIndex(ObjectName viewName, string index) - { - return new[]{ - DropIndex(viewName, index), - DropView(viewName) - }.Combine(Spacing.Simple)!; - } + public SqlPreCommandSimple CreateExtensionIfNotExist(string extensionName) + { + return new SqlPreCommandSimple($"CREATE EXTENSION IF NOT EXISTS \"{ extensionName }\";"); + } - public SqlPreCommand AlterTableAddPeriod(ITable table) - { - return new SqlPreCommandSimple($"ALTER TABLE {table.Name} ADD {Period(table.SystemVersioned!)};"); - } + SqlPreCommand DropViewIndex(ObjectName viewName, string index) + { + return new[]{ + DropIndex(viewName, index), + DropView(viewName) + }.Combine(Spacing.Simple)!; + } - string? Period(SystemVersionedInfo sv) { + public SqlPreCommand AlterTableAddPeriod(ITable table) + { + return new SqlPreCommandSimple($"ALTER TABLE {table.Name} ADD {Period(table.SystemVersioned!)};"); + } - if (!Connector.Current.SupportsTemporalTables) - throw new InvalidOperationException($"The current connector '{Connector.Current}' does not support Temporal Tables"); + string? Period(SystemVersionedInfo sv) { - return $"PERIOD FOR SYSTEM_TIME ({sv.StartColumnName!.SqlEscape(isPostgres)}, {sv.EndColumnName!.SqlEscape(isPostgres)})"; - } + if (!Connector.Current.SupportsTemporalTables) + throw new InvalidOperationException($"The current connector '{Connector.Current}' does not support Temporal Tables"); - public SqlPreCommand AlterTableDropPeriod(ITable table) - { - return new SqlPreCommandSimple($"ALTER TABLE {table.Name} DROP PERIOD FOR SYSTEM_TIME;"); - } + return $"PERIOD FOR SYSTEM_TIME ({sv.StartColumnName!.SqlEscape(isPostgres)}, {sv.EndColumnName!.SqlEscape(isPostgres)})"; + } - public SqlPreCommand AlterTableEnableSystemVersioning(ITable table) - { - return new SqlPreCommandSimple($"ALTER TABLE {table.Name} SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = {table.SystemVersioned!.TableName.OnDatabase(null)}));"); - } + public SqlPreCommand AlterTableDropPeriod(ITable table) + { + return new SqlPreCommandSimple($"ALTER TABLE {table.Name} DROP PERIOD FOR SYSTEM_TIME;"); + } - public SqlPreCommandSimple AlterTableDisableSystemVersioning(ObjectName tableName) - { - return new SqlPreCommandSimple($"ALTER TABLE {tableName} SET (SYSTEM_VERSIONING = OFF);"); - } + public SqlPreCommand AlterTableEnableSystemVersioning(ITable table) + { + return new SqlPreCommandSimple($"ALTER TABLE {table.Name} SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = {table.SystemVersioned!.TableName.OnDatabase(null)}));"); + } - public SqlPreCommand AlterTableDropColumn(ITable table, string columnName) - { - return new SqlPreCommandSimple("ALTER TABLE {0} DROP COLUMN {1};".FormatWith(table.Name, columnName.SqlEscape(isPostgres))); - } + public SqlPreCommandSimple AlterTableDisableSystemVersioning(ObjectName tableName) + { + return new SqlPreCommandSimple($"ALTER TABLE {tableName} SET (SYSTEM_VERSIONING = OFF);"); + } - public SqlPreCommand AlterTableAddColumn(ITable table, IColumn column, SqlBuilder.DefaultConstraint? tempDefault = null) - { - return new SqlPreCommandSimple("ALTER TABLE {0} ADD {1};".FormatWith(table.Name, ColumnLine(column, tempDefault ?? GetDefaultConstaint(table, column), isChange: false))); - } + public SqlPreCommand AlterTableDropColumn(ITable table, string columnName) + { + return new SqlPreCommandSimple("ALTER TABLE {0} DROP COLUMN {1};".FormatWith(table.Name, columnName.SqlEscape(isPostgres))); + } - public SqlPreCommand AlterTableAddOldColumn(ITable table, DiffColumn column) - { - return new SqlPreCommandSimple("ALTER TABLE {0} ADD {1};".FormatWith(table.Name, CreateOldColumn(column))); - } + public SqlPreCommand AlterTableAddColumn(ITable table, IColumn column, SqlBuilder.DefaultConstraint? tempDefault = null) + { + return new SqlPreCommandSimple("ALTER TABLE {0} ADD {1};".FormatWith(table.Name, ColumnLine(column, tempDefault ?? GetDefaultConstaint(table, column), isChange: false))); + } - public SqlPreCommand AlterTableAlterColumn(ITable table, IColumn column, DiffColumn diffColumn, ObjectName? forceTableName = null) - { - var tableName = forceTableName ?? table.Name; - - var alterColumn = !IsPostgres ? - new SqlPreCommandSimple("ALTER TABLE {0} ALTER COLUMN {1};".FormatWith(tableName, this.ColumnLine(column, null, isChange: true))) : - new[] - { - !diffColumn.DbType.Equals(column.DbType) || diffColumn.Collation != column.Collation || !diffColumn.ScaleEquals(column) || !diffColumn.SizeEquals(column) ? - new SqlPreCommandSimple("ALTER TABLE {0} ALTER COLUMN {1} TYPE {2};".FormatWith(tableName, column.Name.SqlEscape(isPostgres), GetColumnType(column) + (column.Collation != null ? " COLLATE " + column.Collation : null))) : null, - diffColumn.Nullable && !column.Nullable.ToBool()? new SqlPreCommandSimple("ALTER TABLE {0} ALTER COLUMN {1} SET NOT NULL;".FormatWith(tableName, column.Name.SqlEscape(isPostgres))) : null, - !diffColumn.Nullable && column.Nullable.ToBool()? new SqlPreCommandSimple("ALTER TABLE {0} ALTER COLUMN {1} DROP NOT NULL;".FormatWith(tableName, column.Name.SqlEscape(isPostgres))) : null, - }.Combine(Spacing.Simple) ?? new SqlPreCommandSimple("ALTER TABLE {0} ALTER COLUMN {1} -- UNEXPECTED COLUMN CHANGE!!".FormatWith(tableName, column.Name.SqlEscape(isPostgres))); - - if (column.Default == null) - return alterColumn; - - var defCons = GetDefaultConstaint(table, column)!; - - return SqlPreCommand.Combine(Spacing.Simple, - AlterTableDropConstraint(table.Name, diffColumn.DefaultConstraint?.Name ?? defCons.Name), - alterColumn, - AlterTableAddDefaultConstraint(table.Name, defCons) - )!; - } + public SqlPreCommand AlterTableAddOldColumn(ITable table, DiffColumn column) + { + return new SqlPreCommandSimple("ALTER TABLE {0} ADD {1};".FormatWith(table.Name, CreateOldColumn(column))); + } - public DefaultConstraint? GetDefaultConstaint(ITable t, IColumn c) - { - if (c.Default == null) - return null; + public SqlPreCommand AlterTableAlterColumn(ITable table, IColumn column, DiffColumn diffColumn, ObjectName? forceTableName = null) + { + var tableName = forceTableName ?? table.Name; + + var alterColumn = !IsPostgres ? + new SqlPreCommandSimple("ALTER TABLE {0} ALTER COLUMN {1};".FormatWith(tableName, this.ColumnLine(column, null, isChange: true))) : + new[] + { + !diffColumn.DbType.Equals(column.DbType) || diffColumn.Collation != column.Collation || !diffColumn.ScaleEquals(column) || !diffColumn.SizeEquals(column) ? + new SqlPreCommandSimple("ALTER TABLE {0} ALTER COLUMN {1} TYPE {2};".FormatWith(tableName, column.Name.SqlEscape(isPostgres), GetColumnType(column) + (column.Collation != null ? " COLLATE " + column.Collation : null))) : null, + diffColumn.Nullable && !column.Nullable.ToBool()? new SqlPreCommandSimple("ALTER TABLE {0} ALTER COLUMN {1} SET NOT NULL;".FormatWith(tableName, column.Name.SqlEscape(isPostgres))) : null, + !diffColumn.Nullable && column.Nullable.ToBool()? new SqlPreCommandSimple("ALTER TABLE {0} ALTER COLUMN {1} DROP NOT NULL;".FormatWith(tableName, column.Name.SqlEscape(isPostgres))) : null, + }.Combine(Spacing.Simple) ?? new SqlPreCommandSimple("ALTER TABLE {0} ALTER COLUMN {1} -- UNEXPECTED COLUMN CHANGE!!".FormatWith(tableName, column.Name.SqlEscape(isPostgres))); + + if (column.Default == null) + return alterColumn; + + var defCons = GetDefaultConstaint(table, column)!; + + return SqlPreCommand.Combine(Spacing.Simple, + AlterTableDropConstraint(table.Name, diffColumn.DefaultConstraint?.Name ?? defCons.Name), + alterColumn, + AlterTableAddDefaultConstraint(table.Name, defCons) + )!; + } - return new DefaultConstraint(c.Name, $"DF_{t.Name.Name}_{c.Name}", Quote(c.DbType, c.Default)); - } + public DefaultConstraint? GetDefaultConstaint(ITable t, IColumn c) + { + if (c.Default == null) + return null; - public class DefaultConstraint - { - public string ColumnName; - public string Name; - public string QuotedDefinition; + return new DefaultConstraint(c.Name, $"DF_{t.Name.Name}_{c.Name}", Quote(c.DbType, c.Default)); + } - public DefaultConstraint(string columnName, string name, string quotedDefinition) + public class DefaultConstraint { - ColumnName = columnName; - Name = name; - QuotedDefinition = quotedDefinition; - } - } + public string ColumnName; + public string Name; + public string QuotedDefinition; - public string CreateOldColumn(DiffColumn c) - { - string fullType = GetColumnType(c); - - var generatedAlways = c.GeneratedAlwaysType != GeneratedAlwaysType.None ? - $"GENERATED ALWAYS AS ROW {(c.GeneratedAlwaysType == GeneratedAlwaysType.AsRowStart ? "START" : "END")} HIDDEN" : - null; - - var defaultConstraint = c.DefaultConstraint!= null ? $"CONSTRAINT {c.DefaultConstraint.Name} DEFAULT " + c.DefaultConstraint.Definition : null; - - return $" ".Combine( - c.Name.SqlEscape(isPostgres), - fullType, - c.Identity ? "IDENTITY " : null, - generatedAlways, - c.Collation != null ? ("COLLATE " + c.Collation) : null, - c.Nullable ? "NULL" : "NOT NULL", - defaultConstraint - ); - } + public DefaultConstraint(string columnName, string name, string quotedDefinition) + { + ColumnName = columnName; + Name = name; + QuotedDefinition = quotedDefinition; + } + } - public string ColumnLine(IColumn c, DefaultConstraint? constraint, bool isChange, bool forHistoryTable = false) - { - string fullType = GetColumnType(c); - - var generatedAlways = c is SystemVersionedInfo.SqlServerPeriodColumn svc && !forHistoryTable ? - $"GENERATED ALWAYS AS ROW {(svc.SystemVersionColumnType == SystemVersionedInfo.ColumnType.Start ? "START" : "END")} HIDDEN" : - null; - - var defaultConstraint = constraint != null ? $"CONSTRAINT {constraint.Name} DEFAULT " + constraint.QuotedDefinition : null; - - return $" ".Combine( - c.Name.SqlEscape(isPostgres), - fullType, - c.Identity && !isChange && !forHistoryTable ? (isPostgres? "GENERATED ALWAYS AS IDENTITY": "IDENTITY") : null, - generatedAlways, - c.Collation != null ? ("COLLATE " + c.Collation) : null, - c.Nullable.ToBool() ? "NULL" : "NOT NULL", - defaultConstraint - ); - } + public string CreateOldColumn(DiffColumn c) + { + string fullType = GetColumnType(c); + + var generatedAlways = c.GeneratedAlwaysType != GeneratedAlwaysType.None ? + $"GENERATED ALWAYS AS ROW {(c.GeneratedAlwaysType == GeneratedAlwaysType.AsRowStart ? "START" : "END")} HIDDEN" : + null; + + var defaultConstraint = c.DefaultConstraint!= null ? $"CONSTRAINT {c.DefaultConstraint.Name} DEFAULT " + c.DefaultConstraint.Definition : null; + + return $" ".Combine( + c.Name.SqlEscape(isPostgres), + fullType, + c.Identity ? "IDENTITY " : null, + generatedAlways, + c.Collation != null ? ("COLLATE " + c.Collation) : null, + c.Nullable ? "NULL" : "NOT NULL", + defaultConstraint + ); + } - public string GetColumnType(IColumn c) - { - return c.UserDefinedTypeName ?? (c.DbType.ToString(IsPostgres) + GetSizeScale(c.Size, c.Scale)); - } + public string ColumnLine(IColumn c, DefaultConstraint? constraint, bool isChange, bool forHistoryTable = false) + { + string fullType = GetColumnType(c); + + var generatedAlways = c is SystemVersionedInfo.SqlServerPeriodColumn svc && !forHistoryTable ? + $"GENERATED ALWAYS AS ROW {(svc.SystemVersionColumnType == SystemVersionedInfo.ColumnType.Start ? "START" : "END")} HIDDEN" : + null; + + var defaultConstraint = constraint != null ? $"CONSTRAINT {constraint.Name} DEFAULT " + constraint.QuotedDefinition : null; + + return $" ".Combine( + c.Name.SqlEscape(isPostgres), + fullType, + c.Identity && !isChange && !forHistoryTable ? (isPostgres? "GENERATED ALWAYS AS IDENTITY": "IDENTITY") : null, + generatedAlways, + c.Collation != null ? ("COLLATE " + c.Collation) : null, + c.Nullable.ToBool() ? "NULL" : "NOT NULL", + defaultConstraint + ); + } - public string GetColumnType(DiffColumn c) - { - return c.UserTypeName ?? c.DbType.ToString(IsPostgres) /*+ GetSizeScale(Math.Max(c.Length, c.Precision), c.Scale)*/; - } + public string GetColumnType(IColumn c) + { + return c.UserDefinedTypeName ?? (c.DbType.ToString(IsPostgres) + GetSizePrecisionScale(c)); + } - public string Quote(AbstractDbType type, string @default) - { - if (type.IsString() && !(@default.StartsWith("'") && @default.StartsWith("'"))) - return "'" + @default + "'"; + public string GetColumnType(DiffColumn c) + { + return c.UserTypeName ?? c.DbType.ToString(IsPostgres) /*+ GetSizeScale(Math.Max(c.Length, c.Precision), c.Scale)*/; + } - return @default; - } + public string GetSizePrecisionScale(IColumn c) + { + return GetSizePrecisionScale(c.Size, c.Precision, c.Scale, c.DbType.IsDecimal()); + } - public string GetSizeScale(int? size, int? scale) - { - if (size == null) - return ""; + public string GetSizePrecisionScale(int? size, byte? precision, byte? scale, bool isDecimal) + { + if (size == null && precision == null) + return ""; - if (size == int.MaxValue) - return IsPostgres ? "" : "(MAX)"; + if (isDecimal) + { + if (scale == null) + return "({0})".FormatWith(precision); + else + return "({0},{1})".FormatWith(precision, scale); + } - if (scale == null) - return "({0})".FormatWith(size); + if (size == int.MaxValue) + return IsPostgres ? "" : "(MAX)"; - return "({0},{1})".FormatWith(size, scale); - } + return "({0})".FormatWith(size); + } - public SqlPreCommand? AlterTableForeignKeys(ITable t) - { - return t.Columns.Values.Select(c => - (c.ReferenceTable == null || c.AvoidForeignKey) ? null : this.AlterTableAddConstraintForeignKey(t, c.Name, c.ReferenceTable)) - .Combine(Spacing.Simple); - } + public string Quote(AbstractDbType type, string @default) + { + if (type.IsString() && !(@default.StartsWith("'") && @default.StartsWith("'"))) + return "'" + @default + "'"; + return @default; + } - public SqlPreCommand DropIndex(ObjectName tableName, DiffIndex index) - { - if (index.IsPrimary) - return AlterTableDropConstraint(tableName, new ObjectName(tableName.Schema, index.IndexName, isPostgres)); + public SqlPreCommand? AlterTableForeignKeys(ITable t) + { + return t.Columns.Values.Select(c => + (c.ReferenceTable == null || c.AvoidForeignKey) ? null : this.AlterTableAddConstraintForeignKey(t, c.Name, c.ReferenceTable)) + .Combine(Spacing.Simple); + } - if (index.ViewName == null) - return DropIndex(tableName, index.IndexName); - else - return DropViewIndex(new ObjectName(tableName.Schema, index.ViewName, isPostgres), index.IndexName); - } - public SqlPreCommand DropIndex(ObjectName objectName, string indexName) - { - if (objectName.Schema.Database == null) + public SqlPreCommand DropIndex(ObjectName tableName, DiffIndex index) { - if (IsPostgres) - return new SqlPreCommandSimple("DROP INDEX {0};".FormatWith(new ObjectName(objectName.Schema, indexName, IsPostgres))); + if (index.IsPrimary) + return AlterTableDropConstraint(tableName, new ObjectName(tableName.Schema, index.IndexName, isPostgres)); + + if (index.ViewName == null) + return DropIndex(tableName, index.IndexName); else - return new SqlPreCommandSimple("DROP INDEX {0} ON {1};".FormatWith(indexName.SqlEscape(isPostgres), objectName)); + return DropViewIndex(new ObjectName(tableName.Schema, index.ViewName, isPostgres), index.IndexName); } - else - return new SqlPreCommandSimple("EXEC {0}.dbo.sp_executesql N'DROP INDEX {1} ON {2}';" - .FormatWith(objectName.Schema.Database.ToString().SqlEscape(isPostgres), indexName.SqlEscape(isPostgres), objectName.OnDatabase(null).ToString())); - } - public SqlPreCommand CreateIndex(TableIndex index, Replacements? checkUnique) - { - if (index is PrimaryKeyIndex) + public SqlPreCommand DropIndex(ObjectName objectName, string indexName) { - var columns = index.Columns.ToString(c => c.Name.SqlEscape(isPostgres), ", "); - - return new SqlPreCommandSimple($"ALTER TABLE {index.Table.Name} ADD CONSTRAINT {index.IndexName.SqlEscape(isPostgres)} PRIMARY KEY CLUSTERED({columns});"); + if (objectName.Schema.Database == null) + { + if (IsPostgres) + return new SqlPreCommandSimple("DROP INDEX {0};".FormatWith(new ObjectName(objectName.Schema, indexName, IsPostgres))); + else + return new SqlPreCommandSimple("DROP INDEX {0} ON {1};".FormatWith(indexName.SqlEscape(isPostgres), objectName)); + } + else + return new SqlPreCommandSimple("EXEC {0}.dbo.sp_executesql N'DROP INDEX {1} ON {2}';" + .FormatWith(objectName.Schema.Database.ToString().SqlEscape(isPostgres), indexName.SqlEscape(isPostgres), objectName.OnDatabase(null).ToString())); } - if (index is UniqueTableIndex uIndex) + public SqlPreCommand CreateIndex(TableIndex index, Replacements? checkUnique) { - if (uIndex.ViewName != null) + if (index is PrimaryKeyIndex) { - ObjectName viewName = new ObjectName(uIndex.Table.Name.Schema, uIndex.ViewName, isPostgres); - var columns = index.Columns.ToString(c => c.Name.SqlEscape(isPostgres), ", "); - SqlPreCommandSimple viewSql = new SqlPreCommandSimple($"CREATE VIEW {viewName} WITH SCHEMABINDING AS SELECT {columns} FROM {uIndex.Table.Name} WHERE {uIndex.Where};") - { GoBefore = true, GoAfter = true }; - - SqlPreCommandSimple indexSql = new SqlPreCommandSimple($"CREATE UNIQUE CLUSTERED INDEX {uIndex.IndexName.SqlEscape(isPostgres)} ON {viewName}({columns});"); + return new SqlPreCommandSimple($"ALTER TABLE {index.Table.Name} ADD CONSTRAINT {index.IndexName.SqlEscape(isPostgres)} PRIMARY KEY CLUSTERED({columns});"); + } - return SqlPreCommand.Combine(Spacing.Simple, - checkUnique!=null ? RemoveDuplicatesIfNecessary(uIndex, checkUnique) : null, - viewSql, - indexSql)!; + if (index is UniqueTableIndex uIndex) + { + if (uIndex.ViewName != null) + { + ObjectName viewName = new ObjectName(uIndex.Table.Name.Schema, uIndex.ViewName, isPostgres); + + var columns = index.Columns.ToString(c => c.Name.SqlEscape(isPostgres), ", "); + + SqlPreCommandSimple viewSql = new SqlPreCommandSimple($"CREATE VIEW {viewName} WITH SCHEMABINDING AS SELECT {columns} FROM {uIndex.Table.Name} WHERE {uIndex.Where};") + { GoBefore = true, GoAfter = true }; + + SqlPreCommandSimple indexSql = new SqlPreCommandSimple($"CREATE UNIQUE CLUSTERED INDEX {uIndex.IndexName.SqlEscape(isPostgres)} ON {viewName}({columns});"); + + return SqlPreCommand.Combine(Spacing.Simple, + checkUnique!=null ? RemoveDuplicatesIfNecessary(uIndex, checkUnique) : null, + viewSql, + indexSql)!; + } + else + { + return SqlPreCommand.Combine(Spacing.Double, + checkUnique != null ? RemoveDuplicatesIfNecessary(uIndex, checkUnique) : null, + CreateIndexBasic(index, forHistoryTable: false))!; + } } else { - return SqlPreCommand.Combine(Spacing.Double, - checkUnique != null ? RemoveDuplicatesIfNecessary(uIndex, checkUnique) : null, - CreateIndexBasic(index, forHistoryTable: false))!; + return CreateIndexBasic(index, forHistoryTable: false); } } - else - { - return CreateIndexBasic(index, forHistoryTable: false); - } - } - public int DuplicateCount(UniqueTableIndex uniqueIndex, Replacements rep) - { - var primaryKey = uniqueIndex.Table.Columns.Values.Where(a => a.PrimaryKey).Only(); + public int DuplicateCount(UniqueTableIndex uniqueIndex, Replacements rep) + { + var primaryKey = uniqueIndex.Table.Columns.Values.Where(a => a.PrimaryKey).Only(); - if (primaryKey == null) - throw new InvalidOperationException("No primary key found"); ; + if (primaryKey == null) + throw new InvalidOperationException("No primary key found"); ; - var oldTableName = rep.Apply(Replacements.KeyTablesInverse, uniqueIndex.Table.Name.ToString()); + var oldTableName = rep.Apply(Replacements.KeyTablesInverse, uniqueIndex.Table.Name.ToString()); - var columnReplacement = rep.TryGetC(Replacements.KeyColumnsForTable(uniqueIndex.Table.Name.ToString()))?.Inverse() ?? new Dictionary(); + var columnReplacement = rep.TryGetC(Replacements.KeyColumnsForTable(uniqueIndex.Table.Name.ToString()))?.Inverse() ?? new Dictionary(); - var oldColumns = uniqueIndex.Columns.ToString(c => (columnReplacement.TryGetC(c.Name) ?? c.Name).SqlEscape(isPostgres), ", "); + var oldColumns = uniqueIndex.Columns.ToString(c => (columnReplacement.TryGetC(c.Name) ?? c.Name).SqlEscape(isPostgres), ", "); - var oldPrimaryKey = columnReplacement.TryGetC(primaryKey.Name) ?? primaryKey.Name; + var oldPrimaryKey = columnReplacement.TryGetC(primaryKey.Name) ?? primaryKey.Name; - return Convert.ToInt32(Executor.ExecuteScalar( + return Convert.ToInt32(Executor.ExecuteScalar( $@"SELECT Count(*) FROM {oldTableName} WHERE {oldPrimaryKey.SqlEscape(IsPostgres)} NOT IN ( -SELECT MIN({oldPrimaryKey.SqlEscape(IsPostgres)}) -FROM {oldTableName} -{(!uniqueIndex.Where.HasText() ? "" : "WHERE " + uniqueIndex.Where.Replace(columnReplacement))} -GROUP BY {oldColumns} + SELECT MIN({oldPrimaryKey.SqlEscape(IsPostgres)}) + FROM {oldTableName} + {(!uniqueIndex.Where.HasText() ? "" : "WHERE " + uniqueIndex.Where.Replace(columnReplacement))} + GROUP BY {oldColumns} ){(!uniqueIndex.Where.HasText() ? "" : "AND " + uniqueIndex.Where.Replace(columnReplacement))};")!); - } + } - public SqlPreCommand? RemoveDuplicatesIfNecessary(UniqueTableIndex uniqueIndex, Replacements rep) - { - try + public SqlPreCommand? RemoveDuplicatesIfNecessary(UniqueTableIndex uniqueIndex, Replacements rep) { - var primaryKey = uniqueIndex.Table.Columns.Values.Where(a => a.PrimaryKey).Only(); + try + { + var primaryKey = uniqueIndex.Table.Columns.Values.Where(a => a.PrimaryKey).Only(); - if (primaryKey == null) - return null; + if (primaryKey == null) + return null; - int count = DuplicateCount(uniqueIndex, rep); + int count = DuplicateCount(uniqueIndex, rep); - if (count == 0) - return null; + if (count == 0) + return null; - var columns = uniqueIndex.Columns.ToString(c => c.Name.SqlEscape(isPostgres), ", "); + var columns = uniqueIndex.Columns.ToString(c => c.Name.SqlEscape(isPostgres), ", "); - if (rep.Interactive) - { - if (SafeConsole.Ask($"There are {count} rows in {uniqueIndex.Table.Name} with the same {columns}. Generate DELETE duplicates script?")) - return RemoveDuplicates(uniqueIndex, primaryKey, columns, commentedOut: false); + if (rep.Interactive) + { + if (SafeConsole.Ask($"There are {count} rows in {uniqueIndex.Table.Name} with the same {columns}. Generate DELETE duplicates script?")) + return RemoveDuplicates(uniqueIndex, primaryKey, columns, commentedOut: false); - return null; + return null; + } + else + { + return RemoveDuplicates(uniqueIndex, primaryKey, columns, commentedOut: true); + } } - else + catch (Exception) { - return RemoveDuplicates(uniqueIndex, primaryKey, columns, commentedOut: true); - } - } - catch (Exception) - { - return new SqlPreCommandSimple($"-- Impossible to determine duplicates in new index {uniqueIndex.IndexName.SqlEscape(isPostgres)}"); + return new SqlPreCommandSimple($"-- Impossible to determine duplicates in new index {uniqueIndex.IndexName.SqlEscape(isPostgres)}"); + } } - } - private SqlPreCommand RemoveDuplicates(UniqueTableIndex uniqueIndex, IColumn primaryKey, string columns, bool commentedOut) - { - return new SqlPreCommandSimple($@"DELETE {uniqueIndex.Table.Name} + private SqlPreCommand RemoveDuplicates(UniqueTableIndex uniqueIndex, IColumn primaryKey, string columns, bool commentedOut) + { + return new SqlPreCommandSimple($@"DELETE {uniqueIndex.Table.Name} WHERE {primaryKey.Name} NOT IN ( -SELECT MIN({primaryKey.Name}) -FROM {uniqueIndex.Table.Name} -{(string.IsNullOrWhiteSpace(uniqueIndex.Where) ? "" : "WHERE " + uniqueIndex.Where)} -GROUP BY {columns} + SELECT MIN({primaryKey.Name}) + FROM {uniqueIndex.Table.Name} + {(string.IsNullOrWhiteSpace(uniqueIndex.Where) ? "" : "WHERE " + uniqueIndex.Where)} + GROUP BY {columns} ){(string.IsNullOrWhiteSpace(uniqueIndex.Where) ? "" : " AND " + uniqueIndex.Where)};".Let(txt => commentedOut ? txt.Indent(2, '-') : txt)); - } + } - public SqlPreCommand CreateIndexBasic(Maps.TableIndex index, bool forHistoryTable) - { - var indexType = index is UniqueTableIndex ? "UNIQUE INDEX" : "INDEX"; - var columns = index.Columns.ToString(c => c.Name.SqlEscape(isPostgres), ", "); - var include = index.IncludeColumns.HasItems() ? $" INCLUDE ({index.IncludeColumns.ToString(c => c.Name.SqlEscape(isPostgres), ", ")})" : null; - var where = index.Where.HasText() ? $" WHERE {index.Where}" : ""; + public SqlPreCommand CreateIndexBasic(Maps.TableIndex index, bool forHistoryTable) + { + var indexType = index is UniqueTableIndex ? "UNIQUE INDEX" : "INDEX"; + var columns = index.Columns.ToString(c => c.Name.SqlEscape(isPostgres), ", "); + var include = index.IncludeColumns.HasItems() ? $" INCLUDE ({index.IncludeColumns.ToString(c => c.Name.SqlEscape(isPostgres), ", ")})" : null; + var where = index.Where.HasText() ? $" WHERE {index.Where}" : ""; - var tableName = forHistoryTable ? index.Table.SystemVersioned!.TableName : index.Table.Name; + var tableName = forHistoryTable ? index.Table.SystemVersioned!.TableName : index.Table.Name; - return new SqlPreCommandSimple($"CREATE {indexType} {index.GetIndexName(tableName).SqlEscape(isPostgres)} ON {tableName}({columns}){include}{where};"); - } + return new SqlPreCommandSimple($"CREATE {indexType} {index.GetIndexName(tableName).SqlEscape(isPostgres)} ON {tableName}({columns}){include}{where};"); + } - internal SqlPreCommand UpdateTrim(ITable tab, IColumn tabCol) - { - return new SqlPreCommandSimple("UPDATE {0} SET {1} = RTRIM({1});".FormatWith(tab.Name, tabCol.Name));; - } + internal SqlPreCommand UpdateTrim(ITable tab, IColumn tabCol) + { + return new SqlPreCommandSimple("UPDATE {0} SET {1} = RTRIM({1});".FormatWith(tab.Name, tabCol.Name));; + } - public SqlPreCommand AlterTableDropConstraint(ObjectName tableName, ObjectName foreignKeyName) => - AlterTableDropConstraint(tableName, foreignKeyName.Name); + public SqlPreCommand AlterTableDropConstraint(ObjectName tableName, ObjectName foreignKeyName) => + AlterTableDropConstraint(tableName, foreignKeyName.Name); - public SqlPreCommand AlterTableDropConstraint(ObjectName tableName, string constraintName) - { - return new SqlPreCommandSimple("ALTER TABLE {0} DROP CONSTRAINT {1};".FormatWith( - tableName, - constraintName.SqlEscape(isPostgres))); - } + public SqlPreCommand AlterTableDropConstraint(ObjectName tableName, string constraintName) + { + return new SqlPreCommandSimple("ALTER TABLE {0} DROP CONSTRAINT {1};".FormatWith( + tableName, + constraintName.SqlEscape(isPostgres))); + } - public SqlPreCommand AlterTableDropDefaultConstaint(ObjectName tableName, DiffColumn column) - { - if (isPostgres) - return AlterTableAlterColumnDropDefault(tableName, column.Name); - else - return AlterTableDropConstraint(tableName, column.DefaultConstraint!.Name!); - } + public SqlPreCommand AlterTableDropDefaultConstaint(ObjectName tableName, DiffColumn column) + { + if (isPostgres) + return AlterTableAlterColumnDropDefault(tableName, column.Name); + else + return AlterTableDropConstraint(tableName, column.DefaultConstraint!.Name!); + } - public SqlPreCommand AlterTableAlterColumnDropDefault(ObjectName tableName, string columnName) - { - return new SqlPreCommandSimple("ALTER TABLE {0} ALTER COLUMN {1} DROP DEFAULT;".FormatWith( - tableName, - columnName.SqlEscape(isPostgres))); - } + public SqlPreCommand AlterTableAlterColumnDropDefault(ObjectName tableName, string columnName) + { + return new SqlPreCommandSimple("ALTER TABLE {0} ALTER COLUMN {1} DROP DEFAULT;".FormatWith( + tableName, + columnName.SqlEscape(isPostgres))); + } - public SqlPreCommandSimple AlterTableAddDefaultConstraint(ObjectName tableName, DefaultConstraint constraint) - { - return new SqlPreCommandSimple($"ALTER TABLE {tableName} ADD CONSTRAINT {constraint.Name} DEFAULT {constraint.QuotedDefinition} FOR {constraint.ColumnName};"); - } + public SqlPreCommandSimple AlterTableAddDefaultConstraint(ObjectName tableName, DefaultConstraint constraint) + { + return new SqlPreCommandSimple($"ALTER TABLE {tableName} ADD CONSTRAINT {constraint.Name} DEFAULT {constraint.QuotedDefinition} FOR {constraint.ColumnName};"); + } - public SqlPreCommand? AlterTableAddConstraintForeignKey(ITable table, string fieldName, ITable foreignTable) - { - return AlterTableAddConstraintForeignKey(table.Name, fieldName, foreignTable.Name, foreignTable.PrimaryKey.Name); - } + public SqlPreCommand? AlterTableAddConstraintForeignKey(ITable table, string fieldName, ITable foreignTable) + { + return AlterTableAddConstraintForeignKey(table.Name, fieldName, foreignTable.Name, foreignTable.PrimaryKey.Name); + } - public SqlPreCommand? AlterTableAddConstraintForeignKey(ObjectName parentTable, string parentColumn, ObjectName targetTable, string targetPrimaryKey) - { - if (!object.Equals(parentTable.Schema.Database, targetTable.Schema.Database)) - return null; - - return new SqlPreCommandSimple("ALTER TABLE {0} ADD CONSTRAINT {1} FOREIGN KEY ({2}) REFERENCES {3}({4});".FormatWith( - parentTable, - ForeignKeyName(parentTable.Name, parentColumn).SqlEscape(isPostgres), - parentColumn.SqlEscape(isPostgres), - targetTable, - targetPrimaryKey.SqlEscape(isPostgres))); - } + public SqlPreCommand? AlterTableAddConstraintForeignKey(ObjectName parentTable, string parentColumn, ObjectName targetTable, string targetPrimaryKey) + { + if (!object.Equals(parentTable.Schema.Database, targetTable.Schema.Database)) + return null; - public string ForeignKeyName(string table, string fieldName) - { - var result = "FK_{0}_{1}".FormatWith(table, fieldName); + return new SqlPreCommandSimple("ALTER TABLE {0} ADD CONSTRAINT {1} FOREIGN KEY ({2}) REFERENCES {3}({4});".FormatWith( + parentTable, + ForeignKeyName(parentTable.Name, parentColumn).SqlEscape(isPostgres), + parentColumn.SqlEscape(isPostgres), + targetTable, + targetPrimaryKey.SqlEscape(isPostgres))); + } - return StringHashEncoder.ChopHash(result, this.connector.MaxNameLength); - } + public string ForeignKeyName(string table, string fieldName) + { + var result = "FK_{0}_{1}".FormatWith(table, fieldName); - public SqlPreCommand RenameForeignKey(ObjectName tn, ObjectName foreignKeyName, string newName) - { - if (IsPostgres) - return new SqlPreCommandSimple($"ALTER TABLE {tn} RENAME CONSTRAINT {foreignKeyName.Name.SqlEscape(IsPostgres)} TO {newName.SqlEscape(IsPostgres)};"); + return StringHashEncoder.ChopHash(result, this.connector.MaxNameLength); + } - return SP_RENAME(foreignKeyName.Schema.Database, foreignKeyName.OnDatabase(null).ToString(), newName, "OBJECT"); - } + public SqlPreCommand RenameForeignKey(ObjectName tn, ObjectName foreignKeyName, string newName) + { + if (IsPostgres) + return new SqlPreCommandSimple($"ALTER TABLE {tn} RENAME CONSTRAINT {foreignKeyName.Name.SqlEscape(IsPostgres)} TO {newName.SqlEscape(IsPostgres)};"); - public SqlPreCommandSimple SP_RENAME(DatabaseName? database, string oldName, string newName, string? objectType) - { - return new SqlPreCommandSimple("EXEC {0}SP_RENAME '{1}' , '{2}'{3};".FormatWith( - database == null ? null: SchemaName.Default(isPostgres).OnDatabase(database).ToString() + ".", - oldName, - newName, - objectType == null ? null : ", '{0}'".FormatWith(objectType) - )); - } + return SP_RENAME(foreignKeyName.Schema.Database, foreignKeyName.OnDatabase(null).ToString(), newName, "OBJECT"); + } - public SqlPreCommand RenameOrChangeSchema(ObjectName oldTableName, ObjectName newTableName) - { - if (!object.Equals(oldTableName.Schema.Database, newTableName.Schema.Database)) - throw new InvalidOperationException("Different database"); + public SqlPreCommandSimple SP_RENAME(DatabaseName? database, string oldName, string newName, string? objectType) + { + return new SqlPreCommandSimple("EXEC {0}SP_RENAME '{1}' , '{2}'{3};".FormatWith( + database == null ? null: SchemaName.Default(isPostgres).OnDatabase(database).ToString() + ".", + oldName, + newName, + objectType == null ? null : ", '{0}'".FormatWith(objectType) + )); + } - if (object.Equals(oldTableName.Schema, newTableName.Schema)) - return RenameTable(oldTableName, newTableName.Name); - - var oldNewSchema = oldTableName.OnSchema(newTableName.Schema); + public SqlPreCommand RenameOrChangeSchema(ObjectName oldTableName, ObjectName newTableName) + { + if (!object.Equals(oldTableName.Schema.Database, newTableName.Schema.Database)) + throw new InvalidOperationException("Different database"); - return SqlPreCommand.Combine(Spacing.Simple, - AlterSchema(oldTableName, newTableName.Schema), - oldNewSchema.Equals(newTableName) ? null : RenameTable(oldNewSchema, newTableName.Name))!; - } + if (object.Equals(oldTableName.Schema, newTableName.Schema)) + return RenameTable(oldTableName, newTableName.Name); + + var oldNewSchema = oldTableName.OnSchema(newTableName.Schema); - public SqlPreCommand RenameOrMove(DiffTable oldTable, ITable newTable, ObjectName newTableName) - { - if (object.Equals(oldTable.Name.Schema.Database, newTableName.Schema.Database)) - return RenameOrChangeSchema(oldTable.Name, newTableName); - - return SqlPreCommand.Combine(Spacing.Simple, - CreateTableSql(newTable, newTableName, avoidSystemVersioning: true), - MoveRows(oldTable.Name, newTableName, newTable.Columns.Keys, avoidIdentityInsert: newTable.SystemVersioned != null && newTable.SystemVersioned.Equals(newTable)), - DropTable(oldTable))!; - } + return SqlPreCommand.Combine(Spacing.Simple, + AlterSchema(oldTableName, newTableName.Schema), + oldNewSchema.Equals(newTableName) ? null : RenameTable(oldNewSchema, newTableName.Name))!; + } - public SqlPreCommand MoveRows(ObjectName oldTable, ObjectName newTable, IEnumerable columnNames, bool avoidIdentityInsert = false) - { - SqlPreCommandSimple command = new SqlPreCommandSimple( + public SqlPreCommand RenameOrMove(DiffTable oldTable, ITable newTable, ObjectName newTableName) + { + if (object.Equals(oldTable.Name.Schema.Database, newTableName.Schema.Database)) + return RenameOrChangeSchema(oldTable.Name, newTableName); + + return SqlPreCommand.Combine(Spacing.Simple, + CreateTableSql(newTable, newTableName, avoidSystemVersioning: true), + MoveRows(oldTable.Name, newTableName, newTable.Columns.Keys, avoidIdentityInsert: newTable.SystemVersioned != null && newTable.SystemVersioned.Equals(newTable)), + DropTable(oldTable))!; + } + + public SqlPreCommand MoveRows(ObjectName oldTable, ObjectName newTable, IEnumerable columnNames, bool avoidIdentityInsert = false) + { + SqlPreCommandSimple command = new SqlPreCommandSimple( @"INSERT INTO {0} ({2}) SELECT {3} FROM {1} as [table];".FormatWith( - newTable, - oldTable, - columnNames.ToString(a => a.SqlEscape(isPostgres), ", "), - columnNames.ToString(a => "[table]." + a.SqlEscape(isPostgres), ", "))); - - if (avoidIdentityInsert) - return command; - - return SqlPreCommand.Combine(Spacing.Simple, - new SqlPreCommandSimple("SET IDENTITY_INSERT {0} ON;".FormatWith(newTable)) { GoBefore = true }, - command, - new SqlPreCommandSimple("SET IDENTITY_INSERT {0} OFF;".FormatWith(newTable)) { GoAfter = true })!; - } + newTable, + oldTable, + columnNames.ToString(a => a.SqlEscape(isPostgres), ", "), + columnNames.ToString(a => "[table]." + a.SqlEscape(isPostgres), ", "))); + + if (avoidIdentityInsert) + return command; + + return SqlPreCommand.Combine(Spacing.Simple, + new SqlPreCommandSimple("SET IDENTITY_INSERT {0} ON;".FormatWith(newTable)) { GoBefore = true }, + command, + new SqlPreCommandSimple("SET IDENTITY_INSERT {0} OFF;".FormatWith(newTable)) { GoAfter = true })!; + } - public SqlPreCommand RenameTable(ObjectName oldName, string newName) - { - if (IsPostgres) - return new SqlPreCommandSimple($"ALTER TABLE {oldName} RENAME TO {newName.SqlEscape(IsPostgres)};"); + public SqlPreCommand RenameTable(ObjectName oldName, string newName) + { + if (IsPostgres) + return new SqlPreCommandSimple($"ALTER TABLE {oldName} RENAME TO {newName.SqlEscape(IsPostgres)};"); - return SP_RENAME(oldName.Schema.Database, oldName.OnDatabase(null).ToString(), newName, null); - } + return SP_RENAME(oldName.Schema.Database, oldName.OnDatabase(null).ToString(), newName, null); + } - public SqlPreCommandSimple AlterSchema(ObjectName oldName, SchemaName schemaName) - { - if (IsPostgres) - return new SqlPreCommandSimple($"ALTER TABLE {oldName} SET SCHEMA {schemaName.Name.SqlEscape(IsPostgres)};"); + public SqlPreCommandSimple AlterSchema(ObjectName oldName, SchemaName schemaName) + { + if (IsPostgres) + return new SqlPreCommandSimple($"ALTER TABLE {oldName} SET SCHEMA {schemaName.Name.SqlEscape(IsPostgres)};"); - return new SqlPreCommandSimple("ALTER SCHEMA {0} TRANSFER {1};".FormatWith(schemaName.Name.SqlEscape(isPostgres), oldName)); - } + return new SqlPreCommandSimple("ALTER SCHEMA {0} TRANSFER {1};".FormatWith(schemaName.Name.SqlEscape(isPostgres), oldName)); + } - public SqlPreCommand RenameColumn(ObjectName tableName, string oldName, string newName) - { - if (IsPostgres) - return new SqlPreCommandSimple($"ALTER TABLE {tableName} RENAME COLUMN {oldName.SqlEscape(IsPostgres)} TO {newName.SqlEscape(IsPostgres)};"); + public SqlPreCommand RenameColumn(ObjectName tableName, string oldName, string newName) + { + if (IsPostgres) + return new SqlPreCommandSimple($"ALTER TABLE {tableName} RENAME COLUMN {oldName.SqlEscape(IsPostgres)} TO {newName.SqlEscape(IsPostgres)};"); - return SP_RENAME(tableName.Schema.Database, tableName.OnDatabase(null) + "." + oldName, newName, "COLUMN"); - } + return SP_RENAME(tableName.Schema.Database, tableName.OnDatabase(null) + "." + oldName, newName, "COLUMN"); + } - public SqlPreCommand RenameIndex(ObjectName tableName, string oldName, string newName) - { - if (IsPostgres) - return new SqlPreCommandSimple($"ALTER INDEX {oldName.SqlEscape(IsPostgres)} RENAME TO {newName.SqlEscape(IsPostgres)};"); + public SqlPreCommand RenameIndex(ObjectName tableName, string oldName, string newName) + { + if (IsPostgres) + return new SqlPreCommandSimple($"ALTER INDEX {oldName.SqlEscape(IsPostgres)} RENAME TO {newName.SqlEscape(IsPostgres)};"); - return SP_RENAME(tableName.Schema.Database, tableName.OnDatabase(null) + "." + oldName, newName, "INDEX"); - } - #endregion + return SP_RENAME(tableName.Schema.Database, tableName.OnDatabase(null) + "." + oldName, newName, "INDEX"); + } + #endregion - public SqlPreCommandSimple SetIdentityInsert(ObjectName tableName, bool value) - { - return new SqlPreCommandSimple("SET IDENTITY_INSERT {0} {1}".FormatWith( - tableName, value ? "ON" : "OFF")); - } + public SqlPreCommandSimple SetIdentityInsert(ObjectName tableName, bool value) + { + return new SqlPreCommandSimple("SET IDENTITY_INSERT {0} {1}".FormatWith( + tableName, value ? "ON" : "OFF")); + } - public SqlPreCommandSimple SetSingleUser(DatabaseName databaseName) - { - return new SqlPreCommandSimple("ALTER DATABASE {0} SET SINGLE_USER WITH ROLLBACK IMMEDIATE;".FormatWith(databaseName)); - } + public SqlPreCommandSimple SetSingleUser(DatabaseName databaseName) + { + return new SqlPreCommandSimple("ALTER DATABASE {0} SET SINGLE_USER WITH ROLLBACK IMMEDIATE;".FormatWith(databaseName)); + } - public SqlPreCommandSimple SetMultiUser(DatabaseName databaseName) - { - return new SqlPreCommandSimple("ALTER DATABASE {0} SET MULTI_USER;".FormatWith(databaseName)); - } + public SqlPreCommandSimple SetMultiUser(DatabaseName databaseName) + { + return new SqlPreCommandSimple("ALTER DATABASE {0} SET MULTI_USER;".FormatWith(databaseName)); + } - public SqlPreCommandSimple SetSnapshotIsolation(DatabaseName databaseName, bool value) - { - return new SqlPreCommandSimple("ALTER DATABASE {0} SET ALLOW_SNAPSHOT_ISOLATION {1};".FormatWith(databaseName, value ? "ON" : "OFF")); - } + public SqlPreCommandSimple SetSnapshotIsolation(DatabaseName databaseName, bool value) + { + return new SqlPreCommandSimple("ALTER DATABASE {0} SET ALLOW_SNAPSHOT_ISOLATION {1};".FormatWith(databaseName, value ? "ON" : "OFF")); + } - public SqlPreCommandSimple MakeSnapshotIsolationDefault(DatabaseName databaseName, bool value) - { - return new SqlPreCommandSimple("ALTER DATABASE {0} SET READ_COMMITTED_SNAPSHOT {1};".FormatWith(databaseName, value ? "ON" : "OFF")); - } + public SqlPreCommandSimple MakeSnapshotIsolationDefault(DatabaseName databaseName, bool value) + { + return new SqlPreCommandSimple("ALTER DATABASE {0} SET READ_COMMITTED_SNAPSHOT {1};".FormatWith(databaseName, value ? "ON" : "OFF")); + } - public SqlPreCommandSimple SelectRowCount() - { - return new SqlPreCommandSimple("select @@rowcount;"); - } + public SqlPreCommandSimple SelectRowCount() + { + return new SqlPreCommandSimple("select @@rowcount;"); + } - public SqlPreCommand CreateSchema(SchemaName schemaName) - { - if (schemaName.Database == null) - return new SqlPreCommandSimple("CREATE SCHEMA {0};".FormatWith(schemaName)) { GoAfter = true, GoBefore = true }; - else - return new SqlPreCommandSimple($"EXEC('use {schemaName.Database}; EXEC sp_executesql N''CREATE SCHEMA {schemaName.Name}'' ');"); - } + public SqlPreCommand CreateSchema(SchemaName schemaName) + { + if (schemaName.Database == null) + return new SqlPreCommandSimple("CREATE SCHEMA {0};".FormatWith(schemaName)) { GoAfter = true, GoBefore = true }; + else + return new SqlPreCommandSimple($"EXEC('use {schemaName.Database}; EXEC sp_executesql N''CREATE SCHEMA {schemaName.Name}'' ');"); + } - public SqlPreCommand DropSchema(SchemaName schemaName) - { - return new SqlPreCommandSimple("DROP SCHEMA {0};".FormatWith(schemaName)) { GoAfter = true, GoBefore = true }; - } + public SqlPreCommand DropSchema(SchemaName schemaName) + { + return new SqlPreCommandSimple("DROP SCHEMA {0};".FormatWith(schemaName)) { GoAfter = true, GoBefore = true }; + } - public SqlPreCommandSimple DisableForeignKey(ObjectName tableName, string foreignKey) - { - return new SqlPreCommandSimple("ALTER TABLE {0} NOCHECK CONSTRAINT {1};".FormatWith(tableName, foreignKey)); - } + public SqlPreCommandSimple DisableForeignKey(ObjectName tableName, string foreignKey) + { + return new SqlPreCommandSimple("ALTER TABLE {0} NOCHECK CONSTRAINT {1};".FormatWith(tableName, foreignKey)); + } - public SqlPreCommandSimple EnableForeignKey(ObjectName tableName, string foreignKey) - { - return new SqlPreCommandSimple("ALTER TABLE {0} WITH CHECK CHECK CONSTRAINT {1};".FormatWith(tableName, foreignKey)); - } + public SqlPreCommandSimple EnableForeignKey(ObjectName tableName, string foreignKey) + { + return new SqlPreCommandSimple("ALTER TABLE {0} WITH CHECK CHECK CONSTRAINT {1};".FormatWith(tableName, foreignKey)); + } - public SqlPreCommandSimple DisableIndex(ObjectName tableName, string indexName) - { - return new SqlPreCommandSimple("ALTER INDEX [{0}] ON {1} DISABLE;".FormatWith(indexName, tableName)); - } + public SqlPreCommandSimple DisableIndex(ObjectName tableName, string indexName) + { + return new SqlPreCommandSimple("ALTER INDEX [{0}] ON {1} DISABLE;".FormatWith(indexName, tableName)); + } - public SqlPreCommandSimple RebuildIndex(ObjectName tableName, string indexName) - { - return new SqlPreCommandSimple("ALTER INDEX [{0}] ON {1} REBUILD;".FormatWith(indexName, tableName)); - } + public SqlPreCommandSimple RebuildIndex(ObjectName tableName, string indexName) + { + return new SqlPreCommandSimple("ALTER INDEX [{0}] ON {1} REBUILD;".FormatWith(indexName, tableName)); + } - public SqlPreCommandSimple DropPrimaryKeyConstraint(ObjectName tableName) - { - DatabaseName? db = tableName.Schema.Database; + public SqlPreCommandSimple DropPrimaryKeyConstraint(ObjectName tableName) + { + DatabaseName? db = tableName.Schema.Database; - var tn = tableName.OnDatabase(null); + var tn = tableName.OnDatabase(null); - string varName = "PrimaryKey_Constraint_" + tn.Name; + string varName = "PrimaryKey_Constraint_" + tn.Name; - string command = @" + string command = @" DECLARE @sql nvarchar(max) SELECT @sql = 'ALTER TABLE {Table} DROP CONSTRAINT [' + kc.name + '];' FROM DB.sys.key_constraints kc WHERE kc.parent_object_id = OBJECT_ID('{FullTable}') EXEC DB.dbo.sp_executesql @sql" - .Replace("DB.", db == null ? null : (db.ToString() + ".")) - .Replace("@sql", "@" + varName) - .Replace("{FullTable}", tableName.ToString()) - .Replace("{Table}", tn.ToString()); + .Replace("DB.", db == null ? null : (db.ToString() + ".")) + .Replace("@sql", "@" + varName) + .Replace("{FullTable}", tableName.ToString()) + .Replace("{Table}", tn.ToString()); - return new SqlPreCommandSimple(command); - } + return new SqlPreCommandSimple(command); + } - internal SqlPreCommand? DropStatistics(string tn, List list) - { - if (list.IsEmpty()) - return null; + internal SqlPreCommand? DropStatistics(string tn, List list) + { + if (list.IsEmpty()) + return null; - return new SqlPreCommandSimple("DROP STATISTICS " + list.ToString(s => tn.SqlEscape(isPostgres) + "." + s.StatsName.SqlEscape(isPostgres), ",\r\n") + ";"); - } + return new SqlPreCommandSimple("DROP STATISTICS " + list.ToString(s => tn.SqlEscape(isPostgres) + "." + s.StatsName.SqlEscape(isPostgres), ",\r\n") + ";"); + } - public SqlPreCommand TruncateTable(ObjectName tableName) => new SqlPreCommandSimple($"TRUNCATE TABLE {tableName};"); -} + public SqlPreCommand TruncateTable(ObjectName tableName) => new SqlPreCommandSimple($"TRUNCATE TABLE {tableName};"); + } diff --git a/Signum.Engine/Engine/SqlPreCommand.cs b/Signum.Engine/Engine/SqlPreCommand.cs index 8b09c300a9..7a3bb26332 100644 --- a/Signum.Engine/Engine/SqlPreCommand.cs +++ b/Signum.Engine/Engine/SqlPreCommand.cs @@ -9,443 +9,446 @@ namespace Signum.Engine; -public enum Spacing -{ - Simple, - Double, - Triple -} - -public abstract class SqlPreCommand -{ - public abstract IEnumerable Leaves(); - - public abstract SqlPreCommand Clone(); - - public abstract bool GoBefore { get; set; } - public abstract bool GoAfter { get; set; } - - protected internal abstract int NumParameters { get; } - - /// - /// For debugging purposes - /// - public string PlainSql() + public enum Spacing { - StringBuilder sb = new StringBuilder(); - this.PlainSql(sb); - return sb.ToString(); + Simple, + Double, + Triple } + public abstract class SqlPreCommand + { + public abstract IEnumerable Leaves(); + public abstract SqlPreCommand Clone(); + public abstract bool GoBefore { get; set; } + public abstract bool GoAfter { get; set; } - protected internal abstract void PlainSql(StringBuilder sb); + protected internal abstract int NumParameters { get; } - public override string ToString() - { - return this.PlainSql(); - } + /// + /// For debugging purposes + /// + public string PlainSql() + { + StringBuilder sb = new StringBuilder(); + this.PlainSql(sb); + return sb.ToString(); + } - public static SqlPreCommand? Combine(Spacing spacing, params SqlPreCommand?[] sentences) - { - if (sentences.Contains(null)) - sentences = sentences.NotNull().ToArray(); - if (sentences.Length == 0) - return null; - if (sentences.Length == 1) - return sentences[0]; - return new SqlPreCommandConcat(spacing, sentences as SqlPreCommand[]); - } + protected internal abstract void PlainSql(StringBuilder sb); - public abstract SqlPreCommand Replace(Regex regex, MatchEvaluator matchEvaluator); -} + public override string ToString() + { + return this.PlainSql(); + } -public static class SqlPreCommandExtensions -{ - public static SqlPreCommand? Combine(this IEnumerable preCommands, Spacing spacing) - { - return SqlPreCommand.Combine(spacing, preCommands.ToArray()); - } + public static SqlPreCommand? Combine(Spacing spacing, params SqlPreCommand?[] sentences) + { + if (sentences.Contains(null)) + sentences = sentences.NotNull().ToArray(); - public static SqlPreCommand PlainSqlCommand(this SqlPreCommand command) - { - return command.PlainSql().SplitNoEmpty("GO\r\n" ) - .Select(s => new SqlPreCommandSimple(s)) - .Combine(Spacing.Simple)!; - } + if (sentences.Length == 0) + return null; - public static void OpenSqlFileRetry(this SqlPreCommand command) - { - SafeConsole.WriteLineColor(ConsoleColor.Yellow, "There are changes!"); - var fileName = "Sync {0:dd-MM-yyyy HH_mm_ss}.sql".FormatWith(DateTime.Now); + if (sentences.Length == 1) + return sentences[0]; - Save(command, fileName); - SafeConsole.WriteLineColor(ConsoleColor.DarkYellow, command.PlainSql()); + return new SqlPreCommandConcat(spacing, sentences as SqlPreCommand[]); + } - Console.WriteLine("Script saved in: " + Path.Combine(Directory.GetCurrentDirectory(), fileName)); - Console.WriteLine("Check the synchronization script before running it!"); - var answer = SafeConsole.AskRetry("Open or run?", "open", "run", "exit"); + public abstract SqlPreCommand Replace(Regex regex, MatchEvaluator matchEvaluator); + } - if(answer == "open") + public static class SqlPreCommandExtensions + { + public static SqlPreCommand? Combine(this IEnumerable preCommands, Spacing spacing) { - Thread.Sleep(1000); - Open(fileName); - if (SafeConsole.Ask("run now?")) - ExecuteRetry(fileName); + return SqlPreCommand.Combine(spacing, preCommands.ToArray()); } - else if(answer == "run") + + public static SqlPreCommand PlainSqlCommand(this SqlPreCommand command) { - ExecuteRetry(fileName); + return command.PlainSql().SplitNoEmpty("GO\r\n" ) + .Select(s => new SqlPreCommandSimple(s)) + .Combine(Spacing.Simple)!; } - } - static void ExecuteRetry(string fileName) - { - retry: - try + public static void OpenSqlFileRetry(this SqlPreCommand command) { - var script = File.ReadAllText(fileName); - using (var tr = Transaction.ForceNew(System.Data.IsolationLevel.Unspecified)) + SafeConsole.WriteLineColor(ConsoleColor.Yellow, "There are changes!"); + var fileName = "Sync {0:dd-MM-yyyy HH_mm_ss}.sql".FormatWith(DateTime.Now); + + Save(command, fileName); + SafeConsole.WriteLineColor(ConsoleColor.DarkYellow, command.PlainSql()); + + Console.WriteLine("Script saved in: " + Path.Combine(Directory.GetCurrentDirectory(), fileName)); + Console.WriteLine("Check the synchronization script before running it!"); + var answer = SafeConsole.AskRetry("Open or run?", "open", "run", "exit"); + + if(answer == "open") + { + Thread.Sleep(1000); + Open(fileName); + if (SafeConsole.Ask("run now?")) + ExecuteRetry(fileName); + } + else if(answer == "run") { - ExecuteScript("script", script); - tr.Commit(); + ExecuteRetry(fileName); } } - catch (ExecuteSqlScriptException) + + static void ExecuteRetry(string fileName) { - Console.WriteLine("The current script is in saved in: " + Path.Combine(Directory.GetCurrentDirectory(), fileName)); - if (SafeConsole.Ask("retry?")) - goto retry; + retry: + try + { + var script = File.ReadAllText(fileName); + using (var tr = Transaction.ForceNew(System.Data.IsolationLevel.Unspecified)) + { + ExecuteScript("script", script); + tr.Commit(); + } + } + catch (ExecuteSqlScriptException) + { + Console.WriteLine("The current script is in saved in: " + Path.Combine(Directory.GetCurrentDirectory(), fileName)); + if (SafeConsole.Ask("retry?")) + goto retry; + } } - } - public static int Timeout = 20 * 60; + public static int Timeout = 20 * 60; - public static void ExecuteScript(string title, string script) - { - using (Connector.CommandTimeoutScope(Timeout)) + public static void ExecuteScript(string title, string script) { - var regex = new Regex(@" *(GO|USE \w+|USE \[[^\]]+\]) *(\r?\n|$)", RegexOptions.IgnoreCase); - - var parts = regex.Split(script); - - var realParts = parts.Where(a => !string.IsNullOrWhiteSpace(a) && !regex.IsMatch(a)).ToArray(); - - int pos = 0; - for (pos = 0; pos < realParts.Length; pos++) + using (Connector.CommandTimeoutScope(Timeout)) { - var currentPart = realParts[pos]; + var regex = new Regex(@" *(GO|USE \w+|USE \[[^\]]+\]) *(\r?\n|$)", RegexOptions.IgnoreCase); - try - { + var parts = regex.Split(script); - SafeConsole.WaitExecute("Executing {0} [{1}/{2}]".FormatWith(title, pos + 1, realParts.Length), - () => Executor.ExecuteNonQuery(currentPart)); + var realParts = parts.Where(a => !string.IsNullOrWhiteSpace(a) && !regex.IsMatch(a)).ToArray(); - } - catch (Exception ex) + int pos = 0; + for (pos = 0; pos < realParts.Length; pos++) { - var sqlE = ex as SqlException ?? ex.InnerException as SqlException; - var pgE = ex as PostgresException ?? ex.InnerException as PostgresException; - if (sqlE == null && pgE == null) - throw; + var currentPart = realParts[pos]; + + try + { - Console.WriteLine(); - Console.WriteLine(); + SafeConsole.WaitExecute("Executing {0} [{1}/{2}]".FormatWith(title, pos + 1, realParts.Length), + () => Executor.ExecuteNonQuery(currentPart)); - var list = currentPart.Lines(); + } + catch (Exception ex) + { + var sqlE = ex as SqlException ?? ex.InnerException as SqlException; + var pgE = ex as PostgresException ?? ex.InnerException as PostgresException; + if (sqlE == null && pgE == null) + throw; - var lineNumer = (pgE?.Line?.ToInt() ?? sqlE!.LineNumber); + Console.WriteLine(); + Console.WriteLine(); - SafeConsole.WriteLineColor(ConsoleColor.Red, "ERROR:"); + var list = currentPart.Lines(); - var min = Math.Max(0, lineNumer - 20); - var max = Math.Min(list.Length - 1, lineNumer + 20); + var lineNumer = (pgE?.Line?.ToInt() ?? sqlE!.LineNumber); - if (min > 0) - Console.WriteLine("..."); + SafeConsole.WriteLineColor(ConsoleColor.Red, "ERROR:"); - for (int i = min; i <= max; i++) - { - Console.Write(i + ": "); - SafeConsole.WriteLineColor(i == (lineNumer - 1) ? ConsoleColor.Red : ConsoleColor.DarkRed, list[i]); - } + var min = Math.Max(0, lineNumer - 20); + var max = Math.Min(list.Length - 1, lineNumer + 20); - if (max < list.Length - 1) - Console.WriteLine("..."); + if (min > 0) + Console.WriteLine("..."); - Console.WriteLine(); + for (int i = min; i <= max; i++) + { + Console.Write(i + ": "); + SafeConsole.WriteLineColor(i == (lineNumer - 1) ? ConsoleColor.Red : ConsoleColor.DarkRed, list[i]); + } - ex.Follow(a => a.InnerException).ToList().ForEach(e => - { - var sql = e as SqlException; - var pg = e as PostgresException; + if (max < list.Length - 1) + Console.WriteLine("..."); - SafeConsole.WriteLineColor(ConsoleColor.DarkRed, (e == ex ? "" : "InnerException: ") + e.GetType().Name + " (Number {0}): ".FormatWith(pg?.SqlState ?? sql?.Number.ToString())); - SafeConsole.WriteLineColor(ConsoleColor.Red, e.Message); Console.WriteLine(); - Console.WriteLine(); - }); - Console.WriteLine(); - throw new ExecuteSqlScriptException(ex.Message, ex); + ex.Follow(a => a.InnerException).ToList().ForEach(e => + { + var sql = e as SqlException; + var pg = e as PostgresException; + + SafeConsole.WriteLineColor(ConsoleColor.DarkRed, (e == ex ? "" : "InnerException: ") + e.GetType().Name + " (Number {0}): ".FormatWith(pg?.SqlState ?? sql?.Number.ToString())); + SafeConsole.WriteLineColor(ConsoleColor.Red, e.Message); + Console.WriteLine(); + Console.WriteLine(); + }); + + Console.WriteLine(); + throw new ExecuteSqlScriptException(ex.Message, ex); + } } } } - } - private static void Open(string fileName) - { - new Process + private static void Open(string fileName) { - StartInfo = new ProcessStartInfo(Path.Combine(Directory.GetCurrentDirectory(), fileName)) + new Process { - UseShellExecute = true - } - }.Start(); - } + StartInfo = new ProcessStartInfo(Path.Combine(Directory.GetCurrentDirectory(), fileName)) + { + UseShellExecute = true + } + }.Start(); + } - public static void Save(this SqlPreCommand command, string fileName) - { - string content = command.PlainSql(); + public static void Save(this SqlPreCommand command, string fileName) + { + string content = command.PlainSql(); - File.WriteAllText(fileName, content, Encoding.Unicode); + File.WriteAllText(fileName, content, Encoding.Unicode); + } } -} -public class ExecuteSqlScriptException : Exception -{ - public ExecuteSqlScriptException() { } - public ExecuteSqlScriptException(string message) : base(message) { } - public ExecuteSqlScriptException(string message, Exception inner) : base(message, inner) { } - protected ExecuteSqlScriptException( - System.Runtime.Serialization.SerializationInfo info, - System.Runtime.Serialization.StreamingContext context) : base(info, context) { } -} - -public class SqlPreCommandSimple : SqlPreCommand -{ - public override bool GoBefore { get; set; } - public override bool GoAfter { get; set; } - - public string Sql { get; private set; } - public List? Parameters { get; private set; } - - public SqlPreCommandSimple(string sql) - { - this.Sql = sql; - } - - public SqlPreCommandSimple(string sql, List? parameters) + public class ExecuteSqlScriptException : Exception { - this.Sql = sql; - this.Parameters = parameters; + public ExecuteSqlScriptException() { } + public ExecuteSqlScriptException(string message) : base(message) { } + public ExecuteSqlScriptException(string message, Exception inner) : base(message, inner) { } + protected ExecuteSqlScriptException( + System.Runtime.Serialization.SerializationInfo info, + System.Runtime.Serialization.StreamingContext context) : base(info, context) { } } - public void AlterSql(string sql) + public class SqlPreCommandSimple : SqlPreCommand { - this.Sql = sql; - } + public override bool GoBefore { get; set; } + public override bool GoAfter { get; set; } - public override IEnumerable Leaves() - { - yield return this; - } + public string Sql { get; private set; } + public List? Parameters { get; private set; } - protected internal override int NumParameters - { - get { return Parameters?.Count ?? 0; } - } + public SqlPreCommandSimple(string sql) + { + this.Sql = sql; + } - static readonly Regex regex = new Regex(@"@[_\p{Ll}\p{Lu}\p{Lt}\p{Lo}\p{Nl}][_\p{Ll}\p{Lu}\p{Lt}\p{Lo}\p{Nl}\p{Nd}]*"); + public SqlPreCommandSimple(string sql, List? parameters) + { + this.Sql = sql; + this.Parameters = parameters; + } - internal static string Encode(object? value, bool simple = false) - { - if (value == null || value == DBNull.Value) - return "NULL"; + public void AlterSql(string sql) + { + this.Sql = sql; + } - if (value is string s) - return "\'" + s.Replace("'", "''") + "'"; + public override IEnumerable Leaves() + { + yield return this; + } - if (value is char c) - return "\'" + c.ToString().Replace("'", "''") + "'"; + protected internal override int NumParameters + { + get { return Parameters?.Count ?? 0; } + } - if (value is Guid g) - return "\'" + g.ToString() + "'"; + static readonly Regex regex = new Regex(@"@[_\p{Ll}\p{Lu}\p{Lt}\p{Lo}\p{Nl}][_\p{Ll}\p{Lu}\p{Lt}\p{Lo}\p{Nl}\p{Nd}]*"); - if (value is DateTime dt) + internal static string Encode(object? value, bool simple = false) { - var str = dt.ToString("yyyy-MM-dd hh:mm:ss.fff", CultureInfo.InvariantCulture); + if (value == null || value == DBNull.Value) + return "NULL"; - return Schema.Current.Settings.IsPostgres || simple ? - "'{0}'".FormatWith(str) : - "convert(datetime, '{0}', 126)".FormatWith(dt.ToString("yyyy-MM-ddThh:mm:ss.fff", CultureInfo.InvariantCulture)); - } + if (value is string s) + return "\'" + s.Replace("'", "''") + "'"; - if (value is TimeSpan ts) - { - var str = ts.ToString("g", CultureInfo.InvariantCulture); + if (value is char c) + return "\'" + c.ToString().Replace("'", "''") + "'"; - return Schema.Current.Settings.IsPostgres || simple? - "'{0}'".FormatWith(str) : - "convert(time, '{0}')".FormatWith(str); - } + if (value is Guid g) + return "\'" + g.ToString() + "'"; - if (value is bool b) - { - if (Schema.Current.Settings.IsPostgres) - return b.ToString(); + if (value is DateTime dt) + { + var str = dt.ToString("yyyy-MM-dd hh:mm:ss.fff", CultureInfo.InvariantCulture); - return (b ? 1 : 0).ToString(); - } + return Schema.Current.Settings.IsPostgres || simple ? + "'{0}'".FormatWith(str) : + "convert(datetime, '{0}', 126)".FormatWith(dt.ToString("yyyy-MM-ddThh:mm:ss.fff", CultureInfo.InvariantCulture)); + } - if (Schema.Current.Settings.UdtSqlName.TryGetValue(value.GetType(), out var name)) - return "CAST('{0}' AS {1})".FormatWith(value, name); + if (value is TimeSpan ts) + { + var str = ts.ToString("g", CultureInfo.InvariantCulture); - if (value.GetType().IsEnum) - return Convert.ToInt32(value).ToString(); + return Schema.Current.Settings.IsPostgres || simple? + "'{0}'".FormatWith(str) : + "convert(time, '{0}')".FormatWith(str); + } - if (value is byte[] bytes) - return "0x" + BitConverter.ToString(bytes).Replace("-", ""); + if (value is bool b) + { + if (Schema.Current.Settings.IsPostgres) + return b.ToString(); - if (value is IFormattable f) - return f.ToString(null, CultureInfo.InvariantCulture); + return (b ? 1 : 0).ToString(); + } - return value.ToString()!; - } + if (Schema.Current.Settings.UdtSqlName.TryGetValue(value.GetType(), out var name)) + return "CAST('{0}' AS {1})".FormatWith(value, name); - protected internal override void PlainSql(StringBuilder sb) - { - if (Parameters.IsNullOrEmpty()) - sb.Append(Sql); - else + if (value.GetType().IsEnum) + return Convert.ToInt32(value).ToString(); + + if (value is byte[] bytes) + return "0x" + BitConverter.ToString(bytes).Replace("-", ""); + + if (value is IFormattable f) + return f.ToString(null, CultureInfo.InvariantCulture); + + return value.ToString()!; + } + + protected internal override void PlainSql(StringBuilder sb) { - var dic = Parameters.ToDictionary(a => a.ParameterName, a => Encode(a.Value)); + if (Parameters.IsNullOrEmpty()) + sb.Append(Sql); + else + { + var dic = Parameters.ToDictionary(a => a.ParameterName, a => Encode(a.Value)); - sb.Append(regex.Replace(Sql, m => dic.TryGetC(m.Value) ?? m.Value)); + sb.Append(regex.Replace(Sql, m => dic.TryGetC(m.Value) ?? m.Value)); + } } - } - public string sp_executesql() - { - var pars = this.Parameters.EmptyIfNull(); - var sqlBuilder = Connector.Current.SqlBuilder; + public string sp_executesql() + { + var pars = this.Parameters.EmptyIfNull(); + var sqlBuilder = Connector.Current.SqlBuilder; - var parameterVars = pars.ToString(p => $"{p.ParameterName} {(p is SqlParameter sp ? sp.SqlDbType.ToString() : ((NpgsqlParameter)p).NpgsqlDbType.ToString())}{sqlBuilder.GetSizeScale(p.Size.DefaultToNull(), p.Scale.DefaultToNull())}", ", "); - var parameterValues = pars.ToString(p => p.ParameterName + " = " + Encode(p.Value, simple: true), ",\r\n"); + var parameterVars = pars.ToString(p => "{0} {1}{2}".FormatWith( + p.ParameterName, + p is SqlParameter sp ? sp.SqlDbType.ToString() : ((NpgsqlParameter)p).NpgsqlDbType.ToString(), + sqlBuilder.GetSizePrecisionScale(p.Size.DefaultToNull(), p.Precision.DefaultToNull(), p.Scale.DefaultToNull(), p.DbType == System.Data.DbType.Decimal)), ", "); + var parameterValues = pars.ToString(p => p.ParameterName + " = " + Encode(p.Value, simple: true), ",\r\n"); - return @$"EXEC sp_executesql N'{this.Sql.Replace("'", "''")}', + return @$"EXEC sp_executesql N'{this.Sql.Replace("'", "''")}', @params = N'{parameterVars}', {parameterValues}"; - } + } - public override SqlPreCommand Clone() - { - return new SqlPreCommandSimple(Sql, Parameters?.Select(p => Connector.Current.CloneParameter(p)).ToList()); - } + public override SqlPreCommand Clone() + { + return new SqlPreCommandSimple(Sql, Parameters?.Select(p => Connector.Current.CloneParameter(p)).ToList()); + } - public SqlPreCommandSimple AddComment(string? comment) - { - if (comment.HasText()) + public SqlPreCommandSimple AddComment(string? comment) { - int index = Sql.IndexOf("\r\n"); - if (index == -1) - Sql = Sql + " -- " + comment; - else - Sql = Sql.Insert(index, " -- " + comment); + if (comment.HasText()) + { + int index = Sql.IndexOf("\r\n"); + if (index == -1) + Sql = Sql + " -- " + comment; + else + Sql = Sql.Insert(index, " -- " + comment); + } + + return this; } - return this; - } + public SqlPreCommandSimple ReplaceFirstParameter(string? variableName) + { + if (variableName == null) + return this; - public SqlPreCommandSimple ReplaceFirstParameter(string? variableName) - { - if (variableName == null) + var first = Parameters!.FirstEx(); + Sql = Regex.Replace(Sql, $@"(?{first.ParameterName})(\b|$)", variableName); //HACK + Parameters!.Remove(first); return this; + } - var first = Parameters!.FirstEx(); - Sql = Regex.Replace(Sql, $@"(?{first.ParameterName})(\b|$)", variableName); //HACK - Parameters!.Remove(first); - return this; + public override SqlPreCommand Replace(Regex regex, MatchEvaluator matchEvaluator) => new SqlPreCommandSimple(regex.Replace(this.Sql, matchEvaluator), this.Parameters?.Select(p => Connector.Current.CloneParameter(p)).ToList()) + { + GoAfter = GoAfter, + GoBefore = GoBefore, + }; } - public override SqlPreCommand Replace(Regex regex, MatchEvaluator matchEvaluator) => new SqlPreCommandSimple(regex.Replace(this.Sql, matchEvaluator), this.Parameters?.Select(p => Connector.Current.CloneParameter(p)).ToList()) + public class SqlPreCommandConcat : SqlPreCommand { - GoAfter = GoAfter, - GoBefore = GoBefore, - }; -} - -public class SqlPreCommandConcat : SqlPreCommand -{ - public Spacing Spacing { get; private set; } - public SqlPreCommand[] Commands { get; private set; } + public Spacing Spacing { get; private set; } + public SqlPreCommand[] Commands { get; private set; } - public override bool GoBefore { get { return this.Commands.First().GoBefore; } set { this.Commands.First().GoBefore = true; } } - public override bool GoAfter { get { return this.Commands.Last().GoAfter; } set { this.Commands.Last().GoAfter = true; } } + public override bool GoBefore { get { return this.Commands.First().GoBefore; } set { this.Commands.First().GoBefore = true; } } + public override bool GoAfter { get { return this.Commands.Last().GoAfter; } set { this.Commands.Last().GoAfter = true; } } - internal SqlPreCommandConcat(Spacing spacing, SqlPreCommand[] commands) - { - this.Spacing = spacing; - this.Commands = commands; - } + internal SqlPreCommandConcat(Spacing spacing, SqlPreCommand[] commands) + { + this.Spacing = spacing; + this.Commands = commands; + } - public override IEnumerable Leaves() - { - return Commands.SelectMany(c => c.Leaves()); - } + public override IEnumerable Leaves() + { + return Commands.SelectMany(c => c.Leaves()); + } - static Dictionary separators = new Dictionary() - { - {Spacing.Simple, "\r\n"}, - {Spacing.Double, "\r\n\r\n"}, - {Spacing.Triple, "\r\n\r\n\r\n"}, - }; + static Dictionary separators = new Dictionary() + { + {Spacing.Simple, "\r\n"}, + {Spacing.Double, "\r\n\r\n"}, + {Spacing.Triple, "\r\n\r\n\r\n"}, + }; - protected internal override int NumParameters - { - get { return Commands.Sum(c => c.NumParameters); } - } + protected internal override int NumParameters + { + get { return Commands.Sum(c => c.NumParameters); } + } - protected internal override void PlainSql(StringBuilder sb) - { - string sep = separators[Spacing]; - bool borrar = false; - foreach (SqlPreCommand com in Commands) + protected internal override void PlainSql(StringBuilder sb) { - var simple = com as SqlPreCommandSimple; + string sep = separators[Spacing]; + bool borrar = false; + foreach (SqlPreCommand com in Commands) + { + var simple = com as SqlPreCommandSimple; - if (simple != null && simple.GoBefore) - sb.Append("GO\r\n"); + if (simple != null && simple.GoBefore) + sb.Append("GO\r\n"); - com.PlainSql(sb); + com.PlainSql(sb); - if (simple != null && simple.GoAfter) - sb.Append("\r\nGO"); + if (simple != null && simple.GoAfter) + sb.Append("\r\nGO"); - sb.Append(sep); - borrar = true; - } + sb.Append(sep); + borrar = true; + } - if (borrar) sb.Remove(sb.Length - sep.Length, sep.Length); - } + if (borrar) sb.Remove(sb.Length - sep.Length, sep.Length); + } - public override SqlPreCommand Clone() - { - return new SqlPreCommandConcat(Spacing, Commands.Select(c => c.Clone()).ToArray()); - } + public override SqlPreCommand Clone() + { + return new SqlPreCommandConcat(Spacing, Commands.Select(c => c.Clone()).ToArray()); + } - public override SqlPreCommand Replace(Regex regex, MatchEvaluator matchEvaluator) - { - return new SqlPreCommandConcat(Spacing, Commands.Select(c => c.Replace(regex, matchEvaluator)).ToArray()); + public override SqlPreCommand Replace(Regex regex, MatchEvaluator matchEvaluator) + { + return new SqlPreCommandConcat(Spacing, Commands.Select(c => c.Replace(regex, matchEvaluator)).ToArray()); + } } -} diff --git a/Signum.Engine/Schema/Schema.Basics.cs b/Signum.Engine/Schema/Schema.Basics.cs index e2a80fdaa6..ff7eaf9f62 100644 --- a/Signum.Engine/Schema/Schema.Basics.cs +++ b/Signum.Engine/Schema/Schema.Basics.cs @@ -6,1682 +6,1693 @@ namespace Signum.Engine.Maps; -public interface IFieldFinder -{ - Field GetField(MemberInfo value); - Field? TryGetField(MemberInfo value); - IEnumerable FindFields(Func predicate); -} - -public interface ITable -{ - ObjectName Name { get; } - - IColumn PrimaryKey { get; } - - Dictionary Columns { get; } + public interface IFieldFinder + { + Field GetField(MemberInfo value); + Field? TryGetField(MemberInfo value); + IEnumerable FindFields(Func predicate); + } - List? MultiColumnIndexes { get; set; } + public interface ITable + { + ObjectName Name { get; } - List GeneratAllIndexes(); + IColumn PrimaryKey { get; } - void GenerateColumns(); + Dictionary Columns { get; } - SystemVersionedInfo? SystemVersioned { get; } + List? MultiColumnIndexes { get; set; } - bool IdentityBehaviour { get; } + List GeneratAllIndexes(); - FieldEmbedded.EmbeddedHasValueColumn? GetHasValueColumn(IColumn column); -} + void GenerateColumns(); -public class SystemVersionedInfo -{ - public ObjectName TableName; - public string? StartColumnName; - public string? EndColumnName; - public string? PostgreeSysPeriodColumnName; + SystemVersionedInfo? SystemVersioned { get; } - public SystemVersionedInfo(ObjectName tableName, string startColumnName, string endColumnName) - { - TableName = tableName; - StartColumnName = startColumnName; - EndColumnName = endColumnName; - } + bool IdentityBehaviour { get; } - public SystemVersionedInfo(ObjectName tableName, string postgreeSysPeriodColumnName) - { - TableName = tableName; - PostgreeSysPeriodColumnName = postgreeSysPeriodColumnName; + FieldEmbedded.EmbeddedHasValueColumn? GetHasValueColumn(IColumn column); } - internal IEnumerable Columns() + public class SystemVersionedInfo { - if (PostgreeSysPeriodColumnName != null) - return new[] - { - new PostgreePeriodColumn(this.PostgreeSysPeriodColumnName!), - }; - else - return new[] - { - new SqlServerPeriodColumn(this.StartColumnName!, ColumnType.Start), - new SqlServerPeriodColumn(this.EndColumnName!, ColumnType.End) - }; - } + public ObjectName TableName; + public string? StartColumnName; + public string? EndColumnName; + public string? PostgreeSysPeriodColumnName; - internal IntervalExpression? IntervalExpression(Alias tableAlias) - { - return new IntervalExpression(typeof(NullableInterval), - StartColumnName == null ? null : new SqlCastLazyExpression(typeof(DateTimeOffset?), new ColumnExpression(typeof(DateTime?), tableAlias, StartColumnName), new AbstractDbType(System.Data.SqlDbType.DateTimeOffset)), - EndColumnName == null ? null : new SqlCastLazyExpression(typeof(DateTimeOffset?), new ColumnExpression(typeof(DateTime?), tableAlias, EndColumnName), new AbstractDbType(System.Data.SqlDbType.DateTimeOffset)), - PostgreeSysPeriodColumnName == null ? null : new ColumnExpression(typeof(NpgsqlRange), tableAlias, PostgreeSysPeriodColumnName) - ); - } + public SystemVersionedInfo(ObjectName tableName, string startColumnName, string endColumnName) + { + TableName = tableName; + StartColumnName = startColumnName; + EndColumnName = endColumnName; + } - public enum ColumnType - { - Start, - End, - } + public SystemVersionedInfo(ObjectName tableName, string postgreeSysPeriodColumnName) + { + TableName = tableName; + PostgreeSysPeriodColumnName = postgreeSysPeriodColumnName; + } - public class SqlServerPeriodColumn : IColumn - { - public SqlServerPeriodColumn(string name, ColumnType systemVersionColumnType) + internal IEnumerable Columns() { - this.Name = name; - this.SystemVersionColumnType = systemVersionColumnType; + if (PostgreeSysPeriodColumnName != null) + return new[] + { + new PostgreePeriodColumn(this.PostgreeSysPeriodColumnName!), + }; + else + return new[] + { + new SqlServerPeriodColumn(this.StartColumnName!, ColumnType.Start), + new SqlServerPeriodColumn(this.EndColumnName!, ColumnType.End) + }; } - public string Name { get; private set; } - public ColumnType SystemVersionColumnType { get; private set; } + internal IntervalExpression? IntervalExpression(Alias tableAlias) + { + return new IntervalExpression(typeof(NullableInterval), + StartColumnName == null ? null : new SqlCastLazyExpression(typeof(DateTimeOffset?), new ColumnExpression(typeof(DateTime?), tableAlias, StartColumnName), new AbstractDbType(System.Data.SqlDbType.DateTimeOffset)), + EndColumnName == null ? null : new SqlCastLazyExpression(typeof(DateTimeOffset?), new ColumnExpression(typeof(DateTime?), tableAlias, EndColumnName), new AbstractDbType(System.Data.SqlDbType.DateTimeOffset)), + PostgreeSysPeriodColumnName == null ? null : new ColumnExpression(typeof(NpgsqlRange), tableAlias, PostgreeSysPeriodColumnName) + ); + } - public IsNullable Nullable => IsNullable.No; - public AbstractDbType DbType => new AbstractDbType(SqlDbType.DateTime2); - public Type Type => typeof(DateTime); - public string? UserDefinedTypeName => null; - public bool PrimaryKey => false; - public bool IdentityBehaviour => false; - public bool Identity => false; - public string? Default { get; set; } - public int? Size => null; - public int? Scale => null; - public string? Collation => null; - public Table? ReferenceTable => null; - public bool AvoidForeignKey => false; - } + public enum ColumnType + { + Start, + End, + } - public class PostgreePeriodColumn : IColumn - { - public PostgreePeriodColumn(string name) + public class SqlServerPeriodColumn : IColumn { - this.Name = name; + public SqlServerPeriodColumn(string name, ColumnType systemVersionColumnType) + { + this.Name = name; + this.SystemVersionColumnType = systemVersionColumnType; + } + + public string Name { get; private set; } + public ColumnType SystemVersionColumnType { get; private set; } + + public IsNullable Nullable => IsNullable.No; + public AbstractDbType DbType => new AbstractDbType(SqlDbType.DateTime2); + public Type Type => typeof(DateTime); + public string? UserDefinedTypeName => null; + public bool PrimaryKey => false; + public bool IdentityBehaviour => false; + public bool Identity => false; + public string? Default { get; set; } + public int? Size => null; + public byte? Precision => null; + public byte? Scale => null; + public string? Collation => null; + public Table? ReferenceTable => null; + public bool AvoidForeignKey => false; } - public string Name { get; private set; } + public class PostgreePeriodColumn : IColumn + { + public PostgreePeriodColumn(string name) + { + this.Name = name; + } + + public string Name { get; private set; } + + public IsNullable Nullable => IsNullable.No; + public AbstractDbType DbType => new AbstractDbType(NpgsqlDbType.Range | NpgsqlDbType.TimestampTz); + public Type Type => typeof(DateTime); + public string? UserDefinedTypeName => null; + public bool PrimaryKey => false; + public bool IdentityBehaviour => false; + public bool Identity => false; + public string? Default { get; set; } + public int? Size => null; + public byte? Precision => null; + public byte? Scale => null; + public string? Collation => null; + public Table? ReferenceTable => null; + public bool AvoidForeignKey => false; + } - public IsNullable Nullable => IsNullable.No; - public AbstractDbType DbType => new AbstractDbType(NpgsqlDbType.Range | NpgsqlDbType.TimestampTz); - public Type Type => typeof(DateTime); - public string? UserDefinedTypeName => null; - public bool PrimaryKey => false; - public bool IdentityBehaviour => false; - public bool Identity => false; - public string? Default { get; set; } - public int? Size => null; - public int? Scale => null; - public string? Collation => null; - public Table? ReferenceTable => null; - public bool AvoidForeignKey => false; } -} - -interface ITablePrivate -{ - ColumnExpression GetPrimaryOrder(Alias alias); -} + interface ITablePrivate + { + ColumnExpression GetPrimaryOrder(Alias alias); + } -public partial class Table : IFieldFinder, ITable, ITablePrivate -{ - public Type Type { get; private set; } - public Schema Schema { get; private set; } + public partial class Table : IFieldFinder, ITable, ITablePrivate + { + public Type Type { get; private set; } + public Schema Schema { get; private set; } - public ObjectName Name { get; set; } + public ObjectName Name { get; set; } - public bool IdentityBehaviour { get; internal set; } - public bool IsView { get; internal set; } - public string CleanTypeName { get; set; } + public bool IdentityBehaviour { get; internal set; } + public bool IsView { get; internal set; } + public string CleanTypeName { get; set; } - public SystemVersionedInfo? SystemVersioned { get; set; } + public SystemVersionedInfo? SystemVersioned { get; set; } - public Dictionary Fields { get; set; } - public Dictionary? Mixins { get; set; } + public Dictionary Fields { get; set; } + public Dictionary? Mixins { get; set; } - public Dictionary Columns { get; set; } + public Dictionary Columns { get; set; } - public List? MultiColumnIndexes { get; set; } + public List? MultiColumnIndexes { get; set; } #pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. - public Table(Type type) + public Table(Type type) #pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. - { - this.Type = type; - } + { + this.Type = type; + } - public override string ToString() - { - return Name.ToString(); - } + public override string ToString() + { + return Name.ToString(); + } - public void GenerateColumns() - { - var errorSuffix = "columns in table " + this.Name.Name; - var columns = new Dictionary(); - void AddColumns(IEnumerable newColumns) + public void GenerateColumns() { - try - { - columns.AddRange(newColumns, c => c.Name, c => c, errorSuffix); - }catch(RepeatedElementsException ex) when (StartParameters.IgnoredCodeErrors != null) + var errorSuffix = "columns in table " + this.Name.Name; + var columns = new Dictionary(); + void AddColumns(IEnumerable newColumns) { - StartParameters.IgnoredCodeErrors.Add(ex); + try + { + columns.AddRange(newColumns, c => c.Name, c => c, errorSuffix); + }catch(RepeatedElementsException ex) when (StartParameters.IgnoredCodeErrors != null) + { + StartParameters.IgnoredCodeErrors.Add(ex); + } } - } - AddColumns(Fields.Values.SelectMany(c => c.Field.Columns())); + AddColumns(Fields.Values.SelectMany(c => c.Field.Columns())); - if (Mixins != null) - AddColumns(Mixins.Values.SelectMany(m => m.Fields.Values).SelectMany(f => f.Field.Columns())); + if (Mixins != null) + AddColumns(Mixins.Values.SelectMany(m => m.Fields.Values).SelectMany(f => f.Field.Columns())); - if (this.SystemVersioned != null) - AddColumns(this.SystemVersioned.Columns()); + if (this.SystemVersioned != null) + AddColumns(this.SystemVersioned.Columns()); - Columns = columns; - inserterDisableIdentity = new ResetLazy(() => InsertCacheDisableIdentity.InitializeInsertDisableIdentity(this)); - inserterIdentity = new ResetLazy(() => InsertCacheIdentity.InitializeInsertIdentity(this)); - updater = new ResetLazy(() => UpdateCache.InitializeUpdate(this)); - saveCollections = new ResetLazy(() => CollectionsCache.InitializeCollections(this)); - } + Columns = columns; + inserterDisableIdentity = new ResetLazy(() => InsertCacheDisableIdentity.InitializeInsertDisableIdentity(this)); + inserterIdentity = new ResetLazy(() => InsertCacheIdentity.InitializeInsertIdentity(this)); + updater = new ResetLazy(() => UpdateCache.InitializeUpdate(this)); + saveCollections = new ResetLazy(() => CollectionsCache.InitializeCollections(this)); + } - public Field GetField(MemberInfo member) - { - Type? mixinType = member as Type ?? GetMixinType(member); - if (mixinType != null) + public Field GetField(MemberInfo member) { - if (Mixins == null) - throw new InvalidOperationException("{0} has not mixins".FormatWith(this.Type.Name)); + Type? mixinType = member as Type ?? GetMixinType(member); + if (mixinType != null) + { + if (Mixins == null) + throw new InvalidOperationException("{0} has not mixins".FormatWith(this.Type.Name)); - return Mixins.GetOrThrow(mixinType); - } - - FieldInfo fi = member as FieldInfo ?? Reflector.FindFieldInfo(Type, (PropertyInfo)member); + return Mixins.GetOrThrow(mixinType); + } + + FieldInfo fi = member as FieldInfo ?? Reflector.FindFieldInfo(Type, (PropertyInfo)member); - if (fi == null) - throw new InvalidOperationException("Field {0} not found on {1}".FormatWith(member.Name, Type)); + if (fi == null) + throw new InvalidOperationException("Field {0} not found on {1}".FormatWith(member.Name, Type)); - EntityField field = Fields.GetOrThrow(fi.Name, "Field {0} not found on schema"); + EntityField field = Fields.GetOrThrow(fi.Name, "Field {0} not found on schema"); - return field.Field; - } + return field.Field; + } - public Field? TryGetField(MemberInfo member) - { - Type? mixinType = member as Type ?? GetMixinType(member); - if (mixinType!= null) + public Field? TryGetField(MemberInfo member) { - return Mixins?.TryGetC(mixinType); - } + Type? mixinType = member as Type ?? GetMixinType(member); + if (mixinType!= null) + { + return Mixins?.TryGetC(mixinType); + } - FieldInfo fi = member as FieldInfo ?? Reflector.TryFindFieldInfo(Type, (PropertyInfo)member)!; + FieldInfo fi = member as FieldInfo ?? Reflector.TryFindFieldInfo(Type, (PropertyInfo)member)!; - if (fi == null) - return null; + if (fi == null) + return null; - EntityField? field = Fields.TryGetC(fi.Name); + EntityField? field = Fields.TryGetC(fi.Name); - if (field == null) - return null; + if (field == null) + return null; - return field.Field; - } + return field.Field; + } - internal static Type? GetMixinType(MemberInfo member) - { - if (member is MethodInfo mi) + internal static Type? GetMixinType(MemberInfo member) { - if (mi.IsGenericMethod && mi.GetGenericMethodDefinition().Name == "Mixin") + if (member is MethodInfo mi) { - return mi.GetGenericArguments().SingleEx(); + if (mi.IsGenericMethod && mi.GetGenericMethodDefinition().Name == "Mixin") + { + return mi.GetGenericArguments().SingleEx(); + } } + return null; } - return null; - } - public IEnumerable FindFields(Func predicate) - { - var fields = - Fields.Values.Select(a => a.Field).SelectMany(f => predicate(f) ? new[] { f } : - f is IFieldFinder ff ? ff.FindFields(predicate) : - Enumerable.Empty()).ToList(); - - if(Mixins != null) - { - foreach (var mixin in this.Mixins.Values) + public IEnumerable FindFields(Func predicate) + { + var fields = + Fields.Values.Select(a => a.Field).SelectMany(f => predicate(f) ? new[] { f } : + f is IFieldFinder ff ? ff.FindFields(predicate) : + Enumerable.Empty()).ToList(); + + if(Mixins != null) { - fields.AddRange(mixin.FindFields(predicate)); + foreach (var mixin in this.Mixins.Values) + { + fields.AddRange(mixin.FindFields(predicate)); + } } + return fields; } - return fields; - } - public List GeneratAllIndexes() - { - IEnumerable fields = Fields.Values.AsEnumerable(); - if (Mixins != null) - fields = fields.Concat(Mixins.Values.SelectMany(m => m.Fields.Values)); - - var result = fields.SelectMany(f => f.Field.GenerateIndexes(this)).ToList(); + public List GeneratAllIndexes() + { + IEnumerable fields = Fields.Values.AsEnumerable(); + if (Mixins != null) + fields = fields.Concat(Mixins.Values.SelectMany(m => m.Fields.Values)); - if (MultiColumnIndexes != null) - result.AddRange(MultiColumnIndexes); + var result = fields.SelectMany(f => f.Field.GenerateIndexes(this)).ToList(); - if (result.OfType().Any()) - { - var s = Schema.Current.Settings; - List attachedFields = fields.Where(f => s.FieldAttributes(PropertyRoute.Root(this.Type).Add(f.FieldInfo))!.OfType().Any()) - .SelectMany(f => TableIndex.GetColumnsFromFields(f.Field)) - .ToList(); + if (MultiColumnIndexes != null) + result.AddRange(MultiColumnIndexes); - if (attachedFields.Any()) + if (result.OfType().Any()) { - result = result.Select(ix => - { - var ui = ix as UniqueTableIndex; - if (ui == null || ui.AvoidAttachToUniqueIndexes) - return ix; + var s = Schema.Current.Settings; + List attachedFields = fields.Where(f => s.FieldAttributes(PropertyRoute.Root(this.Type).Add(f.FieldInfo))!.OfType().Any()) + .SelectMany(f => TableIndex.GetColumnsFromFields(f.Field)) + .ToList(); - return new UniqueTableIndex(ui.Table, ui.Columns.Concat(attachedFields).ToArray()) + if (attachedFields.Any()) + { + result = result.Select(ix => { - Where = ui.Where - }; - }).ToList(); + var ui = ix as UniqueTableIndex; + if (ui == null || ui.AvoidAttachToUniqueIndexes) + return ix; + + return new UniqueTableIndex(ui.Table, ui.Columns.Concat(attachedFields).ToArray()) + { + Where = ui.Where + }; + }).ToList(); + } + } + + if(this.SystemVersioned != null) + { + result.Add(new TableIndex(this, this.SystemVersioned.Columns().PreAnd(this.PrimaryKey).ToArray())); } + + return result; } - if(this.SystemVersioned != null) + public IEnumerable> DependentTables() { - result.Add(new TableIndex(this, this.SystemVersioned.Columns().PreAnd(this.PrimaryKey).ToArray())); - } + var result = Fields.Values.SelectMany(f => f.Field.GetTables()).ToList(); - return result; - } + if (Mixins != null) + result.AddRange(Mixins.Values.SelectMany(fm => fm.GetTables())); - public IEnumerable> DependentTables() - { - var result = Fields.Values.SelectMany(f => f.Field.GetTables()).ToList(); + return result; + } - if (Mixins != null) - result.AddRange(Mixins.Values.SelectMany(fm => fm.GetTables())); + public IEnumerable TablesMList() + { + return this.AllFields().SelectMany(f => f.Field.TablesMList()); + } - return result; - } + public FieldTicks Ticks { get; internal set; } + public FieldPrimaryKey PrimaryKey { get; internal set; } - public IEnumerable TablesMList() - { - return this.AllFields().SelectMany(f => f.Field.TablesMList()); - } + IColumn ITable.PrimaryKey + { + get { return PrimaryKey; } + } - public FieldTicks Ticks { get; internal set; } - public FieldPrimaryKey PrimaryKey { get; internal set; } + public IEnumerable AllFields() + { + return this.Fields.Values.Concat( + this.Mixins == null ? Enumerable.Empty() : + this.Mixins.Values.SelectMany(fm => fm.Fields.Values)); + } - IColumn ITable.PrimaryKey - { - get { return PrimaryKey; } - } + public FieldEmbedded.EmbeddedHasValueColumn? GetHasValueColumn(IColumn column) + { + return this.AllFields().Select(a => a.Field).OfType().Select(a => a.GetHasValueColumn(column)).NotNull().SingleOrDefaultEx(); + } - public IEnumerable AllFields() - { - return this.Fields.Values.Concat( - this.Mixins == null ? Enumerable.Empty() : - this.Mixins.Values.SelectMany(fm => fm.Fields.Values)); } - public FieldEmbedded.EmbeddedHasValueColumn? GetHasValueColumn(IColumn column) + public class EntityField { - return this.AllFields().Select(a => a.Field).OfType().Select(a => a.GetHasValueColumn(column)).NotNull().SingleOrDefaultEx(); - } - -} - -public class EntityField -{ - public Field Field { get; set; } - public FieldInfo FieldInfo { get; private set; } + public Field Field { get; set; } + public FieldInfo FieldInfo { get; private set; } - Type type; - Func? getter; - public Func Getter => getter ?? (getter = ReflectionTools.CreateGetter(FieldInfo)!); + Type type; + Func? getter; + public Func Getter => getter ?? (getter = ReflectionTools.CreateGetter(FieldInfo)!); - public EntityField(Type type, FieldInfo fi, Field field) - { - this.FieldInfo = fi; - this.type = type; - this.Field = field; - } + public EntityField(Type type, FieldInfo fi, Field field) + { + this.FieldInfo = fi; + this.type = type; + this.Field = field; + } - public override string ToString() - { - return FieldInfo.FieldName(); + public override string ToString() + { + return FieldInfo.FieldName(); + } } -} - -public abstract partial class Field -{ - public Type FieldType { get; private set; } - public PropertyRoute Route { get; private set; } - public UniqueTableIndex? UniqueIndex { get; set; } - public Field(PropertyRoute route, Type? fieldType = null) + public abstract partial class Field { - this.Route = route; - this.FieldType = fieldType ?? route.Type; - } - + public Type FieldType { get; private set; } + public PropertyRoute Route { get; private set; } + public UniqueTableIndex? UniqueIndex { get; set; } - public abstract IEnumerable Columns(); - - public virtual IEnumerable GenerateIndexes(ITable table) - { - if (UniqueIndex == null) - return Enumerable.Empty(); + public Field(PropertyRoute route, Type? fieldType = null) + { + this.Route = route; + this.FieldType = fieldType ?? route.Type; + } - return new[] { UniqueIndex }; - } - public virtual UniqueTableIndex? GenerateUniqueIndex(ITable table, UniqueIndexAttribute? attribute) - { - if (attribute == null) - return null; + public abstract IEnumerable Columns(); - var result = new UniqueTableIndex(table, TableIndex.GetColumnsFromFields(this)) + public virtual IEnumerable GenerateIndexes(ITable table) { - AvoidAttachToUniqueIndexes = attribute.AvoidAttachToUniqueIndexes - }; + if (UniqueIndex == null) + return Enumerable.Empty(); - if(attribute.AllowMultipleNulls) - result.Where = IndexWhereExpressionVisitor.IsNull(this, false, Schema.Current.Settings.IsPostgres); - - return result; - } + return new[] { UniqueIndex }; + } - internal abstract IEnumerable> GetTables(); + public virtual UniqueTableIndex? GenerateUniqueIndex(ITable table, UniqueIndexAttribute? attribute) + { + if (attribute == null) + return null; - internal abstract IEnumerable TablesMList(); -} + var result = new UniqueTableIndex(table, TableIndex.GetColumnsFromFields(this)) + { + AvoidAttachToUniqueIndexes = attribute.AvoidAttachToUniqueIndexes + }; -public static class FieldExtensions -{ - public static bool Implements(this Field field, Type type) - { - if (field is FieldReference) - return ((FieldReference)field).FieldType == type; + if(attribute.AllowMultipleNulls) + result.Where = IndexWhereExpressionVisitor.IsNull(this, false, Schema.Current.Settings.IsPostgres); - if (field is FieldImplementedByAll) - return true; + return result; + } - if (field is FieldImplementedBy) - return ((FieldImplementedBy)field).ImplementationColumns.ContainsKey(type); + internal abstract IEnumerable> GetTables(); - return false; + internal abstract IEnumerable TablesMList(); } - public static void AssertImplements(this Field field, Type type) + public static class FieldExtensions { - if (!Implements(field, type)) - throw new InvalidOperationException("{0} does not implement {1}".FormatWith(field.ToString(), type.Name)); - } + public static bool Implements(this Field field, Type type) + { + if (field is FieldReference) + return ((FieldReference)field).FieldType == type; - public static ObjectName GetName(this ITable table, bool useHistoryName) - { - return useHistoryName && table.SystemVersioned != null ? table.SystemVersioned.TableName : table.Name; - } -} - -public partial interface IColumn -{ - string Name { get; } - IsNullable Nullable { get; } - AbstractDbType DbType { get; } - Type Type { get; } - string? UserDefinedTypeName { get; } - bool PrimaryKey { get; } - bool IdentityBehaviour { get; } - bool Identity { get; } - string? Default { get; } - int? Size { get; } - int? Scale { get; } - string? Collation { get; } - Table? ReferenceTable { get; } - bool AvoidForeignKey { get; } -} - -public enum IsNullable -{ - No, - Yes, - //Nullable only because in a Embedded nullabled - Forced -} - -public static partial class ColumnExtensions -{ - public static bool ToBool(this IsNullable isNullable) - { - return isNullable != IsNullable.No; - } + if (field is FieldImplementedByAll) + return true; - //public static string GetSqlDbTypeString(this IColumn column) - //{ - // return column.SqlDbType.ToString().ToUpper(CultureInfo.InvariantCulture) + SqlBuilder.GetSizeScale(column.Size, column.Scale); - //} + if (field is FieldImplementedBy) + return ((FieldImplementedBy)field).ImplementationColumns.ContainsKey(type); - public static GeneratedAlwaysType GetGeneratedAlwaysType(this IColumn column) - { - if (column is SystemVersionedInfo.SqlServerPeriodColumn svc) - return svc.SystemVersionColumnType == SystemVersionedInfo.ColumnType.Start ? GeneratedAlwaysType.AsRowStart : GeneratedAlwaysType.AsRowEnd; + return false; + } - return GeneratedAlwaysType.None; - } -} - -public interface IFieldReference -{ - bool IsLite { get; } - bool ClearEntityOnSaving { get; set; } - bool AvoidExpandOnRetrieving { get; } - Type FieldType { get; } -} - -public partial class FieldPrimaryKey : Field, IColumn -{ - public string Name { get; set; } - IsNullable IColumn.Nullable { get { return IsNullable.No; } } - public AbstractDbType DbType { get; set; } - public string? UserDefinedTypeName { get; set; } - bool IColumn.PrimaryKey { get { return true; } } - public bool Identity { get; set; } - bool IColumn.IdentityBehaviour { get { return table.IdentityBehaviour; } } - public int? Size { get; set; } - int? IColumn.Scale { get { return null; } } - public string? Collation { get; set; } - Table? IColumn.ReferenceTable { get { return null; } } - public Type Type { get; set; } - public bool AvoidForeignKey { get { return false; } } - public string? Default { get; set; } - - Table table; - public FieldPrimaryKey(PropertyRoute route, Table table, string name, Type type) - : base(route) - { - this.table = table; - this.Name = name; - this.Type = type; + public static void AssertImplements(this Field field, Type type) + { + if (!Implements(field, type)) + throw new InvalidOperationException("{0} does not implement {1}".FormatWith(field.ToString(), type.Name)); + } + + public static ObjectName GetName(this ITable table, bool useHistoryName) + { + return useHistoryName && table.SystemVersioned != null ? table.SystemVersioned.TableName : table.Name; + } } - public override string ToString() + public partial interface IColumn { - return "{0} PrimaryKey".FormatWith(Name); + string Name { get; } + IsNullable Nullable { get; } + AbstractDbType DbType { get; } + Type Type { get; } + string? UserDefinedTypeName { get; } + bool PrimaryKey { get; } + bool IdentityBehaviour { get; } + bool Identity { get; } + string? Default { get; } + int? Size { get; } + byte? Precision { get; } + byte? Scale { get; } + string? Collation { get; } + Table? ReferenceTable { get; } + bool AvoidForeignKey { get; } } - public override IEnumerable Columns() + public enum IsNullable { - return new[] { this }; + No, + Yes, + //Nullable only because in a Embedded nullabled + Forced } - public override IEnumerable GenerateIndexes(ITable table) + public static partial class ColumnExtensions { - if (this.UniqueIndex != null) - throw new InvalidOperationException("Changing IndexType is not allowed for FieldPrimaryKey"); + public static bool ToBool(this IsNullable isNullable) + { + return isNullable != IsNullable.No; + } - return new[] { new PrimaryKeyIndex(table) }; - } + //public static string GetSqlDbTypeString(this IColumn column) + //{ + // return column.SqlDbType.ToString().ToUpper(CultureInfo.InvariantCulture) + SqlBuilder.GetSizeScale(column.Size, column.Scale); + //} - internal override IEnumerable> GetTables() - { - return Enumerable.Empty>(); - } + public static GeneratedAlwaysType GetGeneratedAlwaysType(this IColumn column) + { + if (column is SystemVersionedInfo.SqlServerPeriodColumn svc) + return svc.SystemVersionColumnType == SystemVersionedInfo.ColumnType.Start ? GeneratedAlwaysType.AsRowStart : GeneratedAlwaysType.AsRowEnd; - internal override IEnumerable TablesMList() - { - return Enumerable.Empty(); - } -} - -public partial class FieldValue : Field, IColumn -{ - public string Name { get; set; } - public IsNullable Nullable { get; set; } - public AbstractDbType DbType { get; set; } - public string? UserDefinedTypeName { get; set; } - public bool PrimaryKey { get; set; } - bool IColumn.Identity { get { return false; } } - bool IColumn.IdentityBehaviour { get { return false; } } - public int? Size { get; set; } - public string? Collation { get; set; } - public int? Scale { get; set; } - Table? IColumn.ReferenceTable { get { return null; } } - public bool AvoidForeignKey { get { return false; } } - public string? Default { get; set; } - - public FieldValue(PropertyRoute route, Type? fieldType, string name) - : base(route, fieldType) - { - this.Name = name; + return GeneratedAlwaysType.None; + } } - public override string ToString() + public interface IFieldReference { - return "{0} {1} ({2},{3},{4})".FormatWith( - Name, - DbType, - Nullable.ToBool() ? "Nullable" : "", - Size, - Scale); + bool IsLite { get; } + bool ClearEntityOnSaving { get; set; } + bool AvoidExpandOnRetrieving { get; } + Type FieldType { get; } } - public override IEnumerable Columns() + public partial class FieldPrimaryKey : Field, IColumn { - return new[] { this }; - } + public string Name { get; set; } + IsNullable IColumn.Nullable { get { return IsNullable.No; } } + public AbstractDbType DbType { get; set; } + public string? UserDefinedTypeName { get; set; } + bool IColumn.PrimaryKey { get { return true; } } + public bool Identity { get; set; } + bool IColumn.IdentityBehaviour { get { return table.IdentityBehaviour; } } + public int? Size { get; set; } + byte? IColumn.Precision { get { return null; } } + byte? IColumn.Scale { get { return null; } } + public string? Collation { get; set; } + Table? IColumn.ReferenceTable { get { return null; } } + public Type Type { get; set; } + public bool AvoidForeignKey { get { return false; } } + public string? Default { get; set; } - internal override IEnumerable> GetTables() - { - return Enumerable.Empty>(); - } + Table table; + public FieldPrimaryKey(PropertyRoute route, Table table, string name, Type type) + : base(route) + { + this.table = table; + this.Name = name; + this.Type = type; + } - internal override IEnumerable TablesMList() - { - return Enumerable.Empty(); - } + public override string ToString() + { + return "{0} PrimaryKey".FormatWith(Name); + } - public virtual Type Type - { - get { return this.Nullable.ToBool() ? this.FieldType.Nullify() : this.FieldType; } - } -} + public override IEnumerable Columns() + { + return new[] { this }; + } -public partial class FieldTicks : FieldValue -{ - public new Type Type { get; set; } + public override IEnumerable GenerateIndexes(ITable table) + { + if (this.UniqueIndex != null) + throw new InvalidOperationException("Changing IndexType is not allowed for FieldPrimaryKey"); - public FieldTicks(PropertyRoute route, Type type, string name) - : base(route, null, name) - { - this.Type = type; + return new[] { new PrimaryKeyIndex(table) }; + } + + internal override IEnumerable> GetTables() + { + return Enumerable.Empty>(); + } + + internal override IEnumerable TablesMList() + { + return Enumerable.Empty(); + } } -} -public partial class FieldEmbedded : Field, IFieldFinder -{ - public partial class EmbeddedHasValueColumn : IColumn + public partial class FieldValue : Field, IColumn { public string Name { get; set; } - public IsNullable Nullable { get { return IsNullable.No; } } //even on neasted embeddeds - public AbstractDbType DbType => new AbstractDbType(SqlDbType.Bit, NpgsqlDbType.Boolean); - string? IColumn.UserDefinedTypeName { get { return null; } } - bool IColumn.PrimaryKey { get { return false; } } + public IsNullable Nullable { get; set; } + public AbstractDbType DbType { get; set; } + public string? UserDefinedTypeName { get; set; } + public bool PrimaryKey { get; set; } bool IColumn.Identity { get { return false; } } bool IColumn.IdentityBehaviour { get { return false; } } - int? IColumn.Size { get { return null; } } - int? IColumn.Scale { get { return null; } } - string? IColumn.Collation { get { return null; } } - public Table? ReferenceTable { get { return null; } } - Type IColumn.Type { get { return typeof(bool); } } + public int? Size { get; set; } + public string? Collation { get; set; } + public byte? Precision { get; set; } + public byte? Scale { get; set; } + Table? IColumn.ReferenceTable { get { return null; } } public bool AvoidForeignKey { get { return false; } } public string? Default { get; set; } - public EmbeddedHasValueColumn(string name) + public FieldValue(PropertyRoute route, Type? fieldType, string name) + : base(route, fieldType) { - Name = name; + this.Name = name; } - } - public EmbeddedHasValueColumn? HasValue { get; set; } + public override string ToString() + { + return "{0} {1} ({2},{3},{4},{5})".FormatWith( + Name, + DbType, + Nullable.ToBool() ? "Nullable" : "", + Size, + Precision, + Scale); + } - public Dictionary EmbeddedFields { get; set; } - public Dictionary? Mixins { get; set; } + public override IEnumerable Columns() + { + return new[] { this }; + } - public FieldEmbedded(PropertyRoute route, EmbeddedHasValueColumn? hasValue, Dictionary embeddedFields, Dictionary? mixins) - : base(route) - { - this.HasValue = hasValue; - this.EmbeddedFields = embeddedFields; - this.Mixins = mixins; + internal override IEnumerable> GetTables() + { + return Enumerable.Empty>(); + } + + internal override IEnumerable TablesMList() + { + return Enumerable.Empty(); + } + + public virtual Type Type + { + get { return this.Nullable.ToBool() ? this.FieldType.Nullify() : this.FieldType; } + } } - public override string ToString() + public partial class FieldTicks : FieldValue { - return "\r\n".Combine( - "Embedded", - EmbeddedFields.ToString(c => "{0} : {1}".FormatWith(c.Key, c.Value), "\r\n").Indent(2), - Mixins == null ? null : Mixins.ToString(m => "Mixin {0} : {1}".FormatWith(m.Key.Name, m.Value.ToString()), "\r\n") - ); + public new Type Type { get; set; } + + public FieldTicks(PropertyRoute route, Type type, string name) + : base(route, null, name) + { + this.Type = type; + } } - public Field GetField(MemberInfo member) + public partial class FieldEmbedded : Field, IFieldFinder { - Type? mixinType = member as Type ?? Table.GetMixinType(member); - if (mixinType != null) + public partial class EmbeddedHasValueColumn : IColumn { - if (Mixins == null) - throw new InvalidOperationException("{0} has not mixins".FormatWith(this.Route.Type.Name)); - - return Mixins.GetOrThrow(mixinType); + public string Name { get; set; } + public IsNullable Nullable { get { return IsNullable.No; } } //even on neasted embeddeds + public AbstractDbType DbType => new AbstractDbType(SqlDbType.Bit, NpgsqlDbType.Boolean); + string? IColumn.UserDefinedTypeName { get { return null; } } + bool IColumn.PrimaryKey { get { return false; } } + bool IColumn.Identity { get { return false; } } + bool IColumn.IdentityBehaviour { get { return false; } } + int? IColumn.Size { get { return null; } } + byte? IColumn.Precision { get { return null; } } + byte? IColumn.Scale { get { return null; } } + string? IColumn.Collation { get { return null; } } + public Table? ReferenceTable { get { return null; } } + Type IColumn.Type { get { return typeof(bool); } } + public bool AvoidForeignKey { get { return false; } } + public string? Default { get; set; } + + public EmbeddedHasValueColumn(string name) + { + Name = name; + } } - FieldInfo fi = member as FieldInfo ?? Reflector.FindFieldInfo(FieldType, (PropertyInfo)member); - - if (fi == null) - throw new InvalidOperationException("Field {0} not found on {1}".FormatWith(member.Name, FieldType)); - - EntityField field = EmbeddedFields.GetOrThrow(fi.Name, "Field {0} not found on schema"); + public EmbeddedHasValueColumn? HasValue { get; set; } - return field.Field; - } + public Dictionary EmbeddedFields { get; set; } + public Dictionary? Mixins { get; set; } - public Field? TryGetField(MemberInfo member) - { - Type? mixinType = member as Type ?? Table.GetMixinType(member); - if (mixinType != null) + public FieldEmbedded(PropertyRoute route, EmbeddedHasValueColumn? hasValue, Dictionary embeddedFields, Dictionary? mixins) + : base(route) { - return Mixins?.TryGetC(mixinType); + this.HasValue = hasValue; + this.EmbeddedFields = embeddedFields; + this.Mixins = mixins; } - FieldInfo fi = member as FieldInfo ?? Reflector.TryFindFieldInfo(FieldType, (PropertyInfo)member)!; + public override string ToString() + { + return "\r\n".Combine( + "Embedded", + EmbeddedFields.ToString(c => "{0} : {1}".FormatWith(c.Key, c.Value), "\r\n").Indent(2), + Mixins == null ? null : Mixins.ToString(m => "Mixin {0} : {1}".FormatWith(m.Key.Name, m.Value.ToString()), "\r\n") + ); + } - if (fi == null) - return null; + public Field GetField(MemberInfo member) + { + Type? mixinType = member as Type ?? Table.GetMixinType(member); + if (mixinType != null) + { + if (Mixins == null) + throw new InvalidOperationException("{0} has not mixins".FormatWith(this.Route.Type.Name)); - EntityField? field = EmbeddedFields.TryGetC(fi.Name); + return Mixins.GetOrThrow(mixinType); + } - if (field == null) - return null; + FieldInfo fi = member as FieldInfo ?? Reflector.FindFieldInfo(FieldType, (PropertyInfo)member); - return field.Field; - } + if (fi == null) + throw new InvalidOperationException("Field {0} not found on {1}".FormatWith(member.Name, FieldType)); - public IEnumerable FindFields(Func predicate) - { - if (predicate(this)) - return new[] { this }; + EntityField field = EmbeddedFields.GetOrThrow(fi.Name, "Field {0} not found on schema"); - var fields = EmbeddedFields.Values.Select(a => a.Field).SelectMany(f => predicate(f) ? new[] { f } : - f is IFieldFinder ff ? ff.FindFields(predicate) : - Enumerable.Empty()).ToList(); + return field.Field; + } - if (Mixins != null) + public Field? TryGetField(MemberInfo member) { - foreach (var mixin in this.Mixins.Values) + Type? mixinType = member as Type ?? Table.GetMixinType(member); + if (mixinType != null) { - fields.AddRange(mixin.FindFields(predicate)); + return Mixins?.TryGetC(mixinType); } - } - return fields; - } - public override IEnumerable Columns() - { - var result = new List(); + FieldInfo fi = member as FieldInfo ?? Reflector.TryFindFieldInfo(FieldType, (PropertyInfo)member)!; - if (HasValue != null) - result.Add(HasValue); + if (fi == null) + return null; - result.AddRange(EmbeddedFields.Values.SelectMany(c => c.Field.Columns())); + EntityField? field = EmbeddedFields.TryGetC(fi.Name); - if (Mixins != null) - result.AddRange(Mixins.Values.SelectMany(c => c.Columns())); + if (field == null) + return null; - return result; - } + return field.Field; + } - public override IEnumerable GenerateIndexes(ITable table) - { - return this.EmbeddedFields.Values.SelectMany(f => f.Field.GenerateIndexes(table)) - .Concat((this.Mixins?.Values.SelectMany(a => a.Fields.Values).SelectMany(f => f.Field.GenerateIndexes(table))).EmptyIfNull()); - } + public IEnumerable FindFields(Func predicate) + { + if (predicate(this)) + return new[] { this }; - internal override IEnumerable> GetTables() - { + var fields = EmbeddedFields.Values.Select(a => a.Field).SelectMany(f => predicate(f) ? new[] { f } : + f is IFieldFinder ff ? ff.FindFields(predicate) : + Enumerable.Empty()).ToList(); - foreach (var f in EmbeddedFields.Values) - { - foreach (var kvp in f.Field.GetTables()) + if (Mixins != null) { - yield return kvp; + foreach (var mixin in this.Mixins.Values) + { + fields.AddRange(mixin.FindFields(predicate)); + } } + return fields; } - if (Mixins != null) + public override IEnumerable Columns() { - foreach (var mi in Mixins.Values) + var result = new List(); + + if (HasValue != null) + result.Add(HasValue); + + result.AddRange(EmbeddedFields.Values.SelectMany(c => c.Field.Columns())); + + if (Mixins != null) + result.AddRange(Mixins.Values.SelectMany(c => c.Columns())); + + return result; + } + + public override IEnumerable GenerateIndexes(ITable table) + { + return this.EmbeddedFields.Values.SelectMany(f => f.Field.GenerateIndexes(table)) + .Concat((this.Mixins?.Values.SelectMany(a => a.Fields.Values).SelectMany(f => f.Field.GenerateIndexes(table))).EmptyIfNull()); + } + + internal override IEnumerable> GetTables() + { + + foreach (var f in EmbeddedFields.Values) { - foreach (var f in mi.Fields.Values) + foreach (var kvp in f.Field.GetTables()) { - foreach (var kvp in f.Field.GetTables()) + yield return kvp; + } + } + + if (Mixins != null) + { + foreach (var mi in Mixins.Values) + { + foreach (var f in mi.Fields.Values) { - yield return kvp; + foreach (var kvp in f.Field.GetTables()) + { + yield return kvp; + } } } } } - } - internal override IEnumerable TablesMList() - { - return EmbeddedFields.Values.SelectMany(e => e.Field.TablesMList()); - } + internal override IEnumerable TablesMList() + { + return EmbeddedFields.Values.SelectMany(e => e.Field.TablesMList()); + } - internal FieldEmbedded.EmbeddedHasValueColumn? GetHasValueColumn(IColumn column) - { - var enbeddedHasValue = this.EmbeddedFields.Select(a => a.Value.Field).OfType().Select(f => f.GetHasValueColumn(column)).NotNull().SingleOrDefaultEx(); - if (enbeddedHasValue != null) - return enbeddedHasValue; + internal FieldEmbedded.EmbeddedHasValueColumn? GetHasValueColumn(IColumn column) + { + var enbeddedHasValue = this.EmbeddedFields.Select(a => a.Value.Field).OfType().Select(f => f.GetHasValueColumn(column)).NotNull().SingleOrDefaultEx(); + if (enbeddedHasValue != null) + return enbeddedHasValue; - var mixinHasValue = this.Mixins?.Select(a => a.Value).Select(f => f.GetHasValueColumn(column)).NotNull().SingleOrDefaultEx(); - if (mixinHasValue != null) - return mixinHasValue; + var mixinHasValue = this.Mixins?.Select(a => a.Value).Select(f => f.GetHasValueColumn(column)).NotNull().SingleOrDefaultEx(); + if (mixinHasValue != null) + return mixinHasValue; - return this.Columns().Contains(column) ? this.HasValue : null; + return this.Columns().Contains(column) ? this.HasValue : null; + } } -} -public partial class FieldMixin : Field, IFieldFinder -{ - public Dictionary Fields { get; set; } + public partial class FieldMixin : Field, IFieldFinder + { + public Dictionary Fields { get; set; } - public ITable MainEntityTable; + public ITable MainEntityTable; - public FieldMixin(PropertyRoute route, ITable mainEntityTable, Dictionary fields) - : base(route) - { - this.MainEntityTable = mainEntityTable; - this.Fields = fields; - } + public FieldMixin(PropertyRoute route, ITable mainEntityTable, Dictionary fields) + : base(route) + { + this.MainEntityTable = mainEntityTable; + this.Fields = fields; + } - public override string ToString() - { - return "Mixin\r\n{0}".FormatWith(Fields.ToString(c => "{0} : {1}".FormatWith(c.Key, c.Value), "\r\n").Indent(2)); - } + public override string ToString() + { + return "Mixin\r\n{0}".FormatWith(Fields.ToString(c => "{0} : {1}".FormatWith(c.Key, c.Value), "\r\n").Indent(2)); + } - public Field GetField(MemberInfo member) - { - FieldInfo fi = member as FieldInfo ?? Reflector.FindFieldInfo(FieldType, (PropertyInfo)member); + public Field GetField(MemberInfo member) + { + FieldInfo fi = member as FieldInfo ?? Reflector.FindFieldInfo(FieldType, (PropertyInfo)member); - if (fi == null) - throw new InvalidOperationException("Field {0} not found on {1}".FormatWith(member.Name, FieldType)); + if (fi == null) + throw new InvalidOperationException("Field {0} not found on {1}".FormatWith(member.Name, FieldType)); - EntityField field = Fields.GetOrThrow(fi.Name, "Field {0} not found on schema"); + EntityField field = Fields.GetOrThrow(fi.Name, "Field {0} not found on schema"); - return field.Field; - } + return field.Field; + } - public Field? TryGetField(MemberInfo value) - { - FieldInfo fi = value as FieldInfo ?? Reflector.TryFindFieldInfo(FieldType, (PropertyInfo)value)!; + public Field? TryGetField(MemberInfo value) + { + FieldInfo fi = value as FieldInfo ?? Reflector.TryFindFieldInfo(FieldType, (PropertyInfo)value)!; - if (fi == null) - return null; + if (fi == null) + return null; - EntityField? field = Fields.TryGetC(fi.Name); + EntityField? field = Fields.TryGetC(fi.Name); - if (field == null) - return null; + if (field == null) + return null; - return field.Field; - } + return field.Field; + } - public IEnumerable FindFields(Func predicate) - { - if (predicate(this)) - return new[] { this }; + public IEnumerable FindFields(Func predicate) + { + if (predicate(this)) + return new[] { this }; - return Fields.Values.Select(a => a.Field).SelectMany(f => predicate(f) ? new[] { f } : - f is IFieldFinder ff ? ff.FindFields(predicate) : - Enumerable.Empty()).ToList(); - } + return Fields.Values.Select(a => a.Field).SelectMany(f => predicate(f) ? new[] { f } : + f is IFieldFinder ff ? ff.FindFields(predicate) : + Enumerable.Empty()).ToList(); + } - public override IEnumerable Columns() - { - var result = new List(); - result.AddRange(Fields.Values.SelectMany(c => c.Field.Columns())); + public override IEnumerable Columns() + { + var result = new List(); + result.AddRange(Fields.Values.SelectMany(c => c.Field.Columns())); - return result; - } + return result; + } - public override IEnumerable GenerateIndexes(ITable table) - { - throw new InvalidOperationException(); - } + public override IEnumerable GenerateIndexes(ITable table) + { + throw new InvalidOperationException(); + } - internal override IEnumerable> GetTables() - { - foreach (var f in Fields.Values) + internal override IEnumerable> GetTables() { - foreach (var kvp in f.Field.GetTables()) + foreach (var f in Fields.Values) { - yield return kvp; + foreach (var kvp in f.Field.GetTables()) + { + yield return kvp; + } } } - } - internal override IEnumerable TablesMList() - { - return Fields.Values.SelectMany(e => e.Field.TablesMList()); - } + internal override IEnumerable TablesMList() + { + return Fields.Values.SelectMany(e => e.Field.TablesMList()); + } - internal MixinEntity Getter(Entity ident) - { - return ((Entity)ident).GetMixin(FieldType); - } + internal MixinEntity Getter(Entity ident) + { + return ((Entity)ident).GetMixin(FieldType); + } - internal FieldEmbedded.EmbeddedHasValueColumn? GetHasValueColumn(IColumn column) - { - return Fields.Select(a => a.Value.Field).OfType().Select(f => f.GetHasValueColumn(column)).NotNull().SingleOrDefaultEx(); - } -} - -public partial class FieldReference : Field, IColumn, IFieldReference -{ - public string Name { get; set; } - public IsNullable Nullable { get; set; } - - public bool PrimaryKey { get; set; } //For View - bool IColumn.Identity { get { return false; } } - bool IColumn.IdentityBehaviour { get { return false; } } - int? IColumn.Size { get { return this.ReferenceTable.PrimaryKey.Size; } } - int? IColumn.Scale { get { return null; } } - public Table ReferenceTable { get; set; } - Table? IColumn.ReferenceTable => ReferenceTable; - public AbstractDbType DbType { get { return ReferenceTable.PrimaryKey.DbType; } } - public string? Collation { get { return ReferenceTable.PrimaryKey.Collation; } } - public string? UserDefinedTypeName { get { return ReferenceTable.PrimaryKey.UserDefinedTypeName; } } - public virtual Type Type { get { return this.Nullable.ToBool() ? ReferenceTable.PrimaryKey.Type.Nullify() : ReferenceTable.PrimaryKey.Type; } } - - public bool AvoidForeignKey { get; set; } - - public bool IsLite { get; internal set; } - public bool AvoidExpandOnRetrieving { get; set; } - public string? Default { get; set; } - - public FieldReference(PropertyRoute route, Type? fieldType, string name, Table referenceTable) : base(route, fieldType) - { - this.Name = name; - this.ReferenceTable = referenceTable; + internal FieldEmbedded.EmbeddedHasValueColumn? GetHasValueColumn(IColumn column) + { + return Fields.Select(a => a.Value.Field).OfType().Select(f => f.GetHasValueColumn(column)).NotNull().SingleOrDefaultEx(); + } } - public override string ToString() + public partial class FieldReference : Field, IColumn, IFieldReference { - return "{0} -> {1} {3} ({2})".FormatWith( - Name, - ReferenceTable.Name, - IsLite ? "Lite" : "", - Nullable.ToBool() ? "Nullable" : ""); - } + public string Name { get; set; } + public IsNullable Nullable { get; set; } - public override IEnumerable Columns() - { - return new[] { this }; - } + public bool PrimaryKey { get; set; } //For View + bool IColumn.Identity { get { return false; } } + bool IColumn.IdentityBehaviour { get { return false; } } + int? IColumn.Size { get { return this.ReferenceTable.PrimaryKey.Size; } } + byte? IColumn.Precision { get { return null; } } + byte? IColumn.Scale { get { return null; } } + public Table ReferenceTable { get; set; } + Table? IColumn.ReferenceTable => ReferenceTable; + public AbstractDbType DbType { get { return ReferenceTable.PrimaryKey.DbType; } } + public string? Collation { get { return ReferenceTable.PrimaryKey.Collation; } } + public string? UserDefinedTypeName { get { return ReferenceTable.PrimaryKey.UserDefinedTypeName; } } + public virtual Type Type { get { return this.Nullable.ToBool() ? ReferenceTable.PrimaryKey.Type.Nullify() : ReferenceTable.PrimaryKey.Type; } } + + public bool AvoidForeignKey { get; set; } + + public bool IsLite { get; internal set; } + public bool AvoidExpandOnRetrieving { get; set; } + public string? Default { get; set; } - internal override IEnumerable> GetTables() - { - yield return KeyValuePair.Create(ReferenceTable, new RelationInfo + public FieldReference(PropertyRoute route, Type? fieldType, string name, Table referenceTable) : base(route, fieldType) { - IsLite = IsLite, - IsCollection = false, - IsNullable = Nullable.ToBool(), - PropertyRoute = this.Route - }); - } + this.Name = name; + this.ReferenceTable = referenceTable; + } - public override IEnumerable GenerateIndexes(ITable table) - { - if (UniqueIndex == null) - return new[] { new TableIndex(table, (IColumn)this) }; + public override string ToString() + { + return "{0} -> {1} {3} ({2})".FormatWith( + Name, + ReferenceTable.Name, + IsLite ? "Lite" : "", + Nullable.ToBool() ? "Nullable" : ""); + } - return base.GenerateIndexes(table); - } + public override IEnumerable Columns() + { + return new[] { this }; + } - bool clearEntityOnSaving; - public bool ClearEntityOnSaving - { - get + internal override IEnumerable> GetTables() + { + yield return KeyValuePair.Create(ReferenceTable, new RelationInfo + { + IsLite = IsLite, + IsCollection = false, + IsNullable = Nullable.ToBool(), + PropertyRoute = this.Route + }); + } + + public override IEnumerable GenerateIndexes(ITable table) { - this.AssertIsLite(); - return this.clearEntityOnSaving; + if (UniqueIndex == null) + return new[] { new TableIndex(table, (IColumn)this) }; + + return base.GenerateIndexes(table); } - set + + bool clearEntityOnSaving; + public bool ClearEntityOnSaving { - this.AssertIsLite(); - this.clearEntityOnSaving = value; + get + { + this.AssertIsLite(); + return this.clearEntityOnSaving; + } + set + { + this.AssertIsLite(); + this.clearEntityOnSaving = value; + } } - } - internal override IEnumerable TablesMList() - { - return Enumerable.Empty(); + internal override IEnumerable TablesMList() + { + return Enumerable.Empty(); + } } -} -public partial class FieldEnum : FieldReference, IColumn -{ - public override Type Type + public partial class FieldEnum : FieldReference, IColumn { - get + public override Type Type { - if (this.ReferenceTable != null) - return base.Type; + get + { + if (this.ReferenceTable != null) + return base.Type; - var ut = Enum.GetUnderlyingType(this.FieldType.UnNullify()); + var ut = Enum.GetUnderlyingType(this.FieldType.UnNullify()); - return this.Nullable.ToBool() ? ut.Nullify() : ut; + return this.Nullable.ToBool() ? ut.Nullify() : ut; + } } - } - public FieldEnum(PropertyRoute route, string name, Table referenceTable) : base(route, null, name, referenceTable) { } + public FieldEnum(PropertyRoute route, string name, Table referenceTable) : base(route, null, name, referenceTable) { } - public override string ToString() - { - return "{0} -> {1} {3} ({2})".FormatWith( - Name, - "-", - IsLite ? "Lite" : "", - Nullable.ToBool() ? "Nullable" : ""); - } + public override string ToString() + { + return "{0} -> {1} {3} ({2})".FormatWith( + Name, + "-", + IsLite ? "Lite" : "", + Nullable.ToBool() ? "Nullable" : ""); + } - internal override IEnumerable> GetTables() - { - if (ReferenceTable == null) - yield break; + internal override IEnumerable> GetTables() + { + if (ReferenceTable == null) + yield break; - yield return KeyValuePair.Create(ReferenceTable, new RelationInfo + yield return KeyValuePair.Create(ReferenceTable, new RelationInfo + { + IsLite = IsLite, + IsCollection = false, + IsNullable = Nullable.ToBool(), + IsEnum = true, + PropertyRoute = this.Route + }); + } + + internal override IEnumerable TablesMList() { - IsLite = IsLite, - IsCollection = false, - IsNullable = Nullable.ToBool(), - IsEnum = true, - PropertyRoute = this.Route - }); + return Enumerable.Empty(); + } } - internal override IEnumerable TablesMList() + public partial class FieldImplementedBy : Field, IFieldReference { - return Enumerable.Empty(); - } -} - -public partial class FieldImplementedBy : Field, IFieldReference -{ - public bool IsLite { get; internal set; } - public CombineStrategy SplitStrategy { get; internal set; } - public bool AvoidExpandOnRetrieving { get; internal set; } + public bool IsLite { get; internal set; } + public CombineStrategy SplitStrategy { get; internal set; } + public bool AvoidExpandOnRetrieving { get; internal set; } - public Dictionary ImplementationColumns { get; set; } + public Dictionary ImplementationColumns { get; set; } - public FieldImplementedBy(PropertyRoute route, Dictionary implementations) : base(route, null) - { - this.ImplementationColumns = implementations; - } + public FieldImplementedBy(PropertyRoute route, Dictionary implementations) : base(route, null) + { + this.ImplementationColumns = implementations; + } - public override string ToString() - { - return "ImplementedBy\r\n{0}".FormatWith(ImplementationColumns.ToString(k => "{0} -> {1} ({2})".FormatWith(k.Value.Name, k.Value.ReferenceTable.Name, k.Key.Name), "\r\n").Indent(2)); - } + public override string ToString() + { + return "ImplementedBy\r\n{0}".FormatWith(ImplementationColumns.ToString(k => "{0} -> {1} ({2})".FormatWith(k.Value.Name, k.Value.ReferenceTable.Name, k.Key.Name), "\r\n").Indent(2)); + } - public override IEnumerable Columns() - { - return ImplementationColumns.Values.Cast(); - } + public override IEnumerable Columns() + { + return ImplementationColumns.Values.Cast(); + } - internal override IEnumerable> GetTables() - { - return ImplementationColumns.Select(a => KeyValuePair.Create(a.Value.ReferenceTable, new RelationInfo + internal override IEnumerable> GetTables() { - IsLite = IsLite, - IsCollection = false, - IsNullable = a.Value.Nullable.ToBool(), - PropertyRoute = this.Route - })); - } + return ImplementationColumns.Select(a => KeyValuePair.Create(a.Value.ReferenceTable, new RelationInfo + { + IsLite = IsLite, + IsCollection = false, + IsNullable = a.Value.Nullable.ToBool(), + PropertyRoute = this.Route + })); + } - public override IEnumerable GenerateIndexes(ITable table) - { - return this.Columns().Select(c => new TableIndex(table, c)).Concat(base.GenerateIndexes(table)); - } + public override IEnumerable GenerateIndexes(ITable table) + { + return this.Columns().Select(c => new TableIndex(table, c)).Concat(base.GenerateIndexes(table)); + } - bool clearEntityOnSaving; - public bool ClearEntityOnSaving - { - get + bool clearEntityOnSaving; + public bool ClearEntityOnSaving { - this.AssertIsLite(); - return this.clearEntityOnSaving; + get + { + this.AssertIsLite(); + return this.clearEntityOnSaving; + } + set + { + this.AssertIsLite(); + this.clearEntityOnSaving = value; + } } - set + + internal override IEnumerable TablesMList() { - this.AssertIsLite(); - this.clearEntityOnSaving = value; + return Enumerable.Empty(); } } - internal override IEnumerable TablesMList() + public partial class FieldImplementedByAll : Field, IFieldReference { - return Enumerable.Empty(); - } -} + public bool IsLite { get; internal set; } -public partial class FieldImplementedByAll : Field, IFieldReference -{ - public bool IsLite { get; internal set; } + public bool AvoidExpandOnRetrieving { get; internal set; } - public bool AvoidExpandOnRetrieving { get; internal set; } + public ImplementationStringColumn Column { get; set; } + public ImplementationColumn ColumnType { get; set; } - public ImplementationStringColumn Column { get; set; } - public ImplementationColumn ColumnType { get; set; } + public FieldImplementedByAll(PropertyRoute route, ImplementationStringColumn column, ImplementationColumn columnType) : base(route) + { + this.Column = column; + this.ColumnType = columnType; + } - public FieldImplementedByAll(PropertyRoute route, ImplementationStringColumn column, ImplementationColumn columnType) : base(route) - { - this.Column = column; - this.ColumnType = columnType; - } + public override IEnumerable Columns() + { + return new IColumn[] { Column, ColumnType }; + } - public override IEnumerable Columns() - { - return new IColumn[] { Column, ColumnType }; - } + internal override IEnumerable> GetTables() + { + yield return KeyValuePair.Create(ColumnType.ReferenceTable, new RelationInfo + { + IsNullable = this.ColumnType.Nullable.ToBool(), + IsLite = this.IsLite, + IsImplementedByAll = true, + PropertyRoute = this.Route + }); + } - internal override IEnumerable> GetTables() - { - yield return KeyValuePair.Create(ColumnType.ReferenceTable, new RelationInfo + bool clearEntityOnSaving; + public bool ClearEntityOnSaving { - IsNullable = this.ColumnType.Nullable.ToBool(), - IsLite = this.IsLite, - IsImplementedByAll = true, - PropertyRoute = this.Route - }); - } + get + { + this.AssertIsLite(); + return this.clearEntityOnSaving; + } + set + { + this.AssertIsLite(); + this.clearEntityOnSaving = value; + } + } - bool clearEntityOnSaving; - public bool ClearEntityOnSaving - { - get + public override IEnumerable GenerateIndexes(ITable table) { - this.AssertIsLite(); - return this.clearEntityOnSaving; + if (UniqueIndex == null) + return new[] { new TableIndex(table, (IColumn)this.Column, (IColumn)this.ColumnType) }; + + return base.GenerateIndexes(table); } - set + + internal override IEnumerable TablesMList() { - this.AssertIsLite(); - this.clearEntityOnSaving = value; + return Enumerable.Empty(); } } - public override IEnumerable GenerateIndexes(ITable table) + public partial class ImplementationColumn : IColumn { - if (UniqueIndex == null) - return new[] { new TableIndex(table, (IColumn)this.Column, (IColumn)this.ColumnType) }; - - return base.GenerateIndexes(table); - } + public string Name { get; set; } + public IsNullable Nullable { get; set; } + bool IColumn.PrimaryKey { get { return false; } } + bool IColumn.Identity { get { return false; } } + bool IColumn.IdentityBehaviour { get { return false; } } + int? IColumn.Size { get { return null; } } + byte? IColumn.Precision { get { return null; } } + byte? IColumn.Scale { get { return null; } } + public Table ReferenceTable { get; private set; } + Table? IColumn.ReferenceTable => ReferenceTable; + public AbstractDbType DbType { get { return ReferenceTable.PrimaryKey.DbType; } } + public string? Collation { get { return ReferenceTable.PrimaryKey.Collation; } } + public string? UserDefinedTypeName { get { return ReferenceTable.PrimaryKey.UserDefinedTypeName; } } + public Type Type { get { return this.Nullable.ToBool() ? ReferenceTable.PrimaryKey.Type.Nullify() : ReferenceTable.PrimaryKey.Type; } } + public bool AvoidForeignKey { get; set; } + public string? Default { get; set; } - internal override IEnumerable TablesMList() - { - return Enumerable.Empty(); - } -} - -public partial class ImplementationColumn : IColumn -{ - public string Name { get; set; } - public IsNullable Nullable { get; set; } - bool IColumn.PrimaryKey { get { return false; } } - bool IColumn.Identity { get { return false; } } - bool IColumn.IdentityBehaviour { get { return false; } } - int? IColumn.Size { get { return null; } } - int? IColumn.Scale { get { return null; } } - public Table ReferenceTable { get; private set; } - Table? IColumn.ReferenceTable => ReferenceTable; - public AbstractDbType DbType { get { return ReferenceTable.PrimaryKey.DbType; } } - public string? Collation { get { return ReferenceTable.PrimaryKey.Collation; } } - public string? UserDefinedTypeName { get { return ReferenceTable.PrimaryKey.UserDefinedTypeName; } } - public Type Type { get { return this.Nullable.ToBool() ? ReferenceTable.PrimaryKey.Type.Nullify() : ReferenceTable.PrimaryKey.Type; } } - public bool AvoidForeignKey { get; set; } - public string? Default { get; set; } - - public ImplementationColumn(string name, Table referenceTable) - { - Name = name; - ReferenceTable = referenceTable; - } -} - -public partial class ImplementationStringColumn : IColumn -{ - public string Name { get; set; } - public IsNullable Nullable { get; set; } - string? IColumn.UserDefinedTypeName { get { return null; } } - bool IColumn.PrimaryKey { get { return false; } } - bool IColumn.Identity { get { return false; } } - bool IColumn.IdentityBehaviour { get { return false; } } - public int? Size { get; set; } - int? IColumn.Scale { get { return null; } } - public string? Collation { get; set; } - public Table? ReferenceTable { get { return null; } } - public AbstractDbType DbType => new AbstractDbType(SqlDbType.NVarChar, NpgsqlDbType.Varchar); - public Type Type { get { return typeof(string); } } - public bool AvoidForeignKey { get { return false; } } - public string? Default { get; set; } - - public ImplementationStringColumn(string name) - { - Name = name; + public ImplementationColumn(string name, Table referenceTable) + { + Name = name; + ReferenceTable = referenceTable; + } } -} -public partial class FieldMList : Field, IFieldFinder -{ - public TableMList TableMList { get; set; } - - public FieldMList(PropertyRoute route, TableMList tableMList) : base(route) + public partial class ImplementationStringColumn : IColumn { - this.TableMList = tableMList; - } + public string Name { get; set; } + public IsNullable Nullable { get; set; } + string? IColumn.UserDefinedTypeName { get { return null; } } + bool IColumn.PrimaryKey { get { return false; } } + bool IColumn.Identity { get { return false; } } + bool IColumn.IdentityBehaviour { get { return false; } } + public int? Size { get; set; } + byte? IColumn.Precision { get { return null; } } + byte? IColumn.Scale { get { return null; } } + public string? Collation { get; set; } + public Table? ReferenceTable { get { return null; } } + public AbstractDbType DbType => new AbstractDbType(SqlDbType.NVarChar, NpgsqlDbType.Varchar); + public Type Type { get { return typeof(string); } } + public bool AvoidForeignKey { get { return false; } } + public string? Default { get; set; } - public override string ToString() - { - return "Coleccion\r\n{0}".FormatWith(TableMList.ToString().Indent(2)); + public ImplementationStringColumn(string name) + { + Name = name; + } } - public Field GetField(MemberInfo member) + public partial class FieldMList : Field, IFieldFinder { - if (member.Name == "Item") - return TableMList.Field; + public TableMList TableMList { get; set; } - throw new InvalidOperationException("{0} not supported by MList field".FormatWith(member.Name)); - } + public FieldMList(PropertyRoute route, TableMList tableMList) : base(route) + { + this.TableMList = tableMList; + } - public Field? TryGetField(MemberInfo member) - { - if (member.Name == "Item") - return TableMList.Field; + public override string ToString() + { + return "Coleccion\r\n{0}".FormatWith(TableMList.ToString().Indent(2)); + } - return null; - } + public Field GetField(MemberInfo member) + { + if (member.Name == "Item") + return TableMList.Field; - public IEnumerable FindFields(Func predicate) - { - if (predicate(this)) - return new[] { this }; + throw new InvalidOperationException("{0} not supported by MList field".FormatWith(member.Name)); + } - return TableMList.FindFields(predicate); - - } + public Field? TryGetField(MemberInfo member) + { + if (member.Name == "Item") + return TableMList.Field; - public override IEnumerable Columns() - { - return Array.Empty(); - } + return null; + } - public override IEnumerable GenerateIndexes(ITable table) - { - if (UniqueIndex != null) - throw new InvalidOperationException("Changing IndexType is not allowed for FieldMList"); + public IEnumerable FindFields(Func predicate) + { + if (predicate(this)) + return new[] { this }; - return Enumerable.Empty(); - } + return TableMList.FindFields(predicate); + + } - internal override IEnumerable> GetTables() - { - foreach (var kvp in TableMList.GetTables()) + public override IEnumerable Columns() { - kvp.Value.IsCollection = true; - yield return kvp; + return Array.Empty(); } - } - internal override IEnumerable TablesMList() - { - return new[] { TableMList }; - } -} + public override IEnumerable GenerateIndexes(ITable table) + { + if (UniqueIndex != null) + throw new InvalidOperationException("Changing IndexType is not allowed for FieldMList"); -public partial class TableMList : ITable, IFieldFinder, ITablePrivate -{ - public class PrimaryKeyColumn : IColumn - { - public string Name { get; set; } - IsNullable IColumn.Nullable { get { return IsNullable.No; } } - public AbstractDbType DbType { get; set; } - public string? Collation { get; set; } - public string? UserDefinedTypeName { get; set; } - bool IColumn.PrimaryKey { get { return true; } } - public bool Identity { get; set; } - bool IColumn.IdentityBehaviour { get { return true; } } - int? IColumn.Size { get { return null; } } - int? IColumn.Scale { get { return null; } } - Table? IColumn.ReferenceTable { get { return null; } } - public Type Type { get; set; } - public bool AvoidForeignKey { get { return false; } } - public string? Default { get; set; } + return Enumerable.Empty(); + } - public PrimaryKeyColumn(Type type, string name) + internal override IEnumerable> GetTables() { - Type = type; - Name = name; + foreach (var kvp in TableMList.GetTables()) + { + kvp.Value.IsCollection = true; + yield return kvp; + } } + internal override IEnumerable TablesMList() + { + return new[] { TableMList }; + } } - public Dictionary Columns { get; set; } - public List? MultiColumnIndexes { get; set; } + public partial class TableMList : ITable, IFieldFinder, ITablePrivate + { + public class PrimaryKeyColumn : IColumn + { + public string Name { get; set; } + IsNullable IColumn.Nullable { get { return IsNullable.No; } } + public AbstractDbType DbType { get; set; } + public string? Collation { get; set; } + public string? UserDefinedTypeName { get; set; } + bool IColumn.PrimaryKey { get { return true; } } + public bool Identity { get; set; } + bool IColumn.IdentityBehaviour { get { return true; } } + int? IColumn.Size { get { return null; } } + byte? IColumn.Precision { get { return null; } } + byte? IColumn.Scale { get { return null; } } + Table? IColumn.ReferenceTable { get { return null; } } + public Type Type { get; set; } + public bool AvoidForeignKey { get { return false; } } + public string? Default { get; set; } + + public PrimaryKeyColumn(Type type, string name) + { + Type = type; + Name = name; + } + + } + + public Dictionary Columns { get; set; } + public List? MultiColumnIndexes { get; set; } - public ObjectName Name { get; set; } - public PrimaryKeyColumn PrimaryKey { get; set; } - public FieldReference BackReference { get; set; } - public FieldValue? Order { get; set; } - public Field Field { get; set; } + public ObjectName Name { get; set; } + public PrimaryKeyColumn PrimaryKey { get; set; } + public FieldReference BackReference { get; set; } + public FieldValue? Order { get; set; } + public Field Field { get; set; } - public SystemVersionedInfo? SystemVersioned { get; set; } + public SystemVersionedInfo? SystemVersioned { get; set; } - public Type CollectionType { get; private set; } + public Type CollectionType { get; private set; } - public PropertyRoute PropertyRoute { get; internal set; } - Func? getter; - public Func Getter => getter ?? (getter = PropertyRoute.GetLambdaExpression(true).Compile()); + public PropertyRoute PropertyRoute { get; internal set; } + Func? getter; + public Func Getter => getter ?? (getter = PropertyRoute.GetLambdaExpression(true).Compile()); #pragma warning disable CS8618 // Non-nullable field is uninitialized. - public TableMList(Type collectionType, ObjectName name, PrimaryKeyColumn primaryKey, FieldReference backReference) - { - this.Name = name; - this.PrimaryKey = primaryKey; - this.BackReference = backReference; - this.CollectionType = collectionType; - this.cache = new Lazy(() => (IMListCache)giCreateCache.GetInvoker(this.Field!.FieldType)(this)); - } + public TableMList(Type collectionType, ObjectName name, PrimaryKeyColumn primaryKey, FieldReference backReference) + { + this.Name = name; + this.PrimaryKey = primaryKey; + this.BackReference = backReference; + this.CollectionType = collectionType; + this.cache = new Lazy(() => (IMListCache)giCreateCache.GetInvoker(this.Field!.FieldType)(this)); + } #pragma warning restore CS8618 // Non-nullable field is uninitialized. - public override string ToString() - { - return "[{0}]\r\n {1}\r\n {2}".FormatWith(Name, BackReference.Name, Field.ToString()); - } + public override string ToString() + { + return "[{0}]\r\n {1}\r\n {2}".FormatWith(Name, BackReference.Name, Field.ToString()); + } - public void GenerateColumns() - { - List cols = new List { PrimaryKey, BackReference }; + public void GenerateColumns() + { + List cols = new List { PrimaryKey, BackReference }; - if (Order != null) - cols.Add(Order); + if (Order != null) + cols.Add(Order); - cols.AddRange(Field.Columns()); + cols.AddRange(Field.Columns()); - if (this.SystemVersioned != null) - cols.AddRange(this.SystemVersioned.Columns()); + if (this.SystemVersioned != null) + cols.AddRange(this.SystemVersioned.Columns()); - Columns = cols.ToDictionary(a => a.Name); - } + Columns = cols.ToDictionary(a => a.Name); + } - public List GeneratAllIndexes() - { - var result = new List + public List GeneratAllIndexes() { - new PrimaryKeyIndex(this) - }; + var result = new List + { + new PrimaryKeyIndex(this) + }; - result.AddRange(BackReference.GenerateIndexes(this)); - result.AddRange(Field.GenerateIndexes(this)); + result.AddRange(BackReference.GenerateIndexes(this)); + result.AddRange(Field.GenerateIndexes(this)); - if (MultiColumnIndexes != null) - result.AddRange(MultiColumnIndexes); + if (MultiColumnIndexes != null) + result.AddRange(MultiColumnIndexes); - return result; - } + return result; + } - public Field GetField(MemberInfo member) - { - Field? result = TryGetField(member); + public Field GetField(MemberInfo member) + { + Field? result = TryGetField(member); - if (result == null) - throw new InvalidOperationException("'{0}' not found".FormatWith(member.Name)); + if (result == null) + throw new InvalidOperationException("'{0}' not found".FormatWith(member.Name)); - return result; - } + return result; + } - public Field? TryGetField(MemberInfo mi) - { - if (mi.Name == "Parent") - return this.BackReference; + public Field? TryGetField(MemberInfo mi) + { + if (mi.Name == "Parent") + return this.BackReference; - if (mi.Name == "Element") - return this.Field; + if (mi.Name == "Element") + return this.Field; - return null; - } + return null; + } - public IEnumerable FindFields(Func predicate) - { - if (predicate(this.BackReference)) - yield return this.BackReference; + public IEnumerable FindFields(Func predicate) + { + if (predicate(this.BackReference)) + yield return this.BackReference; - if (this.Order != null && predicate(this.Order)) - yield return this.Order; + if (this.Order != null && predicate(this.Order)) + yield return this.Order; - if (predicate(this.Field)) - yield return this.Field; - else if (this.Field is IFieldFinder ff) - { - foreach (var f in ff.FindFields(predicate)) + if (predicate(this.Field)) + yield return this.Field; + else if (this.Field is IFieldFinder ff) { - yield return f; + foreach (var f in ff.FindFields(predicate)) + { + yield return f; + } } } - } - public void ToDatabase(DatabaseName databaseName) - { - this.Name = this.Name.OnDatabase(databaseName); - } + public void ToDatabase(DatabaseName databaseName) + { + this.Name = this.Name.OnDatabase(databaseName); + } - public void ToSchema(SchemaName schemaName) - { - this.Name = this.Name.OnSchema(schemaName); - } + public void ToSchema(SchemaName schemaName) + { + this.Name = this.Name.OnSchema(schemaName); + } - IColumn ITable.PrimaryKey - { - get { return PrimaryKey; } - } + IColumn ITable.PrimaryKey + { + get { return PrimaryKey; } + } - public bool IdentityBehaviour => true; //For now + public bool IdentityBehaviour => true; //For now - internal object?[] BulkInsertDataRow(Entity entity, object value, int order) - { - return this.cache.Value.BulkInsertDataRow(entity, value, order); - } + internal object?[] BulkInsertDataRow(Entity entity, object value, int order) + { + return this.cache.Value.BulkInsertDataRow(entity, value, order); + } - public IEnumerable> GetTables() - { - return this.Field.GetTables(); - } + public IEnumerable> GetTables() + { + return this.Field.GetTables(); + } - public FieldEmbedded.EmbeddedHasValueColumn? GetHasValueColumn(IColumn column) - { - if (this.Field is FieldEmbedded f) - return f.GetHasValueColumn(column); + public FieldEmbedded.EmbeddedHasValueColumn? GetHasValueColumn(IColumn column) + { + if (this.Field is FieldEmbedded f) + return f.GetHasValueColumn(column); - return null; + return null; + } } -} -public struct AbstractDbType : IEquatable -{ - SqlDbType? sqlServer; - public SqlDbType SqlServer => sqlServer ?? throw new InvalidOperationException("No SqlDbType type defined"); + public struct AbstractDbType : IEquatable + { + SqlDbType? sqlServer; + public SqlDbType SqlServer => sqlServer ?? throw new InvalidOperationException("No SqlDbType type defined"); - NpgsqlDbType? postgreSql; - public NpgsqlDbType PostgreSql => postgreSql ?? throw new InvalidOperationException("No PostgresSql type defined"); + NpgsqlDbType? postgreSql; + public NpgsqlDbType PostgreSql => postgreSql ?? throw new InvalidOperationException("No PostgresSql type defined"); - public bool IsPostgres => postgreSql.HasValue; + public bool IsPostgres => postgreSql.HasValue; - public AbstractDbType(SqlDbType sqlDbType) - { - this.sqlServer = sqlDbType; - this.postgreSql = null; - } + public AbstractDbType(SqlDbType sqlDbType) + { + this.sqlServer = sqlDbType; + this.postgreSql = null; + } - public AbstractDbType(NpgsqlDbType npgsqlDbType) - { - this.sqlServer = null; - this.postgreSql = npgsqlDbType; - } + public AbstractDbType(NpgsqlDbType npgsqlDbType) + { + this.sqlServer = null; + this.postgreSql = npgsqlDbType; + } - public AbstractDbType(SqlDbType sqlDbType, NpgsqlDbType npgsqlDbType) - { - this.sqlServer = sqlDbType; - this.postgreSql = npgsqlDbType; - } + public AbstractDbType(SqlDbType sqlDbType, NpgsqlDbType npgsqlDbType) + { + this.sqlServer = sqlDbType; + this.postgreSql = npgsqlDbType; + } - public override bool Equals(object? obj) => obj is AbstractDbType adt && Equals(adt); - public bool Equals(AbstractDbType adt) => - Schema.Current.Settings.IsPostgres ? - this.postgreSql == adt.postgreSql : - this.sqlServer == adt.sqlServer; - public override int GetHashCode() => this.postgreSql.GetHashCode() ^ this.sqlServer.GetHashCode(); + public override bool Equals(object? obj) => obj is AbstractDbType adt && Equals(adt); + public bool Equals(AbstractDbType adt) => + Schema.Current.Settings.IsPostgres ? + this.postgreSql == adt.postgreSql : + this.sqlServer == adt.sqlServer; + public override int GetHashCode() => this.postgreSql.GetHashCode() ^ this.sqlServer.GetHashCode(); - public bool IsDate() - { - if (sqlServer is SqlDbType s) - switch (s) - { - case SqlDbType.Date: - case SqlDbType.DateTime: - case SqlDbType.DateTime2: - case SqlDbType.SmallDateTime: - return true; - default: - return false; - } + public bool IsDate() + { + if (sqlServer is SqlDbType s) + switch (s) + { + case SqlDbType.Date: + case SqlDbType.DateTime: + case SqlDbType.DateTime2: + case SqlDbType.SmallDateTime: + return true; + default: + return false; + } - if (postgreSql is NpgsqlDbType p) - switch (p) - { - case NpgsqlDbType.Date: - case NpgsqlDbType.Timestamp: - case NpgsqlDbType.TimestampTz: - return true; - default: - return false; - } + if (postgreSql is NpgsqlDbType p) + switch (p) + { + case NpgsqlDbType.Date: + case NpgsqlDbType.Timestamp: + case NpgsqlDbType.TimestampTz: + return true; + default: + return false; + } - throw new NotImplementedException(); - } + throw new NotImplementedException(); + } - public bool IsTime() - { - if (sqlServer is SqlDbType s) - switch (s) - { - case SqlDbType.Time: - return true; - default: - return false; - } + public bool IsTime() + { + if (sqlServer is SqlDbType s) + switch (s) + { + case SqlDbType.Time: + return true; + default: + return false; + } - if (postgreSql is NpgsqlDbType p) - switch (p) - { - case NpgsqlDbType.Time: - case NpgsqlDbType.TimeTz: - return true; - default: - return false; - } + if (postgreSql is NpgsqlDbType p) + switch (p) + { + case NpgsqlDbType.Time: + case NpgsqlDbType.TimeTz: + return true; + default: + return false; + } - throw new NotImplementedException(); - } + throw new NotImplementedException(); + } - public bool IsNumber() - { - if(sqlServer is SqlDbType s) - switch (s) - { - case SqlDbType.BigInt: - case SqlDbType.Float: - case SqlDbType.Decimal: - case SqlDbType.Int: - case SqlDbType.Bit: - case SqlDbType.Money: - case SqlDbType.Real: - case SqlDbType.TinyInt: - case SqlDbType.SmallInt: - case SqlDbType.SmallMoney: - return true; - default: - return false; - } + public bool IsNumber() + { + if(sqlServer is SqlDbType s) + switch (s) + { + case SqlDbType.BigInt: + case SqlDbType.Float: + case SqlDbType.Decimal: + case SqlDbType.Int: + case SqlDbType.Bit: + case SqlDbType.Money: + case SqlDbType.Real: + case SqlDbType.TinyInt: + case SqlDbType.SmallInt: + case SqlDbType.SmallMoney: + return true; + default: + return false; + } - if (postgreSql is NpgsqlDbType p) - switch (p) - { - case NpgsqlDbType.Smallint: - case NpgsqlDbType.Integer: - case NpgsqlDbType.Bigint: - case NpgsqlDbType.Numeric: - case NpgsqlDbType.Money: - case NpgsqlDbType.Real: - case NpgsqlDbType.Double: - return true; - default: - return false; - } + if (postgreSql is NpgsqlDbType p) + switch (p) + { + case NpgsqlDbType.Smallint: + case NpgsqlDbType.Integer: + case NpgsqlDbType.Bigint: + case NpgsqlDbType.Numeric: + case NpgsqlDbType.Money: + case NpgsqlDbType.Real: + case NpgsqlDbType.Double: + return true; + default: + return false; + } - throw new NotImplementedException(); - } + throw new NotImplementedException(); + } - public bool IsString() - { - if (sqlServer is SqlDbType s) - switch (s) - { - case SqlDbType.NText: - case SqlDbType.NVarChar: - case SqlDbType.Text: - case SqlDbType.VarChar: - case SqlDbType.Char: - case SqlDbType.NChar: - return true; - default: - return false; - } + public bool IsString() + { + if (sqlServer is SqlDbType s) + switch (s) + { + case SqlDbType.NText: + case SqlDbType.NVarChar: + case SqlDbType.Text: + case SqlDbType.VarChar: + case SqlDbType.Char: + case SqlDbType.NChar: + return true; + default: + return false; + } - if (postgreSql is NpgsqlDbType p) - switch (p) - { - case NpgsqlDbType.Char: - case NpgsqlDbType.Varchar: - case NpgsqlDbType.Text: - return true; - default: - return false; - } + if (postgreSql is NpgsqlDbType p) + switch (p) + { + case NpgsqlDbType.Char: + case NpgsqlDbType.Varchar: + case NpgsqlDbType.Text: + return true; + default: + return false; + } - throw new NotImplementedException(); - } + throw new NotImplementedException(); + } - public override string ToString() => ToString(Schema.Current.Settings.IsPostgres); - public string ToString(bool isPostgres) - { - if (!isPostgres) - return sqlServer.ToString()!.ToUpperInvariant(); + public override string ToString() => ToString(Schema.Current.Settings.IsPostgres); + public string ToString(bool isPostgres) + { + if (!isPostgres) + return sqlServer.ToString()!.ToUpperInvariant(); - var pg = postgreSql!.Value; - if ((pg & NpgsqlDbType.Array) != 0) - return (pg & ~NpgsqlDbType.Range).ToString() + "[]"; + var pg = postgreSql!.Value; + if ((pg & NpgsqlDbType.Array) != 0) + return (pg & ~NpgsqlDbType.Range).ToString() + "[]"; - if ((pg & NpgsqlDbType.Range) != 0) - switch (pg & ~NpgsqlDbType.Range) - { - case NpgsqlDbType.Integer: return "int4range"; - case NpgsqlDbType.Bigint : return "int8range"; - case NpgsqlDbType.Numeric: return "numrange"; - case NpgsqlDbType.TimestampTz: return "tstzrange"; - case NpgsqlDbType.Date: return "daterange"; - throw new InvalidOperationException(""); - } + if ((pg & NpgsqlDbType.Range) != 0) + switch (pg & ~NpgsqlDbType.Range) + { + case NpgsqlDbType.Integer: return "int4range"; + case NpgsqlDbType.Bigint : return "int8range"; + case NpgsqlDbType.Numeric: return "numrange"; + case NpgsqlDbType.TimestampTz: return "tstzrange"; + case NpgsqlDbType.Date: return "daterange"; + throw new InvalidOperationException(""); + } - if (pg == NpgsqlDbType.Double) - return "double precision"; + if (pg == NpgsqlDbType.Double) + return "double precision"; - return pg.ToString()!; - } + return pg.ToString()!; + } - public bool IsBoolean() - { - if (sqlServer is SqlDbType s) - switch (s) - { - case SqlDbType.Bit: - return true; - default: - return false; - } + public bool IsBoolean() + { + if (sqlServer is SqlDbType s) + switch (s) + { + case SqlDbType.Bit: + return true; + default: + return false; + } - if (postgreSql is NpgsqlDbType p) - switch (p) - { - case NpgsqlDbType.Boolean: - return true; - default: - return false; - } + if (postgreSql is NpgsqlDbType p) + switch (p) + { + case NpgsqlDbType.Boolean: + return true; + default: + return false; + } - throw new NotImplementedException(); - } + throw new NotImplementedException(); + } - public bool IsGuid() - { - if (sqlServer is SqlDbType s) - switch (s) - { - case SqlDbType.UniqueIdentifier: - return true; - default: - return false; - } + public bool IsGuid() + { + if (sqlServer is SqlDbType s) + switch (s) + { + case SqlDbType.UniqueIdentifier: + return true; + default: + return false; + } - if (postgreSql is NpgsqlDbType p) - switch (p) - { - case NpgsqlDbType.Uuid: - return true; - default: - return false; - } + if (postgreSql is NpgsqlDbType p) + switch (p) + { + case NpgsqlDbType.Uuid: + return true; + default: + return false; + } - throw new NotImplementedException(); - } + throw new NotImplementedException(); + } - internal bool IsDecimal() - { - if (sqlServer is SqlDbType s) - switch (s) - { - case SqlDbType.Decimal: - return true; - default: - return false; - } + internal bool IsDecimal() + { + if (sqlServer is SqlDbType s) + switch (s) + { + case SqlDbType.Decimal: + return true; + default: + return false; + } - if (postgreSql is NpgsqlDbType p) - switch (p) - { - case NpgsqlDbType.Numeric: - return true; - default: - return false; - } + if (postgreSql is NpgsqlDbType p) + switch (p) + { + case NpgsqlDbType.Numeric: + return true; + default: + return false; + } - throw new NotImplementedException(); + throw new NotImplementedException(); + } } -} diff --git a/Signum.Engine/Schema/Schema.Save.cs b/Signum.Engine/Schema/Schema.Save.cs index 286016d107..333171958c 100644 --- a/Signum.Engine/Schema/Schema.Save.cs +++ b/Signum.Engine/Schema/Schema.Save.cs @@ -7,1562 +7,1564 @@ namespace Signum.Engine.Maps; -public struct Forbidden -{ - public Forbidden(HashSet? set) + public struct Forbidden { - this.set = set; - } - - public Forbidden(DirectedGraph? graph, Entity entity) - { - this.set = graph?.TryRelatedTo(entity); - } + public Forbidden(HashSet? set) + { + this.set = set; + } - readonly HashSet? set; + public Forbidden(DirectedGraph? graph, Entity entity) + { + this.set = graph?.TryRelatedTo(entity); + } - public bool IsEmpty - { - get { return set == null || set.Count == 0; } - } + readonly HashSet? set; - public bool Contains(Entity entity) - { - return set != null && set.Contains(entity); - } -} - -public struct EntityForbidden -{ - public readonly Entity Entity; - public readonly Forbidden Forbidden; + public bool IsEmpty + { + get { return set == null || set.Count == 0; } + } - public EntityForbidden(Entity entity, Forbidden forbidden) - { - this.Entity = (Entity)entity; - this.Forbidden = forbidden; + public bool Contains(Entity entity) + { + return set != null && set.Contains(entity); + } } - public EntityForbidden(Entity entity, DirectedGraph? graph) + public struct EntityForbidden { - this.Entity = (Entity)entity; - this.Forbidden = new Forbidden(graph, entity); - } -} + public readonly Entity Entity; + public readonly Forbidden Forbidden; -public partial class Table -{ - internal static string Var(bool isPostgres, string varName) - { - if (isPostgres) - return varName; - else - return "@" + varName; - } + public EntityForbidden(Entity entity, Forbidden forbidden) + { + this.Entity = (Entity)entity; + this.Forbidden = forbidden; + } - internal static string DeclareTempTable(string variableName, FieldPrimaryKey id, bool isPostgres) - { - if (isPostgres) - return $"CREATE TEMP TABLE {variableName} ({id.Name.SqlEscape(isPostgres)} {id.DbType.ToString(isPostgres)});"; - else - return $"DECLARE {variableName} TABLE({id.Name.SqlEscape(isPostgres)} {id.DbType.ToString(isPostgres)});"; + public EntityForbidden(Entity entity, DirectedGraph? graph) + { + this.Entity = (Entity)entity; + this.Forbidden = new Forbidden(graph, entity); + } } - ResetLazy inserterIdentity; - ResetLazy inserterDisableIdentity; - - internal void InsertMany(List list, DirectedGraph? backEdges) + public partial class Table { - using (HeavyProfiler.LogNoStackTrace("InsertMany", () => this.Type.TypeName())) + internal static string Var(bool isPostgres, string varName) { - if (IdentityBehaviour && !Administrator.IsIdentityBehaviourDisabled(this)) - { - InsertCacheIdentity ic = inserterIdentity.Value; - list.SplitStatements(this.Columns.Count, ls => ic.GetInserter(ls.Count)(ls, backEdges)); - } + if (isPostgres) + return varName; else - { - InsertCacheDisableIdentity ic = inserterDisableIdentity.Value; - list.SplitStatements(this.Columns.Count, ls => ic.GetInserter(ls.Count)(ls, backEdges)); - } + return "@" + varName; } - } - - internal object?[] BulkInsertDataRow(object/*Entity or IView*/ entity) - { - List parameters = GetInsertParameters(entity); - return parameters.Select(a => a.Value).ToArray(); - } - - internal List GetInsertParameters(object entity) - { - List parameters = new List(); - if (IdentityBehaviour && !Administrator.IsIdentityBehaviourDisabled(this)) - inserterIdentity.Value.InsertParameters((Entity)entity, new Forbidden(), "", parameters); - else - inserterDisableIdentity.Value.InsertParameters(entity, new Forbidden(), "", parameters); - return parameters; - } - - class InsertCacheDisableIdentity - { - internal Table table; + internal static string DeclareTempTable(string variableName, FieldPrimaryKey id, bool isPostgres) + { + if (isPostgres) + return $"CREATE TEMP TABLE {variableName} ({id.Name.SqlEscape(isPostgres)} {id.DbType.ToString(isPostgres)});"; + else + return $"DECLARE {variableName} TABLE({id.Name.SqlEscape(isPostgres)} {id.DbType.ToString(isPostgres)});"; + } - public Func SqlInsertPattern; - public Action> InsertParameters; + ResetLazy inserterIdentity; + ResetLazy inserterDisableIdentity; - ConcurrentDictionary, DirectedGraph?>> insertDisableIdentityCache = - new ConcurrentDictionary, DirectedGraph?>>(); + internal void InsertMany(List list, DirectedGraph? backEdges) + { + using (HeavyProfiler.LogNoStackTrace("InsertMany", () => this.Type.TypeName())) + { + if (IdentityBehaviour && !Administrator.IsIdentityBehaviourDisabled(this)) + { + InsertCacheIdentity ic = inserterIdentity.Value; + list.SplitStatements(this.Columns.Count, ls => ic.GetInserter(ls.Count)(ls, backEdges)); + } + else + { + InsertCacheDisableIdentity ic = inserterDisableIdentity.Value; + list.SplitStatements(this.Columns.Count, ls => ic.GetInserter(ls.Count)(ls, backEdges)); + } + } + } - public InsertCacheDisableIdentity(Table table, Func sqlInsertPattern, Action> insertParameters) + internal object?[] BulkInsertDataRow(object/*Entity or IView*/ entity) { - this.table = table; - SqlInsertPattern = sqlInsertPattern; - InsertParameters = insertParameters; + List parameters = GetInsertParameters(entity); + + return parameters.Select(a => a.Value).ToArray(); } - internal Action, DirectedGraph?> GetInserter(int numElements) + internal List GetInsertParameters(object entity) { - return insertDisableIdentityCache.GetOrAdd(numElements, (int num) => num == 1 ? GetInsertDisableIdentity() : GetInsertMultiDisableIdentity(num)); + List parameters = new List(); + if (IdentityBehaviour && !Administrator.IsIdentityBehaviourDisabled(this)) + inserterIdentity.Value.InsertParameters((Entity)entity, new Forbidden(), "", parameters); + else + inserterDisableIdentity.Value.InsertParameters(entity, new Forbidden(), "", parameters); + return parameters; } - Action, DirectedGraph?> GetInsertDisableIdentity() + class InsertCacheDisableIdentity { - string sqlSingle = SqlInsertPattern(new[] { "" }); + internal Table table; + + public Func SqlInsertPattern; + public Action> InsertParameters; + + ConcurrentDictionary, DirectedGraph?>> insertDisableIdentityCache = + new ConcurrentDictionary, DirectedGraph?>>(); - return (list, graph) => + public InsertCacheDisableIdentity(Table table, Func sqlInsertPattern, Action> insertParameters) { - Entity entity = list.Single(); + this.table = table; + SqlInsertPattern = sqlInsertPattern; + InsertParameters = insertParameters; + } - AssertHasId(entity); - - entity.Ticks = Clock.Now.Ticks; + internal Action, DirectedGraph?> GetInserter(int numElements) + { + return insertDisableIdentityCache.GetOrAdd(numElements, (int num) => num == 1 ? GetInsertDisableIdentity() : GetInsertMultiDisableIdentity(num)); + } - table.SetToStrField(entity); + Action, DirectedGraph?> GetInsertDisableIdentity() + { + string sqlSingle = SqlInsertPattern(new[] { "" }); - var forbidden = new Forbidden(graph, entity); + return (list, graph) => + { + Entity entity = list.Single(); - var parameters = new List(); - InsertParameters(entity, forbidden, "", parameters); - new SqlPreCommandSimple(sqlSingle, parameters).ExecuteNonQuery(); + AssertHasId(entity); + + entity.Ticks = Clock.Now.Ticks; - entity.IsNew = false; - if (table.saveCollections.Value != null) - table.saveCollections.Value.InsertCollections(new List { new EntityForbidden(entity, forbidden) }); - }; - } + table.SetToStrField(entity); - Action, DirectedGraph?> GetInsertMultiDisableIdentity(int num) - { - string sqlMulti = SqlInsertPattern(Enumerable.Range(0, num).Select(i => i.ToString()).ToArray()); + var forbidden = new Forbidden(graph, entity); + + var parameters = new List(); + InsertParameters(entity, forbidden, "", parameters); + new SqlPreCommandSimple(sqlSingle, parameters).ExecuteNonQuery(); - return (entities, graph) => + entity.IsNew = false; + if (table.saveCollections.Value != null) + table.saveCollections.Value.InsertCollections(new List { new EntityForbidden(entity, forbidden) }); + }; + } + + Action, DirectedGraph?> GetInsertMultiDisableIdentity(int num) { - for (int i = 0; i < num; i++) + string sqlMulti = SqlInsertPattern(Enumerable.Range(0, num).Select(i => i.ToString()).ToArray()); + + return (entities, graph) => { - var entity = entities[i]; - AssertHasId(entity); + for (int i = 0; i < num; i++) + { + var entity = entities[i]; + AssertHasId(entity); entity.Ticks = Clock.Now.Ticks; - table.SetToStrField(entity); - } + table.SetToStrField(entity); + } - List result = new List(); - for (int i = 0; i < entities.Count; i++) - InsertParameters(entities[i], new Forbidden(graph, entities[i]), i.ToString(), result); + List result = new List(); + for (int i = 0; i < entities.Count; i++) + InsertParameters(entities[i], new Forbidden(graph, entities[i]), i.ToString(), result); - new SqlPreCommandSimple(sqlMulti, result).ExecuteNonQuery(); - for (int i = 0; i < num; i++) - { - Entity ident = entities[i]; + new SqlPreCommandSimple(sqlMulti, result).ExecuteNonQuery(); + for (int i = 0; i < num; i++) + { + Entity ident = entities[i]; - ident.IsNew = false; - } + ident.IsNew = false; + } - if (table.saveCollections.Value != null) - table.saveCollections.Value.InsertCollections(entities.Select(e => new EntityForbidden(e, graph)).ToList()); - }; - } + if (table.saveCollections.Value != null) + table.saveCollections.Value.InsertCollections(entities.Select(e => new EntityForbidden(e, graph)).ToList()); + }; + } - internal static InsertCacheDisableIdentity InitializeInsertDisableIdentity(Table table) - { - using (HeavyProfiler.LogNoStackTrace("InitializeInsertDisableIdentity", () => table.Type.TypeName())) + internal static InsertCacheDisableIdentity InitializeInsertDisableIdentity(Table table) { - var trios = new List(); - var assigments = new List(); - var paramIdent = Expression.Parameter(typeof(object) /*Entity*/, "ident"); - var paramForbidden = Expression.Parameter(typeof(Forbidden), "forbidden"); - var paramSuffix = Expression.Parameter(typeof(string), "suffix"); - var paramList = Expression.Parameter(typeof(List), "dbParams"); + using (HeavyProfiler.LogNoStackTrace("InitializeInsertDisableIdentity", () => table.Type.TypeName())) + { + var trios = new List(); + var assigments = new List(); + var paramIdent = Expression.Parameter(typeof(object) /*Entity*/, "ident"); + var paramForbidden = Expression.Parameter(typeof(Forbidden), "forbidden"); + var paramSuffix = Expression.Parameter(typeof(string), "suffix"); + var paramList = Expression.Parameter(typeof(List), "dbParams"); - var cast = Expression.Parameter(table.Type, "casted"); - assigments.Add(Expression.Assign(cast, Expression.Convert(paramIdent, table.Type))); + var cast = Expression.Parameter(table.Type, "casted"); + assigments.Add(Expression.Assign(cast, Expression.Convert(paramIdent, table.Type))); - foreach (var item in table.Fields.Values) - item.Field.CreateParameter(trios, assigments, Expression.Field(cast, item.FieldInfo), paramForbidden, paramSuffix); + foreach (var item in table.Fields.Values) + item.Field.CreateParameter(trios, assigments, Expression.Field(cast, item.FieldInfo), paramForbidden, paramSuffix); - if (table.Mixins != null) - foreach (var item in table.Mixins.Values) - item.CreateParameter(trios, assigments, cast, paramForbidden, paramSuffix); + if (table.Mixins != null) + foreach (var item in table.Mixins.Values) + item.CreateParameter(trios, assigments, cast, paramForbidden, paramSuffix); - var isPostgres = Schema.Current.Settings.IsPostgres; + var isPostgres = Schema.Current.Settings.IsPostgres; string insertPattern(string[] suffixes) => - "INSERT INTO {0}\r\n ({1})\r\n{2}VALUES\r\n{3};".FormatWith(table.Name, - trios.ToString(p => p.SourceColumn.SqlEscape(isPostgres), ", "), - isPostgres ? "OVERRIDING SYSTEM VALUE\r\n" : null, - suffixes.ToString(s => " (" + trios.ToString(p => p.ParameterName + s, ", ") + ")", ",\r\n")); + "INSERT INTO {0}\r\n ({1})\r\n{2}VALUES\r\n{3};".FormatWith(table.Name, + trios.ToString(p => p.SourceColumn.SqlEscape(isPostgres), ", "), + isPostgres? "OVERRIDING SYSTEM VALUE\r\n" : null, + suffixes.ToString(s => " (" + trios.ToString(p => p.ParameterName + s, ", ") + ")", ",\r\n")); - var expr = Expression.Lambda>>( - CreateBlock(trios.Select(a => a.ParameterBuilder), assigments, paramList), paramIdent, paramForbidden, paramSuffix, paramList); + var expr = Expression.Lambda>>( + CreateBlock(trios.Select(a => a.ParameterBuilder), assigments, paramList), paramIdent, paramForbidden, paramSuffix, paramList); - - return new InsertCacheDisableIdentity(table, insertPattern, expr.Compile()); + + return new InsertCacheDisableIdentity(table, insertPattern, expr.Compile()); + } } } - } - class InsertCacheIdentity - { - internal Table table; - - public Func SqlInsertPattern; - public Action> InsertParameters; + class InsertCacheIdentity + { + internal Table table; - ConcurrentDictionary, DirectedGraph?>> insertIdentityCache = - new ConcurrentDictionary, DirectedGraph?>>(); + public Func SqlInsertPattern; + public Action> InsertParameters; - public InsertCacheIdentity(Table table, Func sqlInsertPattern, Action> insertParameters) - { - this.table = table; - SqlInsertPattern = sqlInsertPattern; - InsertParameters = insertParameters; - } + ConcurrentDictionary, DirectedGraph?>> insertIdentityCache = + new ConcurrentDictionary, DirectedGraph?>>(); - internal Action, DirectedGraph?> GetInserter(int numElements) - { - return insertIdentityCache.GetOrAdd(numElements, (int num) => GetInsertMultiIdentity(num)); - } + public InsertCacheIdentity(Table table, Func sqlInsertPattern, Action> insertParameters) + { + this.table = table; + SqlInsertPattern = sqlInsertPattern; + InsertParameters = insertParameters; + } - Action, DirectedGraph?> GetInsertMultiIdentity(int num) - { - var isPostgres = Schema.Current.Settings.IsPostgres; - string sqlMulti = SqlInsertPattern(Enumerable.Range(0, num).Select(i => i.ToString()).ToArray(), "" /*output but no name*/); + internal Action, DirectedGraph?> GetInserter(int numElements) + { + return insertIdentityCache.GetOrAdd(numElements, (int num) => GetInsertMultiIdentity(num)); + } - return (entities, graph) => + Action, DirectedGraph?> GetInsertMultiIdentity(int num) { - for (int i = 0; i < num; i++) + var isPostgres = Schema.Current.Settings.IsPostgres; + string sqlMulti = SqlInsertPattern(Enumerable.Range(0, num).Select(i => i.ToString()).ToArray(), "" /*output but no name*/); + + return (entities, graph) => { - var entity = entities[i]; - AssertNoId(entity); + for (int i = 0; i < num; i++) + { + var entity = entities[i]; + AssertNoId(entity); entity.Ticks = Clock.Now.Ticks; - table.SetToStrField(entity); - } + table.SetToStrField(entity); + } - List result = new List(); - for (int i = 0; i < entities.Count; i++) - InsertParameters(entities[i], new Forbidden(graph, entities[i]), i.ToString(), result); + List result = new List(); + for (int i = 0; i < entities.Count; i++) + InsertParameters(entities[i], new Forbidden(graph, entities[i]), i.ToString(), result); - DataTable dt = new SqlPreCommandSimple(sqlMulti, result).ExecuteDataTable(); + DataTable dt = new SqlPreCommandSimple(sqlMulti, result).ExecuteDataTable(); - for (int i = 0; i < num; i++) - { - Entity ident = entities[i]; + for (int i = 0; i < num; i++) + { + Entity ident = entities[i]; - ident.id = new PrimaryKey((IComparable)dt.Rows[i][0]); - ident.IsNew = false; - } + ident.id = new PrimaryKey((IComparable)dt.Rows[i][0]); + ident.IsNew = false; + } - if (table.saveCollections.Value != null) - table.saveCollections.Value.InsertCollections(entities.Select(e => new EntityForbidden(e, graph)).ToList()); - }; + if (table.saveCollections.Value != null) + table.saveCollections.Value.InsertCollections(entities.Select(e => new EntityForbidden(e, graph)).ToList()); + }; - } + } - internal static InsertCacheIdentity InitializeInsertIdentity(Table table) - { - using (HeavyProfiler.LogNoStackTrace("InitializeInsertIdentity", () => table.Type.TypeName())) + internal static InsertCacheIdentity InitializeInsertIdentity(Table table) { - var trios = new List(); - var assigments = new List(); - var paramIdent = Expression.Parameter(typeof(Entity), "ident"); - var paramForbidden = Expression.Parameter(typeof(Forbidden), "forbidden"); - var paramSuffix = Expression.Parameter(typeof(string), "suffix"); - var paramList = Expression.Parameter(typeof(List), "dbParams"); + using (HeavyProfiler.LogNoStackTrace("InitializeInsertIdentity", () => table.Type.TypeName())) + { + var trios = new List(); + var assigments = new List(); + var paramIdent = Expression.Parameter(typeof(Entity), "ident"); + var paramForbidden = Expression.Parameter(typeof(Forbidden), "forbidden"); + var paramSuffix = Expression.Parameter(typeof(string), "suffix"); + var paramList = Expression.Parameter(typeof(List), "dbParams"); - var cast = Expression.Parameter(table.Type, "casted"); - assigments.Add(Expression.Assign(cast, Expression.Convert(paramIdent, table.Type))); + var cast = Expression.Parameter(table.Type, "casted"); + assigments.Add(Expression.Assign(cast, Expression.Convert(paramIdent, table.Type))); - foreach (var item in table.Fields.Values.Where(a => !(a.Field is FieldPrimaryKey))) - item.Field.CreateParameter(trios, assigments, Expression.Field(cast, item.FieldInfo), paramForbidden, paramSuffix); + foreach (var item in table.Fields.Values.Where(a => !(a.Field is FieldPrimaryKey))) + item.Field.CreateParameter(trios, assigments, Expression.Field(cast, item.FieldInfo), paramForbidden, paramSuffix); - if (table.Mixins != null) - foreach (var item in table.Mixins.Values) - item.CreateParameter(trios, assigments, cast, paramForbidden, paramSuffix); + if (table.Mixins != null) + foreach (var item in table.Mixins.Values) + item.CreateParameter(trios, assigments, cast, paramForbidden, paramSuffix); - var isPostgres = Schema.Current.Settings.IsPostgres; + var isPostgres = Schema.Current.Settings.IsPostgres; string sqlInsertPattern(string[] suffixes, string? output) => - "INSERT INTO {0}\r\n ({1})\r\n{2}VALUES\r\n{3}{4};".FormatWith( - table.Name, - trios.ToString(p => p.SourceColumn.SqlEscape(isPostgres), ", "), - output != null && !isPostgres ? $"OUTPUT INSERTED.{table.PrimaryKey.Name.SqlEscape(isPostgres)}{(output.Length > 0 ? " INTO " + output : "")}\r\n" : null, - suffixes.ToString(s => " (" + trios.ToString(p => p.ParameterName + s, ", ") + ")", ",\r\n"), - output != null && isPostgres ? $"\r\nRETURNING {table.PrimaryKey.Name.SqlEscape(isPostgres)}{(output.Length > 0 ? " INTO " + output : "")}" : null); - - - var expr = Expression.Lambda>>( - CreateBlock(trios.Select(a => a.ParameterBuilder), assigments, paramList), paramIdent, paramForbidden, paramSuffix, paramList); - - return new InsertCacheIdentity(table, sqlInsertPattern, expr.Compile()); + "INSERT INTO {0}\r\n ({1})\r\n{2}VALUES\r\n{3}{4};".FormatWith( + table.Name, + trios.ToString(p => p.SourceColumn.SqlEscape(isPostgres), ", "), + output != null && !isPostgres ? $"OUTPUT INSERTED.{table.PrimaryKey.Name.SqlEscape(isPostgres)}{(output.Length > 0 ? " INTO " + output : "")}\r\n" : null, + suffixes.ToString(s => " (" + trios.ToString(p => p.ParameterName + s, ", ") + ")", ",\r\n"), + output != null && isPostgres ? $"\r\nRETURNING {table.PrimaryKey.Name.SqlEscape(isPostgres)}{(output.Length > 0 ? " INTO " + output : "")}" : null); + + + var expr = Expression.Lambda>>( + CreateBlock(trios.Select(a => a.ParameterBuilder), assigments, paramList), paramIdent, paramForbidden, paramSuffix, paramList); + + return new InsertCacheIdentity(table, sqlInsertPattern, expr.Compile()); + } } } - } - static void AssertHasId(Entity ident) - { - if (ident.IdOrNull == null) - throw new InvalidOperationException("{0} should have an Id, since the table has no Identity".FormatWith(ident, ident.IdOrNull)); - } + static void AssertHasId(Entity ident) + { + if (ident.IdOrNull == null) + throw new InvalidOperationException("{0} should have an Id, since the table has no Identity".FormatWith(ident, ident.IdOrNull)); + } - static void AssertNoId(Entity ident) - { - if (ident.IdOrNull != null) - throw new InvalidOperationException("{0} is new, but has Id {1}".FormatWith(ident, ident.IdOrNull)); - } + static void AssertNoId(Entity ident) + { + if (ident.IdOrNull != null) + throw new InvalidOperationException("{0} is new, but has Id {1}".FormatWith(ident, ident.IdOrNull)); + } - public IColumn? ToStrColumn - { - get + public IColumn? ToStrColumn { + get + { - if (Fields.TryGetValue("toStr", out var entity)) - return (IColumn)entity.Field; + if (Fields.TryGetValue("toStr", out var entity)) + return (IColumn)entity.Field; - return null; + return null; + } } - } - internal bool SetToStrField(Entity entity) - { - var toStrColumn = ToStrColumn; - if (toStrColumn != null) + internal bool SetToStrField(Entity entity) { - string newStr; - using (CultureInfoUtils.ChangeCultureUI(Schema.Current.ForceCultureInfo)) - newStr = entity.ToString(); + var toStrColumn = ToStrColumn; + if (toStrColumn != null) + { + string newStr; + using (CultureInfoUtils.ChangeCultureUI(Schema.Current.ForceCultureInfo)) + newStr = entity.ToString(); - if (newStr.HasText() && toStrColumn.Size.HasValue && newStr.Length > toStrColumn.Size) - newStr = newStr.Substring(0, toStrColumn.Size.Value); + if (newStr.HasText() && toStrColumn.Size.HasValue && newStr.Length > toStrColumn.Size) + newStr = newStr.Substring(0, toStrColumn.Size.Value); - if (entity.toStr != newStr) - { - entity.toStr = newStr; - return true; + if (entity.toStr != newStr) + { + entity.toStr = newStr; + return true; + } } - } - return false; - } + return false; + } - internal static FieldInfo fiId = ReflectionTools.GetFieldInfo((Entity i) => i.id); + internal static FieldInfo fiId = ReflectionTools.GetFieldInfo((Entity i) => i.id); - internal void UpdateMany(List list, DirectedGraph? backEdges) - { - using (HeavyProfiler.LogNoStackTrace("UpdateMany", () => this.Type.TypeName())) + internal void UpdateMany(List list, DirectedGraph? backEdges) { - var uc = updater.Value; - list.SplitStatements(this.Columns.Count + 2, ls => uc.GetUpdater(ls.Count)(ls, backEdges)); + using (HeavyProfiler.LogNoStackTrace("UpdateMany", () => this.Type.TypeName())) + { + var uc = updater.Value; + list.SplitStatements(this.Columns.Count + 2, ls => uc.GetUpdater(ls.Count)(ls, backEdges)); + } } - } - - class UpdateCache - { - internal Table table; - public Func SqlUpdatePattern; - public Action> UpdateParameters; + class UpdateCache + { + internal Table table; - ConcurrentDictionary, DirectedGraph?>> updateCache = - new ConcurrentDictionary, DirectedGraph?>>(); + public Func SqlUpdatePattern; + public Action> UpdateParameters; - public UpdateCache(Table table, Func sqlUpdatePattern, Action> updateParameters) - { - this.table = table; - SqlUpdatePattern = sqlUpdatePattern; - UpdateParameters = updateParameters; - } + ConcurrentDictionary, DirectedGraph?>> updateCache = + new ConcurrentDictionary, DirectedGraph?>>(); - public Action, DirectedGraph?> GetUpdater(int numElements) - { - return updateCache.GetOrAdd(numElements, num => num == 1 ? GenerateUpdate() : GetUpdateMultiple(num)); - } + public UpdateCache(Table table, Func sqlUpdatePattern, Action> updateParameters) + { + this.table = table; + SqlUpdatePattern = sqlUpdatePattern; + UpdateParameters = updateParameters; + } - Action, DirectedGraph?> GenerateUpdate() - { - string sqlUpdate = SqlUpdatePattern("", false); + public Action, DirectedGraph?> GetUpdater(int numElements) + { + return updateCache.GetOrAdd(numElements, num => num == 1 ? GenerateUpdate() : GetUpdateMultiple(num)); + } - if (table.Ticks != null) + Action, DirectedGraph?> GenerateUpdate() { - return (uniList, graph) => + string sqlUpdate = SqlUpdatePattern("", false); + + if (table.Ticks != null) { - Entity ident = uniList.Single(); - Entity entity = (Entity)ident; + return (uniList, graph) => + { + Entity ident = uniList.Single(); + Entity entity = (Entity)ident; - long oldTicks = entity.Ticks; + long oldTicks = entity.Ticks; entity.Ticks = Clock.Now.Ticks; - table.SetToStrField(ident); + table.SetToStrField(ident); - var forbidden = new Forbidden(graph, ident); + var forbidden = new Forbidden(graph, ident); - int num = (int)new SqlPreCommandSimple(sqlUpdate, new List().Do(ps => UpdateParameters(ident, oldTicks, forbidden, "", ps))).ExecuteNonQuery(); - if (num != 1) - throw new ConcurrencyException(ident.GetType(), ident.Id); + int num = (int)new SqlPreCommandSimple(sqlUpdate, new List().Do(ps => UpdateParameters(ident, oldTicks, forbidden, "", ps))).ExecuteNonQuery(); + if (num != 1) + throw new ConcurrencyException(ident.GetType(), ident.Id); - if (table.saveCollections.Value != null) - table.saveCollections.Value.UpdateCollections(new List { new EntityForbidden(ident, forbidden) }); - }; - } - else - { - return (uniList, graph) => + if (table.saveCollections.Value != null) + table.saveCollections.Value.UpdateCollections(new List { new EntityForbidden(ident, forbidden) }); + }; + } + else { - Entity ident = uniList.Single(); + return (uniList, graph) => + { + Entity ident = uniList.Single(); - table.SetToStrField(ident); + table.SetToStrField(ident); - var forbidden = new Forbidden(graph, ident); + var forbidden = new Forbidden(graph, ident); - int num = (int)new SqlPreCommandSimple(sqlUpdate, new List().Do(ps => UpdateParameters(ident, -1, forbidden, "", ps))).ExecuteNonQuery(); - if (num != 1) - throw new EntityNotFoundException(ident.GetType(), ident.Id); - }; + int num = (int)new SqlPreCommandSimple(sqlUpdate, new List().Do(ps => UpdateParameters(ident, -1, forbidden, "", ps))).ExecuteNonQuery(); + if (num != 1) + throw new EntityNotFoundException(ident.GetType(), ident.Id); + }; + } } - } - Action, DirectedGraph?> GetUpdateMultiple(int num) - { - var isPostgres = Schema.Current.Settings.IsPostgres; - var updated = Table.Var(isPostgres, "updated"); - var id = this.table.PrimaryKey.Name.SqlEscape(isPostgres); - string sqlMulti = num == 1 ? SqlUpdatePattern("", true) : - new StringBuilder() - .AppendLine(Table.DeclareTempTable(updated, this.table.PrimaryKey, isPostgres)) - .AppendLine() - .AppendLines(Enumerable.Range(0, num).Select(i => SqlUpdatePattern(i.ToString(), true) + "\r\n")) - .AppendLine() - .AppendLine($"SELECT {id} from {updated}") - .ToString(); - - if (table.Ticks != null) + Action, DirectedGraph?> GetUpdateMultiple(int num) { - return (idents, graph) => + var isPostgres = Schema.Current.Settings.IsPostgres; + var updated = Table.Var(isPostgres, "updated"); + var id = this.table.PrimaryKey.Name.SqlEscape(isPostgres); + string sqlMulti = num == 1 ? SqlUpdatePattern("", true) : + new StringBuilder() + .AppendLine(Table.DeclareTempTable(updated, this.table.PrimaryKey, isPostgres)) + .AppendLine() + .AppendLines(Enumerable.Range(0, num).Select(i => SqlUpdatePattern(i.ToString(), true) + "\r\n")) + .AppendLine() + .AppendLine($"SELECT {id} from {updated}") + .ToString(); + + if (table.Ticks != null) { - List parameters = new List(); - for (int i = 0; i < num; i++) + return (idents, graph) => { - Entity entity = (Entity)idents[i]; + List parameters = new List(); + for (int i = 0; i < num; i++) + { + Entity entity = (Entity)idents[i]; - long oldTicks = entity.Ticks; + long oldTicks = entity.Ticks; entity.Ticks = Clock.Now.Ticks; - UpdateParameters(entity, oldTicks, new Forbidden(graph, entity), i.ToString(), parameters); - } + UpdateParameters(entity, oldTicks, new Forbidden(graph, entity), i.ToString(), parameters); + } - DataTable dt = new SqlPreCommandSimple(sqlMulti, parameters).ExecuteDataTable(); + DataTable dt = new SqlPreCommandSimple(sqlMulti, parameters).ExecuteDataTable(); - if (dt.Rows.Count != idents.Count) - { - var updated = dt.Rows.Cast().Select(r => new PrimaryKey((IComparable)r[0])).ToList(); + if (dt.Rows.Count != idents.Count) + { + var updated = dt.Rows.Cast().Select(r => new PrimaryKey((IComparable)r[0])).ToList(); - var missing = idents.Select(a => a.Id).Except(updated).ToArray(); + var missing = idents.Select(a => a.Id).Except(updated).ToArray(); - throw new ConcurrencyException(table.Type, missing); - } + throw new ConcurrencyException(table.Type, missing); + } - if (isPostgres && num > 1) - { - new SqlPreCommandSimple($"DROP TABLE {updated}").ExecuteNonQuery(); - } + if (isPostgres && num > 1) + { + new SqlPreCommandSimple($"DROP TABLE {updated}").ExecuteNonQuery(); + } - if (table.saveCollections.Value != null) - table.saveCollections.Value.UpdateCollections(idents.Select(e => new EntityForbidden(e, new Forbidden(graph, e))).ToList()); - }; - } - else - { - return (idents, graph) => + if (table.saveCollections.Value != null) + table.saveCollections.Value.UpdateCollections(idents.Select(e => new EntityForbidden(e, new Forbidden(graph, e))).ToList()); + }; + } + else { - List parameters = new List(); - for (int i = 0; i < num; i++) + return (idents, graph) => { - var ident = idents[i]; - UpdateParameters(ident, -1, new Forbidden(graph, ident), i.ToString(), parameters); - } + List parameters = new List(); + for (int i = 0; i < num; i++) + { + var ident = idents[i]; + UpdateParameters(ident, -1, new Forbidden(graph, ident), i.ToString(), parameters); + } - DataTable dt = new SqlPreCommandSimple(sqlMulti, parameters).ExecuteDataTable(); + DataTable dt = new SqlPreCommandSimple(sqlMulti, parameters).ExecuteDataTable(); - if (dt.Rows.Count != idents.Count) - { - var updated = dt.Rows.Cast().Select(r => new PrimaryKey((IComparable)r[0])).ToList(); + if (dt.Rows.Count != idents.Count) + { + var updated = dt.Rows.Cast().Select(r => new PrimaryKey((IComparable)r[0])).ToList(); - var missing = idents.Select(a => a.Id).Except(updated).ToArray(); + var missing = idents.Select(a => a.Id).Except(updated).ToArray(); - throw new EntityNotFoundException(table.Type, missing); - } + throw new EntityNotFoundException(table.Type, missing); + } - for (int i = 0; i < num; i++) - { - Entity ident = idents[i]; - } + for (int i = 0; i < num; i++) + { + Entity ident = idents[i]; + } - if (table.saveCollections.Value != null) - table.saveCollections.Value.UpdateCollections(idents.Select(e => new EntityForbidden(e, new Forbidden(graph, e))).ToList()); - }; + if (table.saveCollections.Value != null) + table.saveCollections.Value.UpdateCollections(idents.Select(e => new EntityForbidden(e, new Forbidden(graph, e))).ToList()); + }; + } } - } - internal static UpdateCache InitializeUpdate(Table table) - { - using (HeavyProfiler.LogNoStackTrace("InitializeUpdate", () => table.Type.TypeName())) + internal static UpdateCache InitializeUpdate(Table table) { - var trios = new List(); - var assigments = new List(); - var paramIdent = Expression.Parameter(typeof(Entity), "ident"); - var paramForbidden = Expression.Parameter(typeof(Forbidden), "forbidden"); - var paramOldTicks = Expression.Parameter(typeof(long), "oldTicks"); - var paramSuffix = Expression.Parameter(typeof(string), "suffix"); - var paramList = Expression.Parameter(typeof(List), "paramList"); + using (HeavyProfiler.LogNoStackTrace("InitializeUpdate", () => table.Type.TypeName())) + { + var trios = new List(); + var assigments = new List(); + var paramIdent = Expression.Parameter(typeof(Entity), "ident"); + var paramForbidden = Expression.Parameter(typeof(Forbidden), "forbidden"); + var paramOldTicks = Expression.Parameter(typeof(long), "oldTicks"); + var paramSuffix = Expression.Parameter(typeof(string), "suffix"); + var paramList = Expression.Parameter(typeof(List), "paramList"); - var cast = Expression.Parameter(table.Type); - assigments.Add(Expression.Assign(cast, Expression.Convert(paramIdent, table.Type))); + var cast = Expression.Parameter(table.Type); + assigments.Add(Expression.Assign(cast, Expression.Convert(paramIdent, table.Type))); - foreach (var item in table.Fields.Values.Where(a => !(a.Field is FieldPrimaryKey))) - item.Field.CreateParameter(trios, assigments, Expression.Field(cast, item.FieldInfo), paramForbidden, paramSuffix); + foreach (var item in table.Fields.Values.Where(a => !(a.Field is FieldPrimaryKey))) + item.Field.CreateParameter(trios, assigments, Expression.Field(cast, item.FieldInfo), paramForbidden, paramSuffix); - if (table.Mixins != null) - foreach (var item in table.Mixins.Values) - item.CreateParameter(trios, assigments, cast, paramForbidden, paramSuffix); + if (table.Mixins != null) + foreach (var item in table.Mixins.Values) + item.CreateParameter(trios, assigments, cast, paramForbidden, paramSuffix); - var pb = Connector.Current.ParameterBuilder; + var pb = Connector.Current.ParameterBuilder; - string idParamName = ParameterBuilder.GetParameterName("id"); + string idParamName = ParameterBuilder.GetParameterName("id"); - string oldTicksParamName = ParameterBuilder.GetParameterName("old_ticks"); + string oldTicksParamName = ParameterBuilder.GetParameterName("old_ticks"); - var isPostgres = Schema.Current.Settings.IsPostgres; + var isPostgres = Schema.Current.Settings.IsPostgres; - var id = table.PrimaryKey.Name.SqlEscape(isPostgres); - var updated = Table.Var(isPostgres, "updated"); + var id = table.PrimaryKey.Name.SqlEscape(isPostgres); + var updated = Table.Var(isPostgres, "updated"); - Func sqlUpdatePattern = (suffix, output) => - { - var result = $"UPDATE {table.Name} SET\r\n" + + Func sqlUpdatePattern = (suffix, output) => + { + var result = $"UPDATE {table.Name} SET\r\n" + trios.ToString(p => "{0} = {1}".FormatWith(p.SourceColumn.SqlEscape(isPostgres), p.ParameterName + suffix).Indent(2), ",\r\n") + "\r\n" + (output && !isPostgres ? $"OUTPUT INSERTED.{id} INTO {updated}\r\n" : null) + $"WHERE {id} = {idParamName + suffix}" + (table.Ticks != null ? $" AND {table.Ticks.Name.SqlEscape(isPostgres)} = {oldTicksParamName + suffix}" : null) + "\r\n" + (output && isPostgres ? $"RETURNING ({id})" : null); - if (!(isPostgres && output)) - return result.Trim() + ";"; + if (!(isPostgres && output)) + return result.Trim() + ";"; - return $@"WITH rows AS ( + return $@"WITH rows AS ( {result.Indent(4)} ) INSERT INTO {updated}({id}) SELECT {id} FROM rows;"; - }; + }; - List parameters = new List - { - pb.ParameterFactory(Trio.Concat(idParamName, paramSuffix), table.PrimaryKey.DbType, null, false, - Expression.Field(Expression.Property(Expression.Field(paramIdent, fiId), "Value"), "Object")) - }; + List parameters = new List + { + pb.ParameterFactory(Trio.Concat(idParamName, paramSuffix), table.PrimaryKey.DbType, null, null, null, null, false, + Expression.Field(Expression.Property(Expression.Field(paramIdent, fiId), "Value"), "Object")) + }; - if (table.Ticks != null) - { - parameters.Add(pb.ParameterFactory(Trio.Concat(oldTicksParamName, paramSuffix), table.Ticks.DbType, null, false, table.Ticks.ConvertTicks(paramOldTicks))); - } + if (table.Ticks != null) + { + parameters.Add(pb.ParameterFactory(Trio.Concat(oldTicksParamName, paramSuffix), table.Ticks.DbType, null, null, null, null, false, table.Ticks.ConvertTicks(paramOldTicks))); + } - parameters.AddRange(trios.Select(a => (Expression)a.ParameterBuilder)); + parameters.AddRange(trios.Select(a => (Expression)a.ParameterBuilder)); - var expr = Expression.Lambda>>( - CreateBlock(parameters, assigments, paramList), paramIdent, paramOldTicks, paramForbidden, paramSuffix, paramList); + var expr = Expression.Lambda>>( + CreateBlock(parameters, assigments, paramList), paramIdent, paramOldTicks, paramForbidden, paramSuffix, paramList); - - return new UpdateCache(table, sqlUpdatePattern, expr.Compile()); + + return new UpdateCache(table, sqlUpdatePattern, expr.Compile()); + } } - } - - } - - ResetLazy updater; + } - class CollectionsCache - { - public Func InsertCollectionsSync; + ResetLazy updater; - public Action> InsertCollections; - public Action> UpdateCollections; - public CollectionsCache(Func insertCollectionsSync, - Action> insertCollections, - Action> updateCollections) + class CollectionsCache { - InsertCollectionsSync = insertCollectionsSync; - InsertCollections = insertCollections; - UpdateCollections = updateCollections; - } + public Func InsertCollectionsSync; - internal static CollectionsCache? InitializeCollections(Table table) - { - using (HeavyProfiler.LogNoStackTrace("InitializeCollections", () => table.Type.TypeName())) + public Action> InsertCollections; + public Action> UpdateCollections; + + public CollectionsCache(Func insertCollectionsSync, + Action> insertCollections, + Action> updateCollections) { - var caches = - (from rt in table.TablesMList() - select rt.cache.Value).ToList(); + InsertCollectionsSync = insertCollectionsSync; + InsertCollections = insertCollections; + UpdateCollections = updateCollections; + } - if (caches.IsEmpty()) - return null; - else + internal static CollectionsCache? InitializeCollections(Table table) + { + using (HeavyProfiler.LogNoStackTrace("InitializeCollections", () => table.Type.TypeName())) { - return new CollectionsCache( - insertCollections: (entities) => - { - foreach (var rc in caches) - rc.RelationalInserts(entities); - }, - updateCollections: (entities) => - { - foreach (var rc in caches) - rc.RelationalUpdates(entities); - }, - insertCollectionsSync: (ident, suffix, replaceParameter) => - caches.Select((rc, i) => rc.RelationalUpdateSync(ident, suffix + "_" + i.ToString(), replaceParameter)).Combine(Spacing.Double)! - ); + var caches = + (from rt in table.TablesMList() + select rt.cache.Value).ToList(); + + if (caches.IsEmpty()) + return null; + else + { + return new CollectionsCache( + insertCollections: (entities) => + { + foreach (var rc in caches) + rc.RelationalInserts(entities); + }, + updateCollections: (entities) => + { + foreach (var rc in caches) + rc.RelationalUpdates(entities); + }, + insertCollectionsSync: (ident, suffix, replaceParameter) => + caches.Select((rc, i) => rc.RelationalUpdateSync(ident, suffix + "_" + i.ToString(), replaceParameter)).Combine(Spacing.Double)! + ); + } } } } - } - ResetLazy saveCollections; + ResetLazy saveCollections; - private SqlPreCommand? GetInsertCollectionSync(Entity ident, bool includeCollections, string suffix) - { - if (!includeCollections) - return null; + private SqlPreCommand? GetInsertCollectionSync(Entity ident, bool includeCollections, string suffix) + { + if (!includeCollections) + return null; - var cc = saveCollections.Value; - if (cc == null) - return null; + var cc = saveCollections.Value; + if (cc == null) + return null; - return cc.InsertCollectionsSync(ident, suffix, false); - } + return cc.InsertCollectionsSync(ident, suffix, false); + } - public SqlPreCommand InsertSqlSync(Entity ident, bool includeCollections = true, string? comment = null, string suffix = "") - { - PrepareEntitySync(ident); - SetToStrField(ident); + public SqlPreCommand InsertSqlSync(Entity ident, bool includeCollections = true, string? comment = null, string suffix = "") + { + PrepareEntitySync(ident); + SetToStrField(ident); - bool isGuid = this.PrimaryKey.DbType.IsGuid(); - var isPostgres = Schema.Current.Settings.IsPostgres; + bool isGuid = this.PrimaryKey.DbType.IsGuid(); + var isPostgres = Schema.Current.Settings.IsPostgres; - SqlPreCommand? collections = GetInsertCollectionSync(ident, includeCollections, suffix); + SqlPreCommand? collections = GetInsertCollectionSync(ident, includeCollections, suffix); - var identityBehaviour = IdentityBehaviour && !Administrator.IsIdentityBehaviourDisabled(this); + var identityBehaviour = IdentityBehaviour && !Administrator.IsIdentityBehaviourDisabled(this); - string? parentId = collections != null && identityBehaviour ? Table.Var(isPostgres, "parentId") : null; - string? newIds = collections != null && identityBehaviour && (!isPostgres && isGuid) ? Table.Var(isPostgres, "newIDs") : null; + string? parentId = collections != null && identityBehaviour ? Table.Var(isPostgres, "parentId") : null; + string? newIds = collections != null && identityBehaviour && (!isPostgres && isGuid) ? Table.Var(isPostgres, "newIDs") : null; - SqlPreCommandSimple insert = identityBehaviour ? - new SqlPreCommandSimple( - inserterIdentity.Value.SqlInsertPattern(new[] { suffix }, newIds ?? (isPostgres ? parentId : null)), - new List().Do(dbParams => inserterIdentity.Value.InsertParameters(ident, new Forbidden(), suffix, dbParams))).AddComment(comment) : - new SqlPreCommandSimple( - inserterDisableIdentity.Value.SqlInsertPattern(new[] { suffix }), - new List().Do(dbParams => inserterDisableIdentity.Value.InsertParameters(ident, new Forbidden(), suffix, dbParams))).AddComment(comment); + SqlPreCommandSimple insert = identityBehaviour ? + new SqlPreCommandSimple( + inserterIdentity.Value.SqlInsertPattern(new[] { suffix }, newIds ?? (isPostgres ? parentId : null)), + new List().Do(dbParams => inserterIdentity.Value.InsertParameters(ident, new Forbidden(), suffix, dbParams))).AddComment(comment) : + new SqlPreCommandSimple( + inserterDisableIdentity.Value.SqlInsertPattern(new[] { suffix }), + new List().Do(dbParams => inserterDisableIdentity.Value.InsertParameters(ident, new Forbidden(), suffix, dbParams))).AddComment(comment); - if (collections == null) - return insert; + if (collections == null) + return insert; - if (!identityBehaviour) - { - return SqlPreCommand.Combine(Spacing.Simple, - insert, - collections)!; - } + if (!identityBehaviour) + { + return SqlPreCommand.Combine(Spacing.Simple, + insert, + collections)!; + } - var pkType = this.PrimaryKey.DbType.ToString(isPostgres); + var pkType = this.PrimaryKey.DbType.ToString(isPostgres); - if (isPostgres) - { - return new SqlPreCommandSimple(@$"DO $$ + if (isPostgres) + { + return new SqlPreCommandSimple(@$"DO $$ DECLARE {parentId} {pkType}; BEGIN {insert.PlainSql().Indent(4)} {collections.PlainSql().Indent(4)} END $$;"); ; + } + else if (isGuid) + { + return SqlPreCommand.Combine(Spacing.Simple, + insert, + new SqlPreCommandSimple($"DECLARE {parentId} {pkType};"), + new SqlPreCommandSimple($"SELECT {parentId} = ID FROM {newIds};"), + collections)!; + } + else + { + return SqlPreCommand.Combine(Spacing.Simple, + new SqlPreCommandSimple($"DECLARE {parentId} {pkType};") { GoBefore = true }, + insert, + new SqlPreCommandSimple($"SET {parentId} = @@Identity;"), + collections)!; + } } - else if (isGuid) - { - return SqlPreCommand.Combine(Spacing.Simple, - insert, - new SqlPreCommandSimple($"DECLARE {parentId} {pkType};"), - new SqlPreCommandSimple($"SELECT {parentId} = ID FROM {newIds};"), - collections)!; - } - else - { - return SqlPreCommand.Combine(Spacing.Simple, - new SqlPreCommandSimple($"DECLARE {parentId} {pkType};") { GoBefore = true }, - insert, - new SqlPreCommandSimple($"SET {parentId} = @@Identity;"), - collections)!; - } - } - public SqlPreCommand? UpdateSqlSync(T entity, Expression>? where, bool includeCollections = true, string? comment = null, string suffix = "") - where T : Entity - { - if (typeof(T) != Type && where != null) - throw new InvalidOperationException("Invalid table"); - - PrepareEntitySync(entity); + public SqlPreCommand? UpdateSqlSync(T entity, Expression>? where, bool includeCollections = true, string? comment = null, string suffix = "") + where T : Entity + { + if (typeof(T) != Type && where != null) + throw new InvalidOperationException("Invalid table"); - if (SetToStrField(entity)) - entity.SetSelfModified(); + PrepareEntitySync(entity); - if (entity.Modified == ModifiedState.Clean || entity.Modified == ModifiedState.Sealed) - return null; + if (SetToStrField(entity)) + entity.SetSelfModified(); - var uc = updater.Value; - var sql = uc.SqlUpdatePattern(suffix, false); - var parameters = new List().Do(ps => uc.UpdateParameters(entity, (entity as Entity)?.Ticks ?? -1, new Forbidden(), suffix, ps)); + if (entity.Modified == ModifiedState.Clean || entity.Modified == ModifiedState.Sealed) + return null; - SqlPreCommand? update; - if (where != null) - { - bool isPostgres = Schema.Current.Settings.IsPostgres; + var uc = updater.Value; + var sql = uc.SqlUpdatePattern(suffix, false); + var parameters = new List().Do(ps => uc.UpdateParameters(entity, (entity as Entity)?.Ticks ?? -1, new Forbidden(), suffix, ps)); - var declare = DeclarePrimaryKeyVariable(entity, where); - var updateSql = new SqlPreCommandSimple(sql, parameters).AddComment(comment).ReplaceFirstParameter(entity.Id.VariableName); + SqlPreCommand? update; + if (where != null) + { + bool isPostgres = Schema.Current.Settings.IsPostgres; - update = isPostgres ? - PostgresDoBlock(entity.Id.VariableName!, declare, updateSql) : - SqlPreCommand.Combine(Spacing.Simple, declare, updateSql);; - } - else - { - update = new SqlPreCommandSimple(sql, parameters).AddComment(comment); - } + var declare = DeclarePrimaryKeyVariable(entity, where); + var updateSql = new SqlPreCommandSimple(sql, parameters).AddComment(comment).ReplaceFirstParameter(entity.Id.VariableName); - if (!includeCollections) - return update; + update = isPostgres ? + PostgresDoBlock(entity.Id.VariableName!, declare, updateSql) : + SqlPreCommand.Combine(Spacing.Simple, declare, updateSql);; + } + else + { + update = new SqlPreCommandSimple(sql, parameters).AddComment(comment); + } - var cc = saveCollections.Value; - if (cc == null) - return update; + if (!includeCollections) + return update; - SqlPreCommand collections = cc.InsertCollectionsSync((Entity)entity, suffix, where != null); + var cc = saveCollections.Value; + if (cc == null) + return update; - return SqlPreCommand.Combine(Spacing.Simple, update, collections); - } + SqlPreCommand collections = cc.InsertCollectionsSync((Entity)entity, suffix, where != null); - void PrepareEntitySync(Entity entity) - { - Schema current = Schema.Current; - DirectedGraph modifiables = Saver.PreSaving(() => GraphExplorer.FromRoot(entity)); + return SqlPreCommand.Combine(Spacing.Simple, update, collections); + } - var error = GraphExplorer.FullIntegrityCheck(modifiables); - if (error != null) + void PrepareEntitySync(Entity entity) { + Schema current = Schema.Current; + DirectedGraph modifiables = Saver.PreSaving(() => GraphExplorer.FromRoot(entity)); + + var error = GraphExplorer.FullIntegrityCheck(modifiables); + if (error != null) + { #if DEBUG - var withEntites = error.WithEntities(modifiables); - throw new IntegrityCheckException(withEntites); + var withEntites = error.WithEntities(modifiables); + throw new IntegrityCheckException(withEntites); #else - throw new IntegrityCheckException(error); + throw new IntegrityCheckException(error); #endif + } + GraphExplorer.PropagateModifications(modifiables.Inverse()); } - GraphExplorer.PropagateModifications(modifiables.Inverse()); - } - public class Trio - { - public Trio(IColumn column, Expression value, Expression suffix) + public class Trio { - this.SourceColumn = column.Name; - this.ParameterName = Signum.Engine.ParameterBuilder.GetParameterName(column.Name); - this.ParameterBuilder = Connector.Current.ParameterBuilder.ParameterFactory(Concat(this.ParameterName, suffix), column.DbType, column.UserDefinedTypeName, column.Nullable.ToBool(), value); - } + public Trio(IColumn column, Expression value, Expression suffix) + { + this.SourceColumn = column.Name; + this.ParameterName = Signum.Engine.ParameterBuilder.GetParameterName(column.Name); + this.ParameterBuilder = Connector.Current.ParameterBuilder.ParameterFactory( + Concat(this.ParameterName, suffix), + column.DbType, column.Size, column.Precision, column.Scale, column.UserDefinedTypeName, column.Nullable.ToBool(), value); + } - public string SourceColumn; - public string ParameterName; - public MemberInitExpression ParameterBuilder; //Expression + public string SourceColumn; + public string ParameterName; + public MemberInitExpression ParameterBuilder; //Expression - public override string ToString() - { - return "{0} {1} {2}".FormatWith(SourceColumn, ParameterName, ParameterBuilder.ToString()); - } + public override string ToString() + { + return "{0} {1} {2}".FormatWith(SourceColumn, ParameterName, ParameterBuilder.ToString()); + } - static readonly MethodInfo miConcat = ReflectionTools.GetMethodInfo(() => string.Concat("", "")); + static readonly MethodInfo miConcat = ReflectionTools.GetMethodInfo(() => string.Concat("", "")); - internal static Expression Concat(string baseName, Expression suffix) - { - return Expression.Call(null, miConcat, Expression.Constant(baseName), suffix); + internal static Expression Concat(string baseName, Expression suffix) + { + return Expression.Call(null, miConcat, Expression.Constant(baseName), suffix); + } } - } - static MethodInfo miAdd = ReflectionTools.GetMethodInfo(() => new List(1).Add(null!)); + static MethodInfo miAdd = ReflectionTools.GetMethodInfo(() => new List(1).Add(null!)); - public static Expression CreateBlock(IEnumerable parameters, IEnumerable assigments, Expression parameterList) - { - return Expression.Block( - assigments.OfType().Select(a => (ParameterExpression)a.Left), - assigments.Concat(parameters.Select(p => Expression.Call(parameterList, miAdd, p)))); + public static Expression CreateBlock(IEnumerable parameters, IEnumerable assigments, Expression parameterList) + { + return Expression.Block( + assigments.OfType().Select(a => (ParameterExpression)a.Left), + assigments.Concat(parameters.Select(p => Expression.Call(parameterList, miAdd, p)))); + } } -} -public partial class TableMList -{ - internal interface IMListCache + public partial class TableMList { - SqlPreCommand? RelationalUpdateSync(Entity parent, string suffix, bool replaceParameter); - void RelationalInserts(List entities); - void RelationalUpdates(List entities); + internal interface IMListCache + { + SqlPreCommand? RelationalUpdateSync(Entity parent, string suffix, bool replaceParameter); + void RelationalInserts(List entities); + void RelationalUpdates(List entities); - object?[] BulkInsertDataRow(Entity entity, object value, int order); - } + object?[] BulkInsertDataRow(Entity entity, object value, int order); + } - internal class TableMListCache : IMListCache - { - internal TableMList table = null!; + internal class TableMListCache : IMListCache + { + internal TableMList table = null!; - internal Func sqlDelete = null!; - public Func DeleteParameter = null!; - public ConcurrentDictionary>> deleteCache = new ConcurrentDictionary>>(); + internal Func sqlDelete = null!; + public Func DeleteParameter = null!; + public ConcurrentDictionary>> deleteCache = new ConcurrentDictionary>>(); - Action> GetDelete(int numEntities) - { - return deleteCache.GetOrAdd(numEntities, num => + Action> GetDelete(int numEntities) { - string sql = Enumerable.Range(0, num).ToString(i => sqlDelete(i.ToString()), ";\r\n"); - - return list => + return deleteCache.GetOrAdd(numEntities, num => { - List parameters = new List(); - for (int i = 0; i < num; i++) + string sql = Enumerable.Range(0, num).ToString(i => sqlDelete(i.ToString()), ";\r\n"); + + return list => { - parameters.Add(DeleteParameter(list[i], i.ToString())); - } - new SqlPreCommandSimple(sql, parameters).ExecuteNonQuery(); - }; - }); - } + List parameters = new List(); + for (int i = 0; i < num; i++) + { + parameters.Add(DeleteParameter(list[i], i.ToString())); + } + new SqlPreCommandSimple(sql, parameters).ExecuteNonQuery(); + }; + }); + } - internal Func sqlDeleteExcept = null!; - public Func> DeleteExceptParameter = null!; - public ConcurrentDictionary> deleteExceptCache = new ConcurrentDictionary>(); + internal Func sqlDeleteExcept = null!; + public Func> DeleteExceptParameter = null!; + public ConcurrentDictionary> deleteExceptCache = new ConcurrentDictionary>(); - Action GetDeleteExcept(int numExceptions) - { - return deleteExceptCache.GetOrAdd(numExceptions, num => + Action GetDeleteExcept(int numExceptions) { - string sql = sqlDeleteExcept(numExceptions); Enumerable.Range(0, num).ToString(i => sqlDelete(i.ToString()), ";\r\n"); - - return delete => + return deleteExceptCache.GetOrAdd(numExceptions, num => { - new SqlPreCommandSimple(sql, DeleteExceptParameter(delete)).ExecuteNonQuery(); - }; - }); - } + string sql = sqlDeleteExcept(numExceptions); Enumerable.Range(0, num).ToString(i => sqlDelete(i.ToString()), ";\r\n"); - public struct MListDelete - { - public readonly Entity Entity; - public readonly PrimaryKey[] ExceptRowIds; + return delete => + { + new SqlPreCommandSimple(sql, DeleteExceptParameter(delete)).ExecuteNonQuery(); + }; + }); + } - public MListDelete(Entity ident, PrimaryKey[] exceptRowIds) + public struct MListDelete { - this.Entity = ident; - this.ExceptRowIds = exceptRowIds; + public readonly Entity Entity; + public readonly PrimaryKey[] ExceptRowIds; + + public MListDelete(Entity ident, PrimaryKey[] exceptRowIds) + { + this.Entity = ident; + this.ExceptRowIds = exceptRowIds; + } } - } - internal bool hasOrder = false; - internal bool isEmbeddedEntity = false; - internal Func sqlUpdate = null!; - public Action> UpdateParameters = null!; - public ConcurrentDictionary>> updateCache = - new ConcurrentDictionary>>(); + internal bool hasOrder = false; + internal bool isEmbeddedEntity = false; + internal Func sqlUpdate = null!; + public Action> UpdateParameters = null!; + public ConcurrentDictionary>> updateCache = + new ConcurrentDictionary>>(); - Action> GetUpdate(int numElements) - { - return updateCache.GetOrAdd(numElements, num => + Action> GetUpdate(int numElements) { - string sql = Enumerable.Range(0, num).ToString(i => sqlUpdate(i.ToString()), ";\r\n"); - - return (List list) => + return updateCache.GetOrAdd(numElements, num => { - List parameters = new List(); - for (int i = 0; i < num; i++) - { - var pair = list[i]; + string sql = Enumerable.Range(0, num).ToString(i => sqlUpdate(i.ToString()), ";\r\n"); - var row = pair.MList.InnerList[pair.Index]; + return (List list) => + { + List parameters = new List(); + for (int i = 0; i < num; i++) + { + var pair = list[i]; - UpdateParameters(pair.Entity, row.RowId!.Value, row.Element, pair.Index, pair.Forbidden, i.ToString(), parameters); - } - new SqlPreCommandSimple(sql, parameters).ExecuteNonQuery(); - }; - }); - } + var row = pair.MList.InnerList[pair.Index]; - public struct MListUpdate - { - public readonly Entity Entity; - public readonly Forbidden Forbidden; - public readonly IMListPrivate MList; - public readonly int Index; + UpdateParameters(pair.Entity, row.RowId!.Value, row.Element, pair.Index, pair.Forbidden, i.ToString(), parameters); + } + new SqlPreCommandSimple(sql, parameters).ExecuteNonQuery(); + }; + }); + } - public MListUpdate(EntityForbidden ef, MList mlist, int index) + public struct MListUpdate { - this.Entity = ef.Entity; - this.Forbidden = ef.Forbidden; - this.MList = mlist; - this.Index = index; + public readonly Entity Entity; + public readonly Forbidden Forbidden; + public readonly IMListPrivate MList; + public readonly int Index; + + public MListUpdate(EntityForbidden ef, MList mlist, int index) + { + this.Entity = ef.Entity; + this.Forbidden = ef.Forbidden; + this.MList = mlist; + this.Index = index; + } } - } - internal Func sqlInsert = null!; - public Action> InsertParameters = null!; - public ConcurrentDictionary>> insertCache = - new ConcurrentDictionary>>(); + internal Func sqlInsert = null!; + public Action> InsertParameters = null!; + public ConcurrentDictionary>> insertCache = + new ConcurrentDictionary>>(); - Action> GetInsert(int numElements) - { - return insertCache.GetOrAdd(numElements, num => + Action> GetInsert(int numElements) { - bool isPostgres = Schema.Current.Settings.IsPostgres; - - string sqlMulti = sqlInsert(Enumerable.Range(0, num).Select(i => i.ToString()).ToArray(), true); - - return (List list) => + return insertCache.GetOrAdd(numElements, num => { - List result = new List(); - for (int i = 0; i < num; i++) - { - var pair = list[i]; - InsertParameters(pair.Entity, pair.MList.InnerList[pair.Index].Element, pair.Index, pair.Forbidden, i.ToString(), result); - } + bool isPostgres = Schema.Current.Settings.IsPostgres; - DataTable dt = new SqlPreCommandSimple(sqlMulti, result).ExecuteDataTable(); + string sqlMulti = sqlInsert(Enumerable.Range(0, num).Select(i => i.ToString()).ToArray(), true); - for (int i = 0; i < num; i++) + return (List list) => { - var pair = list[i]; + List result = new List(); + for (int i = 0; i < num; i++) + { + var pair = list[i]; + InsertParameters(pair.Entity, pair.MList.InnerList[pair.Index].Element, pair.Index, pair.Forbidden, i.ToString(), result); + } - pair.MList.SetRowId(pair.Index, new PrimaryKey((IComparable)dt.Rows[i][0])); + DataTable dt = new SqlPreCommandSimple(sqlMulti, result).ExecuteDataTable(); - if (this.hasOrder) - pair.MList.SetOldIndex(pair.Index); - } - }; - }); - } + for (int i = 0; i < num; i++) + { + var pair = list[i]; - public struct MListInsert - { - public readonly Entity Entity; - public readonly Forbidden Forbidden; - public readonly IMListPrivate MList; - public readonly int Index; + pair.MList.SetRowId(pair.Index, new PrimaryKey((IComparable)dt.Rows[i][0])); - public MListInsert(EntityForbidden ef, MList mlist, int index) - { - this.Entity = ef.Entity; - this.Forbidden = ef.Forbidden; - this.MList = mlist; - this.Index = index; + if (this.hasOrder) + pair.MList.SetOldIndex(pair.Index); + } + }; + }); } - } - public object?[] BulkInsertDataRow(Entity entity, object value, int order) - { - List paramList = new List(); - InsertParameters(entity, (T)value, order, new Forbidden(null), "", paramList); - return paramList.Select(a => a.Value).ToArray(); - } - - public Func> Getter = null!; - - public void RelationalInserts(List entities) - { - List toInsert = new List(); - - foreach (var ef in entities) + public struct MListInsert { - if (!ef.Forbidden.IsEmpty) - continue; //Will be called again - - MList collection = Getter(ef.Entity); - - if (collection == null) - continue; - - if (collection.Modified == ModifiedState.Clean) - continue; + public readonly Entity Entity; + public readonly Forbidden Forbidden; + public readonly IMListPrivate MList; + public readonly int Index; - for (int i = 0; i < collection.Count; i++) + public MListInsert(EntityForbidden ef, MList mlist, int index) { - toInsert.Add(new MListInsert(ef, collection, i)); + this.Entity = ef.Entity; + this.Forbidden = ef.Forbidden; + this.MList = mlist; + this.Index = index; } } - toInsert.SplitStatements(this.table.Columns.Count, list => GetInsert(list.Count)(list)); - } + public object?[] BulkInsertDataRow(Entity entity, object value, int order) + { + List paramList = new List(); + InsertParameters(entity, (T)value, order, new Forbidden(null), "", paramList); + return paramList.Select(a => a.Value).ToArray(); + } - public void RelationalUpdates(List idents) - { - List toDelete = new List(); - List toDeleteExcept = new List(); - List toInsert = new List(); - List toUpdate = new List(); + public Func> Getter = null!; - foreach (var ef in idents) + public void RelationalInserts(List entities) { - if (!ef.Forbidden.IsEmpty) - continue; //Will be called again - - MList collection = Getter(ef.Entity); + List toInsert = new List(); - if (collection == null) - toDelete.Add(ef.Entity); - else + foreach (var ef in entities) { + if (!ef.Forbidden.IsEmpty) + continue; //Will be called again + + MList collection = Getter(ef.Entity); + + if (collection == null) + continue; + if (collection.Modified == ModifiedState.Clean) continue; - var innerList = ((IMListPrivate)collection).InnerList; + for (int i = 0; i < collection.Count; i++) + { + toInsert.Add(new MListInsert(ef, collection, i)); + } + } + + toInsert.SplitStatements(this.table.Columns.Count, list => GetInsert(list.Count)(list)); + } + + public void RelationalUpdates(List idents) + { + List toDelete = new List(); + List toDeleteExcept = new List(); + List toInsert = new List(); + List toUpdate = new List(); + + foreach (var ef in idents) + { + if (!ef.Forbidden.IsEmpty) + continue; //Will be called again - var exceptions = innerList.Select(a => a.RowId).NotNull().ToArray(); + MList collection = Getter(ef.Entity); - if (exceptions.IsEmpty()) + if (collection == null) toDelete.Add(ef.Entity); else - toDeleteExcept.Add(new MListDelete(ef.Entity, exceptions)); - - if (isEmbeddedEntity || hasOrder) { - for (int i = 0; i < innerList.Count; i++) - { - var row = innerList[i]; + if (collection.Modified == ModifiedState.Clean) + continue; + + var innerList = ((IMListPrivate)collection).InnerList; + + var exceptions = innerList.Select(a => a.RowId).NotNull().ToArray(); - if (row.RowId.HasValue) + if (exceptions.IsEmpty()) + toDelete.Add(ef.Entity); + else + toDeleteExcept.Add(new MListDelete(ef.Entity, exceptions)); + + if (isEmbeddedEntity || hasOrder) + { + for (int i = 0; i < innerList.Count; i++) { - if (hasOrder && row.OldIndex != i || - isEmbeddedEntity && ((ModifiableEntity)(object)row.Element!).IsGraphModified) + var row = innerList[i]; + + if (row.RowId.HasValue) { - toUpdate.Add(new MListUpdate(ef, collection, i)); + if (hasOrder && row.OldIndex != i || + isEmbeddedEntity && ((ModifiableEntity)(object)row.Element!).IsGraphModified) + { + toUpdate.Add(new MListUpdate(ef, collection, i)); + } } } } - } - for (int i = 0; i < innerList.Count; i++) - { - if (innerList[i].RowId == null) - toInsert.Add(new MListInsert(ef, collection, i)); + for (int i = 0; i < innerList.Count; i++) + { + if (innerList[i].RowId == null) + toInsert.Add(new MListInsert(ef, collection, i)); + } } } - } - - toDelete.SplitStatements(2, list => GetDelete(list.Count)(list)); - toDeleteExcept.ForEach(e => GetDeleteExcept(e.ExceptRowIds.Length)(e)); - toUpdate.SplitStatements(this.table.Columns.Count + 2, listPairs => GetUpdate(listPairs.Count)(listPairs)); - toInsert.SplitStatements(this.table.Columns.Count, listPairs => GetInsert(listPairs.Count)(listPairs)); - } + toDelete.SplitStatements(2, list => GetDelete(list.Count)(list)); - public SqlPreCommand? RelationalUpdateSync(Entity parent, string suffix, bool replaceParameter) - { - MList collection = Getter(parent); + toDeleteExcept.ForEach(e => GetDeleteExcept(e.ExceptRowIds.Length)(e)); + toUpdate.SplitStatements(this.table.Columns.Count + 2, listPairs => GetUpdate(listPairs.Count)(listPairs)); + toInsert.SplitStatements(this.table.Columns.Count, listPairs => GetInsert(listPairs.Count)(listPairs)); + } - if (collection == null) + public SqlPreCommand? RelationalUpdateSync(Entity parent, string suffix, bool replaceParameter) { - if (parent.IsNew) - return null; + MList collection = Getter(parent); - return new SqlPreCommandSimple(sqlDelete(suffix), new List { DeleteParameter(parent, suffix) }) - .ReplaceFirstParameter(replaceParameter ? parent.Id.VariableName : null); - } + if (collection == null) + { + if (parent.IsNew) + return null; - if (collection.Modified == ModifiedState.Clean) - return null; + return new SqlPreCommandSimple(sqlDelete(suffix), new List { DeleteParameter(parent, suffix) }) + .ReplaceFirstParameter(replaceParameter ? parent.Id.VariableName : null); + } - if (parent.IsNew) - { - var isPostgres = Schema.Current.Settings.IsPostgres; - var parentIdVar = Table.Var(isPostgres, "parentId"); - return collection.Select((e, i) => + if (collection.Modified == ModifiedState.Clean) + return null; + + if (parent.IsNew) { - var parameters = new List(); - InsertParameters(parent, e, i, new Forbidden(new HashSet { parent }), suffix + "_" + i, parameters); - var parentId = parameters.First(); // wont be replaced, generating @parentId - parameters.RemoveAt(0); - string script = sqlInsert(new[] { suffix + "_" + i }, false); - script = script.Replace(parentId.ParameterName, parentIdVar); - return new SqlPreCommandSimple(script, parameters).AddComment(e?.ToString()); - }).Combine(Spacing.Simple); - } - else - { - return SqlPreCommand.Combine(Spacing.Simple, - new SqlPreCommandSimple(sqlDelete(suffix), new List { DeleteParameter(parent, suffix) }).ReplaceFirstParameter(replaceParameter ? parent.Id.VariableName : null), - collection.Select((e, i) => new SqlPreCommandSimple(sqlInsert(new[] { suffix + "_" + i }, false), new List().Do(ps => InsertParameters(parent, e, i, new Forbidden(), suffix + "_" + i, ps))) - .AddComment(e?.ToString()) - .ReplaceFirstParameter(replaceParameter ? parent.Id.VariableName : null) - ).Combine(Spacing.Simple)); + var isPostgres = Schema.Current.Settings.IsPostgres; + var parentIdVar = Table.Var(isPostgres, "parentId"); + return collection.Select((e, i) => + { + var parameters = new List(); + InsertParameters(parent, e, i, new Forbidden(new HashSet { parent }), suffix + "_" + i, parameters); + var parentId = parameters.First(); // wont be replaced, generating @parentId + parameters.RemoveAt(0); + string script = sqlInsert(new[] { suffix + "_" + i }, false); + script = script.Replace(parentId.ParameterName, parentIdVar); + return new SqlPreCommandSimple(script, parameters).AddComment(e?.ToString()); + }).Combine(Spacing.Simple); + } + else + { + return SqlPreCommand.Combine(Spacing.Simple, + new SqlPreCommandSimple(sqlDelete(suffix), new List { DeleteParameter(parent, suffix) }).ReplaceFirstParameter(replaceParameter ? parent.Id.VariableName : null), + collection.Select((e, i) => new SqlPreCommandSimple(sqlInsert(new[] { suffix + "_" + i }, false), new List().Do(ps => InsertParameters(parent, e, i, new Forbidden(), suffix + "_" + i, ps))) + .AddComment(e?.ToString()) + .ReplaceFirstParameter(replaceParameter ? parent.Id.VariableName : null) + ).Combine(Spacing.Simple)); + } } } - } - static GenericInvoker> giCreateCache = + static GenericInvoker> giCreateCache = new((TableMList rt) => rt.CreateCache()); - internal Lazy cache; - - TableMListCache CreateCache() - { - var pb = Connector.Current.ParameterBuilder; - var isPostgres = Schema.Current.Settings.IsPostgres; + internal Lazy cache; - TableMListCache result = new TableMListCache + TableMListCache CreateCache() { - table = this, - Getter = entity => (MList)Getter(entity), - - sqlDelete = suffix => "DELETE FROM {0} WHERE {1} = {2}".FormatWith(Name, BackReference.Name.SqlEscape(isPostgres), ParameterBuilder.GetParameterName(BackReference.Name + suffix)), - DeleteParameter = (ident, suffix) => pb.CreateReferenceParameter(ParameterBuilder.GetParameterName(BackReference.Name + suffix), ident.Id, this.BackReference.ReferenceTable.PrimaryKey), + var pb = Connector.Current.ParameterBuilder; + var isPostgres = Schema.Current.Settings.IsPostgres; - sqlDeleteExcept = num => + TableMListCache result = new TableMListCache { - var sql = "DELETE FROM {0} WHERE {1} = {2}" - .FormatWith(Name, BackReference.Name.SqlEscape(isPostgres), ParameterBuilder.GetParameterName(BackReference.Name)); + table = this, + Getter = entity => (MList)Getter(entity), + + sqlDelete = suffix => "DELETE FROM {0} WHERE {1} = {2}".FormatWith(Name, BackReference.Name.SqlEscape(isPostgres), ParameterBuilder.GetParameterName(BackReference.Name + suffix)), + DeleteParameter = (ident, suffix) => pb.CreateReferenceParameter(ParameterBuilder.GetParameterName(BackReference.Name + suffix), ident.Id, this.BackReference.ReferenceTable.PrimaryKey), - sql += " AND {0} NOT IN ({1})" - .FormatWith(PrimaryKey.Name.SqlEscape(isPostgres), 0.To(num).Select(i => ParameterBuilder.GetParameterName("e" + i)).ToString(", ")); + sqlDeleteExcept = num => + { + var sql = "DELETE FROM {0} WHERE {1} = {2}" + .FormatWith(Name, BackReference.Name.SqlEscape(isPostgres), ParameterBuilder.GetParameterName(BackReference.Name)); - return sql; - }, + sql += " AND {0} NOT IN ({1})" + .FormatWith(PrimaryKey.Name.SqlEscape(isPostgres), 0.To(num).Select(i => ParameterBuilder.GetParameterName("e" + i)).ToString(", ")); - DeleteExceptParameter = delete => - { - var list = new List + return sql; + }, + + DeleteExceptParameter = delete => { - pb.CreateReferenceParameter(ParameterBuilder.GetParameterName(BackReference.Name), delete.Entity.Id, BackReference) - }; + var list = new List + { + pb.CreateReferenceParameter(ParameterBuilder.GetParameterName(BackReference.Name), delete.Entity.Id, BackReference) + }; - list.AddRange(delete.ExceptRowIds.Select((e, i) => pb.CreateReferenceParameter(ParameterBuilder.GetParameterName("e" + i), e, PrimaryKey))); + list.AddRange(delete.ExceptRowIds.Select((e, i) => pb.CreateReferenceParameter(ParameterBuilder.GetParameterName("e" + i), e, PrimaryKey))); - return list; - } - }; - var paramIdent = Expression.Parameter(typeof(Entity), "ident"); - var paramItem = Expression.Parameter(typeof(T), "item"); - var paramOrder = Expression.Parameter(typeof(int), "order"); - var paramForbidden = Expression.Parameter(typeof(Forbidden), "forbidden"); - var paramSuffix = Expression.Parameter(typeof(string), "suffix"); - var paramList = Expression.Parameter(typeof(List), "paramList"); - { - var trios = new List(); - var assigments = new List(); + return list; + } + }; + var paramIdent = Expression.Parameter(typeof(Entity), "ident"); + var paramItem = Expression.Parameter(typeof(T), "item"); + var paramOrder = Expression.Parameter(typeof(int), "order"); + var paramForbidden = Expression.Parameter(typeof(Forbidden), "forbidden"); + var paramSuffix = Expression.Parameter(typeof(string), "suffix"); + var paramList = Expression.Parameter(typeof(List), "paramList"); + { + var trios = new List(); + var assigments = new List(); - BackReference.CreateParameter(trios, assigments, paramIdent, paramForbidden, paramSuffix); - if (this.Order != null) - Order.CreateParameter(trios, assigments, paramOrder, paramForbidden, paramSuffix); - Field.CreateParameter(trios, assigments, paramItem, paramForbidden, paramSuffix); + BackReference.CreateParameter(trios, assigments, paramIdent, paramForbidden, paramSuffix); + if (this.Order != null) + Order.CreateParameter(trios, assigments, paramOrder, paramForbidden, paramSuffix); + Field.CreateParameter(trios, assigments, paramItem, paramForbidden, paramSuffix); - result.sqlInsert = (suffixes, output) => "INSERT INTO {0} ({1})\r\n{2}VALUES\r\n{3}{4};".FormatWith(Name, - trios.ToString(p => p.SourceColumn.SqlEscape(isPostgres), ", "), - output && !isPostgres ? $"OUTPUT INSERTED.{PrimaryKey.Name.SqlEscape(isPostgres)}\r\n" : null, - suffixes.ToString(s => " (" + trios.ToString(p => p.ParameterName + s, ", ") + ")", ",\r\n"), - output && isPostgres ? $"\r\nRETURNING {PrimaryKey.Name.SqlEscape(isPostgres)}" : null); + result.sqlInsert = (suffixes, output) => "INSERT INTO {0} ({1})\r\n{2}VALUES\r\n{3}{4};".FormatWith(Name, + trios.ToString(p => p.SourceColumn.SqlEscape(isPostgres), ", "), + output && !isPostgres ? $"OUTPUT INSERTED.{PrimaryKey.Name.SqlEscape(isPostgres)}\r\n" : null, + suffixes.ToString(s => " (" + trios.ToString(p => p.ParameterName + s, ", ") + ")", ",\r\n"), + output && isPostgres ? $"\r\nRETURNING {PrimaryKey.Name.SqlEscape(isPostgres)}" : null); - var expr = Expression.Lambda>>( - Table.CreateBlock(trios.Select(a => a.ParameterBuilder), assigments, paramList), paramIdent, paramItem, paramOrder, paramForbidden, paramSuffix, paramList); + var expr = Expression.Lambda>>( + Table.CreateBlock(trios.Select(a => a.ParameterBuilder), assigments, paramList), paramIdent, paramItem, paramOrder, paramForbidden, paramSuffix, paramList); - result.InsertParameters = expr.Compile(); - } + result.InsertParameters = expr.Compile(); + } - result.hasOrder = this.Order != null; - result.isEmbeddedEntity = typeof(EmbeddedEntity).IsAssignableFrom(this.Field.FieldType); + result.hasOrder = this.Order != null; + result.isEmbeddedEntity = typeof(EmbeddedEntity).IsAssignableFrom(this.Field.FieldType); - if (result.isEmbeddedEntity || result.hasOrder) - { - var trios = new List(); - var assigments = new List(); + if (result.isEmbeddedEntity || result.hasOrder) + { + var trios = new List(); + var assigments = new List(); - var paramRowId = Expression.Parameter(typeof(PrimaryKey), "rowId"); + var paramRowId = Expression.Parameter(typeof(PrimaryKey), "rowId"); - string parentId = "parentId"; - string rowId = "rowId"; + string parentId = "parentId"; + string rowId = "rowId"; - //BackReference.CreateParameter(trios, assigments, paramIdent, paramForbidden, paramSuffix); - if (this.Order != null) - Order.CreateParameter(trios, assigments, paramOrder, paramForbidden, paramSuffix); - Field.CreateParameter(trios, assigments, paramItem, paramForbidden, paramSuffix); + //BackReference.CreateParameter(trios, assigments, paramIdent, paramForbidden, paramSuffix); + if (this.Order != null) + Order.CreateParameter(trios, assigments, paramOrder, paramForbidden, paramSuffix); + Field.CreateParameter(trios, assigments, paramItem, paramForbidden, paramSuffix); - result.sqlUpdate = suffix => "UPDATE {0} SET \r\n{1}\r\n WHERE {2} = {3} AND {4} = {5};".FormatWith(Name, - trios.ToString(p => "{0} = {1}".FormatWith(p.SourceColumn.SqlEscape(isPostgres), p.ParameterName + suffix).Indent(2), ",\r\n"), - this.BackReference.Name.SqlEscape(isPostgres), ParameterBuilder.GetParameterName(parentId + suffix), - this.PrimaryKey.Name.SqlEscape(isPostgres), ParameterBuilder.GetParameterName(rowId + suffix)); + result.sqlUpdate = suffix => "UPDATE {0} SET \r\n{1}\r\n WHERE {2} = {3} AND {4} = {5};".FormatWith(Name, + trios.ToString(p => "{0} = {1}".FormatWith(p.SourceColumn.SqlEscape(isPostgres), p.ParameterName + suffix).Indent(2), ",\r\n"), + this.BackReference.Name.SqlEscape(isPostgres), ParameterBuilder.GetParameterName(parentId + suffix), + this.PrimaryKey.Name.SqlEscape(isPostgres), ParameterBuilder.GetParameterName(rowId + suffix)); - var parameters = trios.Select(a => a.ParameterBuilder).ToList(); + var parameters = trios.Select(a => a.ParameterBuilder).ToList(); - parameters.Add(pb.ParameterFactory(Table.Trio.Concat(parentId, paramSuffix), this.BackReference.DbType, null, false, - Expression.Field(Expression.Property(Expression.Field(paramIdent, Table.fiId), "Value"), "Object"))); - parameters.Add(pb.ParameterFactory(Table.Trio.Concat(rowId, paramSuffix), this.PrimaryKey.DbType, null, false, - Expression.Field(paramRowId, "Object"))); + parameters.Add(pb.ParameterFactory(Table.Trio.Concat(parentId, paramSuffix), this.BackReference.DbType, null, null, null, null, false, + Expression.Field(Expression.Property(Expression.Field(paramIdent, Table.fiId), "Value"), "Object"))); + parameters.Add(pb.ParameterFactory(Table.Trio.Concat(rowId, paramSuffix), this.PrimaryKey.DbType, null, null, null, null, false, + Expression.Field(paramRowId, "Object"))); - var expr = Expression.Lambda>>( - Table.CreateBlock(parameters, assigments, paramList), paramIdent, paramRowId, paramItem, paramOrder, paramForbidden, paramSuffix, paramList); - result.UpdateParameters = expr.Compile(); - } + var expr = Expression.Lambda>>( + Table.CreateBlock(parameters, assigments, paramList), paramIdent, paramRowId, paramItem, paramOrder, paramForbidden, paramSuffix, paramList); + result.UpdateParameters = expr.Compile(); + } - return result; + return result; + } } -} -internal static class SaveUtils -{ - public static void SplitStatements(this IList original, int numParametersPerElement, Action> action) + internal static class SaveUtils { - if (!Connector.Current.AllowsMultipleQueries) + public static void SplitStatements(this IList original, int numParametersPerElement, Action> action) { - List part = new List(1); - for (int i = 0; i < original.Count; i++) + if (!Connector.Current.AllowsMultipleQueries) { - part[0] = original[i]; - action(part); + List part = new List(1); + for (int i = 0; i < original.Count; i++) + { + part[0] = original[i]; + action(part); + } } - } - else - { - var s = Schema.Current.Settings; - int max = Math.Min(s.MaxNumberOfStatementsInSaveQueries, s.MaxNumberOfParameters / numParametersPerElement); - - List part = new List(max); - int i = 0; - for (; i <= original.Count - max; i += max) + else { - Fill(part, original, i, max); - action(part); - } + var s = Schema.Current.Settings; + int max = Math.Min(s.MaxNumberOfStatementsInSaveQueries, s.MaxNumberOfParameters / numParametersPerElement); - int remaining = original.Count - i; - if (remaining > 0) - { - Fill(part, original, i, remaining); - action(part); + List part = new List(max); + int i = 0; + for (; i <= original.Count - max; i += max) + { + Fill(part, original, i, max); + action(part); + } + + int remaining = original.Count - i; + if (remaining > 0) + { + Fill(part, original, i, remaining); + action(part); + } } } - } - static List Fill(List part, IList original, int pos, int count) - { - part.Clear(); - int max = pos + count; - for (int i = pos; i < max; i++) - part.Add(original[i]); - return part; + static List Fill(List part, IList original, int pos, int count) + { + part.Clear(); + int max = pos + count; + for (int i = pos; i < max; i++) + part.Add(original[i]); + return part; + } } -} - -public abstract partial class Field -{ - protected internal virtual void CreateParameter(List trios, List assigments, Expression value, Expression forbidden, Expression suffix) { } -} -public partial class FieldPrimaryKey -{ - protected internal override void CreateParameter(List trios, List assigments, Expression value, Expression forbidden, Expression suffix) + public abstract partial class Field { - trios.Add(new Table.Trio(this, Expression.Field(Expression.Property(value, "Value"), "Object"), suffix)); + protected internal virtual void CreateParameter(List trios, List assigments, Expression value, Expression forbidden, Expression suffix) { } } -} -public partial class FieldValue -{ - protected internal override void CreateParameter(List trios, List assigments, Expression value, Expression forbidden, Expression suffix) + public partial class FieldPrimaryKey { - trios.Add(new Table.Trio(this, value, suffix)); + protected internal override void CreateParameter(List trios, List assigments, Expression value, Expression forbidden, Expression suffix) + { + trios.Add(new Table.Trio(this, Expression.Field(Expression.Property(value, "Value"), "Object"), suffix)); + } } -} -public partial class FieldTicks -{ - public static readonly ConstructorInfo ciDateTimeTicks = ReflectionTools.GetConstuctorInfo(() => new DateTime(0L)); - - protected internal override void CreateParameter(List trios, List assigments, Expression value, Expression forbidden, Expression suffix) + public partial class FieldValue { - if (this.Type == this.FieldType) + protected internal override void CreateParameter(List trios, List assigments, Expression value, Expression forbidden, Expression suffix) + { trios.Add(new Table.Trio(this, value, suffix)); - else if (this.Type == typeof(DateTime)) - trios.Add(new Table.Trio(this, Expression.New(ciDateTimeTicks, value), suffix)); - else - throw new NotImplementedException("FieldTicks of type {0} not supported".FormatWith(this.Type)); + } } - internal Expression ConvertTicks(ParameterExpression paramOldTicks) + public partial class FieldTicks { - if (this.Type == this.FieldType) - return paramOldTicks; + public static readonly ConstructorInfo ciDateTimeTicks = ReflectionTools.GetConstuctorInfo(() => new DateTime(0L)); - if (this.Type == typeof(DateTime)) - return Expression.New(ciDateTimeTicks, paramOldTicks); + protected internal override void CreateParameter(List trios, List assigments, Expression value, Expression forbidden, Expression suffix) + { + if (this.Type == this.FieldType) + trios.Add(new Table.Trio(this, value, suffix)); + else if (this.Type == typeof(DateTime)) + trios.Add(new Table.Trio(this, Expression.New(ciDateTimeTicks, value), suffix)); + else + throw new NotImplementedException("FieldTicks of type {0} not supported".FormatWith(this.Type)); + } - throw new NotImplementedException("FieldTicks of type {0} not supported".FormatWith(this.Type)); - } -} + internal Expression ConvertTicks(ParameterExpression paramOldTicks) + { + if (this.Type == this.FieldType) + return paramOldTicks; -public static partial class FieldReferenceExtensions -{ - static readonly MethodInfo miGetIdForLite = ReflectionTools.GetMethodInfo(() => GetIdForLite(null!, new Forbidden())); - static readonly MethodInfo miGetIdForEntity = ReflectionTools.GetMethodInfo(() => GetIdForEntity(null!, new Forbidden())); - static readonly MethodInfo miGetIdForLiteCleanEntity = ReflectionTools.GetMethodInfo(() => GetIdForLiteCleanEntity(null!, new Forbidden())); + if (this.Type == typeof(DateTime)) + return Expression.New(ciDateTimeTicks, paramOldTicks); - public static void AssertIsLite(this IFieldReference fr) - { - if (!fr.IsLite) - throw new InvalidOperationException("The field is not a lite"); + throw new NotImplementedException("FieldTicks of type {0} not supported".FormatWith(this.Type)); + } } - public static Expression GetIdFactory(this IFieldReference fr, Expression value, Expression forbidden) + public static partial class FieldReferenceExtensions { - var mi = !fr.IsLite ? miGetIdForEntity : - fr.ClearEntityOnSaving ? miGetIdForLiteCleanEntity : - miGetIdForLite; + static readonly MethodInfo miGetIdForLite = ReflectionTools.GetMethodInfo(() => GetIdForLite(null!, new Forbidden())); + static readonly MethodInfo miGetIdForEntity = ReflectionTools.GetMethodInfo(() => GetIdForEntity(null!, new Forbidden())); + static readonly MethodInfo miGetIdForLiteCleanEntity = ReflectionTools.GetMethodInfo(() => GetIdForLiteCleanEntity(null!, new Forbidden())); - return Expression.Call(mi, value, forbidden); - } + public static void AssertIsLite(this IFieldReference fr) + { + if (!fr.IsLite) + throw new InvalidOperationException("The field is not a lite"); + } - static PrimaryKey? GetIdForLite(Lite lite, Forbidden forbidden) - { - if (lite == null) - return null; + public static Expression GetIdFactory(this IFieldReference fr, Expression value, Expression forbidden) + { + var mi = !fr.IsLite ? miGetIdForEntity : + fr.ClearEntityOnSaving ? miGetIdForLiteCleanEntity : + miGetIdForLite; - if (lite.EntityOrNull == null) - return lite.Id; + return Expression.Call(mi, value, forbidden); + } - if (forbidden.Contains((Entity)lite.EntityOrNull)) - return null; + static PrimaryKey? GetIdForLite(Lite lite, Forbidden forbidden) + { + if (lite == null) + return null; - lite.RefreshId(); + if (lite.EntityOrNull == null) + return lite.Id; - return lite.Id; - } + if (forbidden.Contains((Entity)lite.EntityOrNull)) + return null; - static PrimaryKey? GetIdForLiteCleanEntity(Lite lite, Forbidden forbidden) - { - if (lite == null) - return null; + lite.RefreshId(); - if (lite.EntityOrNull == null) return lite.Id; + } - if (forbidden.Contains((Entity)lite.EntityOrNull)) - return null; - - lite.RefreshId(); - lite.ClearEntity(); + static PrimaryKey? GetIdForLiteCleanEntity(Lite lite, Forbidden forbidden) + { + if (lite == null) + return null; - return lite.Id; - } + if (lite.EntityOrNull == null) + return lite.Id; - static PrimaryKey? GetIdForEntity(IEntity value, Forbidden forbidden) - { - if (value == null) - return null; + if (forbidden.Contains((Entity)lite.EntityOrNull)) + return null; - Entity ie = (Entity)value; - return forbidden.Contains(ie) ? (PrimaryKey?)null : ie.Id; - } + lite.RefreshId(); + lite.ClearEntity(); - static MethodInfo miGetTypeForLite = ReflectionTools.GetMethodInfo(() => GetTypeForLite(null!, new Forbidden())); - static MethodInfo miGetTypeForEntity = ReflectionTools.GetMethodInfo(() => GetTypeForEntity(null!, new Forbidden())); + return lite.Id; + } - public static Expression GetTypeFactory(this IFieldReference fr, Expression value, Expression forbidden) - { - return Expression.Call(fr.IsLite ? miGetTypeForLite : miGetTypeForEntity, value, forbidden); - } + static PrimaryKey? GetIdForEntity(IEntity value, Forbidden forbidden) + { + if (value == null) + return null; - static Type? GetTypeForLite(Lite value, Forbidden forbidden) - { - if (value == null) - return null; + Entity ie = (Entity)value; + return forbidden.Contains(ie) ? (PrimaryKey?)null : ie.Id; + } - Lite l = (Lite)value; - return l.EntityOrNull == null ? l.EntityType : - forbidden.Contains((Entity)l.EntityOrNull) ? null : - l.EntityType; - } + static MethodInfo miGetTypeForLite = ReflectionTools.GetMethodInfo(() => GetTypeForLite(null!, new Forbidden())); + static MethodInfo miGetTypeForEntity = ReflectionTools.GetMethodInfo(() => GetTypeForEntity(null!, new Forbidden())); - static Type? GetTypeForEntity(IEntity value, Forbidden forbidden) - { - if (value == null) - return null; + public static Expression GetTypeFactory(this IFieldReference fr, Expression value, Expression forbidden) + { + return Expression.Call(fr.IsLite ? miGetTypeForLite : miGetTypeForEntity, value, forbidden); + } - Entity ie = (Entity)value; - return forbidden.Contains(ie) ? null : ie.GetType(); - } -} + static Type? GetTypeForLite(Lite value, Forbidden forbidden) + { + if (value == null) + return null; -public partial class FieldReference -{ - protected internal override void CreateParameter(List trios, List assigments, Expression value, Expression forbidden, Expression suffix) - { - trios.Add(new Table.Trio(this, Expression.Call(miUnWrap, this.GetIdFactory(value, forbidden)), suffix)); - } + Lite l = (Lite)value; + return l.EntityOrNull == null ? l.EntityType : + forbidden.Contains((Entity)l.EntityOrNull) ? null : + l.EntityType; + } - static MethodInfo miUnWrap = ReflectionTools.GetMethodInfo(() => Signum.Entities.PrimaryKey.Unwrap(null)); -} + static Type? GetTypeForEntity(IEntity value, Forbidden forbidden) + { + if (value == null) + return null; -public partial class FieldEnum -{ - protected internal override void CreateParameter(List trios, List assigments, Expression value, Expression forbidden, Expression suffix) - { - trios.Add(new Table.Trio(this, Expression.Convert(value, this.Type), suffix)); + Entity ie = (Entity)value; + return forbidden.Contains(ie) ? null : ie.GetType(); + } } -} - - -public partial class FieldImplementedBy -{ - protected internal override void CreateParameter(List trios, List assigments, Expression value, Expression forbidden, Expression suffix) + public partial class FieldReference { - ParameterExpression ibType = Expression.Parameter(typeof(Type), "ibType"); - ParameterExpression ibId = Expression.Parameter(typeof(PrimaryKey?), "ibId"); + protected internal override void CreateParameter(List trios, List assigments, Expression value, Expression forbidden, Expression suffix) + { + trios.Add(new Table.Trio(this, Expression.Call(miUnWrap, this.GetIdFactory(value, forbidden)), suffix)); + } - assigments.Add(Expression.Assign(ibType, Expression.Call(Expression.Constant(this), miCheckType, this.GetTypeFactory(value, forbidden)))); - assigments.Add(Expression.Assign(ibId, this.GetIdFactory(value, forbidden))); + static MethodInfo miUnWrap = ReflectionTools.GetMethodInfo(() => Signum.Entities.PrimaryKey.Unwrap(null)); + } - foreach (var imp in ImplementationColumns) + public partial class FieldEnum + { + protected internal override void CreateParameter(List trios, List assigments, Expression value, Expression forbidden, Expression suffix) { - trios.Add(new Table.Trio(imp.Value, - Expression.Condition(Expression.Equal(ibType, Expression.Constant(imp.Key)), - Expression.Field(Expression.Property(ibId, "Value"), "Object"), - Expression.Constant(null, typeof(IComparable))), - suffix)); + trios.Add(new Table.Trio(this, Expression.Convert(value, this.Type), suffix)); } } - static readonly MethodInfo miCheckType = ReflectionTools.GetMethodInfo((FieldImplementedBy fe) => fe.CheckType(null!)); - Type? CheckType(Type? type) + + public partial class FieldImplementedBy { - if (type != null && !ImplementationColumns.ContainsKey(type)) - throw new InvalidOperationException($"Type {type.Name} is not in the list of ImplementedBy of {Route}, currently types allowed: {ImplementationColumns.Keys.ToString(a => a.Name, ", ")}.\r\n" + - $"Consider writing in your Starter class something like: sb.Schema.Settings.FieldAttributes(({Route.RootType.Name} e) => e.{Route.PropertyString().Replace("/", ".First().")}).Replace(new ImplementedByAttribute({ImplementationColumns.Keys.And(type).ToString(t => $"typeof({t.Name})", ", ")}));"); + protected internal override void CreateParameter(List trios, List assigments, Expression value, Expression forbidden, Expression suffix) + { + ParameterExpression ibType = Expression.Parameter(typeof(Type), "ibType"); + ParameterExpression ibId = Expression.Parameter(typeof(PrimaryKey?), "ibId"); - return type; - } -} + assigments.Add(Expression.Assign(ibType, Expression.Call(Expression.Constant(this), miCheckType, this.GetTypeFactory(value, forbidden)))); + assigments.Add(Expression.Assign(ibId, this.GetIdFactory(value, forbidden))); -public partial class ImplementationColumn -{ + foreach (var imp in ImplementationColumns) + { + trios.Add(new Table.Trio(imp.Value, + Expression.Condition(Expression.Equal(ibType, Expression.Constant(imp.Key)), + Expression.Field(Expression.Property(ibId, "Value"), "Object"), + Expression.Constant(null, typeof(IComparable))), + suffix)); + } + } -} + static readonly MethodInfo miCheckType = ReflectionTools.GetMethodInfo((FieldImplementedBy fe) => fe.CheckType(null!)); -public partial class FieldImplementedByAll -{ + Type? CheckType(Type? type) + { + if (type != null && !ImplementationColumns.ContainsKey(type)) + throw new InvalidOperationException($"Type {type.Name} is not in the list of ImplementedBy of {Route}, currently types allowed: {ImplementationColumns.Keys.ToString(a => a.Name, ", ")}.\r\n" + + $"Consider writing in your Starter class something like: sb.Schema.Settings.FieldAttributes(({Route.RootType.Name} e) => e.{Route.PropertyString().Replace("/", ".First().")}).Replace(new ImplementedByAttribute({ImplementationColumns.Keys.And(type).ToString(t => $"typeof({t.Name})", ", ")}));"); - protected internal override void CreateParameter(List trios, List assigments, Expression value, Expression forbidden, Expression suffix) - { - trios.Add(new Table.Trio(Column, Expression.Call(miUnWrapToString, this.GetIdFactory(value, forbidden)), suffix)); - trios.Add(new Table.Trio(ColumnType, Expression.Call(miConvertType, this.GetTypeFactory(value, forbidden)), suffix)); + return type; + } } - static MethodInfo miUnWrapToString = ReflectionTools.GetMethodInfo(() => PrimaryKey.UnwrapToString(null)); - static MethodInfo miConvertType = ReflectionTools.GetMethodInfo(() => ConvertType(null!)); - - static IComparable? ConvertType(Type type) + public partial class ImplementationColumn { - if (type == null) - return null; - return TypeLogic.TypeToId.GetOrThrow(type, "{0} not registered in the schema").Object; } -} -public partial class FieldMList -{ -} - -public partial class FieldEmbedded -{ - protected internal override void CreateParameter(List trios, List assigments, Expression value, Expression forbidden, Expression suffix) + public partial class FieldImplementedByAll { - ParameterExpression embedded = Expression.Parameter(this.FieldType, "embedded"); - if (HasValue != null) + protected internal override void CreateParameter(List trios, List assigments, Expression value, Expression forbidden, Expression suffix) { - trios.Add(new Table.Trio(HasValue, Expression.NotEqual(value, Expression.Constant(null, FieldType)), suffix)); + trios.Add(new Table.Trio(Column, Expression.Call(miUnWrapToString, this.GetIdFactory(value, forbidden)), suffix)); + trios.Add(new Table.Trio(ColumnType, Expression.Call(miConvertType, this.GetTypeFactory(value, forbidden)), suffix)); } - assigments.Add(Expression.Assign(embedded, Expression.Convert(value, this.FieldType))); + static MethodInfo miUnWrapToString = ReflectionTools.GetMethodInfo(() => PrimaryKey.UnwrapToString(null)); + static MethodInfo miConvertType = ReflectionTools.GetMethodInfo(() => ConvertType(null!)); - foreach (var ef in EmbeddedFields.Values) + static IComparable? ConvertType(Type type) { - ef.Field.CreateParameter(trios, assigments, - Expression.Condition( - Expression.Equal(embedded, Expression.Constant(null, this.FieldType)), - Expression.Constant(null, ef.FieldInfo.FieldType.Nullify()), - Expression.Field(embedded, ef.FieldInfo).Nullify()), forbidden, suffix); + if (type == null) + return null; + + return TypeLogic.TypeToId.GetOrThrow(type, "{0} not registered in the schema").Object; } + } + + public partial class FieldMList + { + } - if(Mixins != null) + public partial class FieldEmbedded + { + protected internal override void CreateParameter(List trios, List assigments, Expression value, Expression forbidden, Expression suffix) { - foreach (var mi in Mixins) + ParameterExpression embedded = Expression.Parameter(this.FieldType, "embedded"); + + if (HasValue != null) + { + trios.Add(new Table.Trio(HasValue, Expression.NotEqual(value, Expression.Constant(null, FieldType)), suffix)); + } + + assigments.Add(Expression.Assign(embedded, Expression.Convert(value, this.FieldType))); + + foreach (var ef in EmbeddedFields.Values) + { + ef.Field.CreateParameter(trios, assigments, + Expression.Condition( + Expression.Equal(embedded, Expression.Constant(null, this.FieldType)), + Expression.Constant(null, ef.FieldInfo.FieldType.Nullify()), + Expression.Field(embedded, ef.FieldInfo).Nullify()), forbidden, suffix); + } + + if(Mixins != null) { - mi.Value.CreateParameter(trios, assigments, embedded, forbidden, suffix); + foreach (var mi in Mixins) + { + mi.Value.CreateParameter(trios, assigments, embedded, forbidden, suffix); + } } } - } - static readonly MethodInfo miCheckNull = ReflectionTools.GetMethodInfo((FieldEmbedded fe) => fe.CheckNull(null!)); - object CheckNull(object obj) - { - if (obj == null) - throw new InvalidOperationException("Impossible to save 'null' on the not-nullable embedded field of type '{0}'".FormatWith(this.FieldType.Name)); + static readonly MethodInfo miCheckNull = ReflectionTools.GetMethodInfo((FieldEmbedded fe) => fe.CheckNull(null!)); + object CheckNull(object obj) + { + if (obj == null) + throw new InvalidOperationException("Impossible to save 'null' on the not-nullable embedded field of type '{0}'".FormatWith(this.FieldType.Name)); - return obj; + return obj; + } } -} -public partial class FieldMixin -{ - protected internal override void CreateParameter(List trios, List assigments, Expression value, Expression forbidden, Expression suffix) + public partial class FieldMixin { - ParameterExpression mixin = Expression.Parameter(this.FieldType, "mixin"); - - assigments.Add(Expression.Assign(mixin, Expression.Call(value, MixinDeclarations.miMixin.MakeGenericMethod(this.FieldType)))); - foreach (var ef in Fields.Values) + protected internal override void CreateParameter(List trios, List assigments, Expression value, Expression forbidden, Expression suffix) { - ef.Field.CreateParameter(trios, assigments, - Expression.Field(mixin, ef.FieldInfo), forbidden, suffix); + ParameterExpression mixin = Expression.Parameter(this.FieldType, "mixin"); + + assigments.Add(Expression.Assign(mixin, Expression.Call(value, MixinDeclarations.miMixin.MakeGenericMethod(this.FieldType)))); + foreach (var ef in Fields.Values) + { + ef.Field.CreateParameter(trios, assigments, + Expression.Field(mixin, ef.FieldInfo), forbidden, suffix); + } } } -} diff --git a/Signum.Engine/Schema/SchemaBuilder/SchemaBuilder.cs b/Signum.Engine/Schema/SchemaBuilder/SchemaBuilder.cs index 77005a37c3..9a0be51885 100644 --- a/Signum.Engine/Schema/SchemaBuilder/SchemaBuilder.cs +++ b/Signum.Engine/Schema/SchemaBuilder/SchemaBuilder.cs @@ -6,1042 +6,1045 @@ namespace Signum.Engine.Maps; -public class SchemaBuilder -{ - Schema schema; - public SchemaSettings Settings + public class SchemaBuilder { - get { return schema.Settings; } - } - - public SchemaBuilder(bool isDefault) - { - schema = new Schema(new SchemaSettings()); - - if (isDefault) + Schema schema; + public SchemaSettings Settings { - if (TypeEntity.AlreadySet) - throw new InvalidOperationException("Only one default SchemaBuilder per application allowed"); - - TypeEntity.SetTypeNameCallbacks( - t => schema.TypeToName.GetOrThrow(t, "Type {0} not found in the schema"), - cleanName => schema.NameToType.TryGetC(cleanName)); - - FromEnumMethodExpander.miQuery = ReflectionTools.GetMethodInfo(() => Database.Query()).GetGenericMethodDefinition(); + get { return schema.Settings; } } - Settings.AssertNotIncluded = MixinDeclarations.AssertNotIncluded = t => + public SchemaBuilder(bool isDefault) { - if (schema.Tables.ContainsKey(t)) - throw new InvalidOperationException("{0} is already included in the Schema".FormatWith(t.TypeName())); - }; - } + schema = new Schema(new SchemaSettings()); + if (isDefault) + { + if (TypeEntity.AlreadySet) + throw new InvalidOperationException("Only one default SchemaBuilder per application allowed"); - protected SchemaBuilder(Schema schema) - { - this.schema = schema; - } + TypeEntity.SetTypeNameCallbacks( + t => schema.TypeToName.GetOrThrow(t, "Type {0} not found in the schema"), + cleanName => schema.NameToType.TryGetC(cleanName)); - public SchemaBuilder(SchemaSettings settings) - { - schema = new Schema(settings); - } + FromEnumMethodExpander.miQuery = ReflectionTools.GetMethodInfo(() => Database.Query()).GetGenericMethodDefinition(); + } - public Schema Schema - { - get { return schema; } - } + Settings.AssertNotIncluded = MixinDeclarations.AssertNotIncluded = t => + { + if (schema.Tables.ContainsKey(t)) + throw new InvalidOperationException("{0} is already included in the Schema".FormatWith(t.TypeName())); + }; + } - public UniqueTableIndex AddUniqueIndex(Expression> fields, Expression>? where = null, Expression>? includeFields = null) where T : Entity - { - var table = Schema.Table(); + protected SchemaBuilder(Schema schema) + { + this.schema = schema; + } - IColumn[] columns = IndexKeyColumns.Split(table, fields); + public SchemaBuilder(SchemaSettings settings) + { + schema = new Schema(settings); + } - var index = AddUniqueIndex(table, columns); + public Schema Schema + { + get { return schema; } + } - if (where != null) - index.Where = IndexWhereExpressionVisitor.GetIndexWhere(where, table); - if (includeFields != null) + public UniqueTableIndex AddUniqueIndex(Expression> fields, Expression>? where = null, Expression>? includeFields = null) where T : Entity { - index.IncludeColumns = IndexKeyColumns.Split(table, includeFields); - if (table.SystemVersioned != null) + var table = Schema.Table(); + + IColumn[] columns = IndexKeyColumns.Split(table, fields); + + var index = AddUniqueIndex(table, columns); + + if (where != null) + index.Where = IndexWhereExpressionVisitor.GetIndexWhere(where, table); + + if (includeFields != null) { - index.IncludeColumns = index.IncludeColumns.Concat(table.SystemVersioned.Columns()).ToArray(); + index.IncludeColumns = IndexKeyColumns.Split(table, includeFields); + if (table.SystemVersioned != null) + { + index.IncludeColumns = index.IncludeColumns.Concat(table.SystemVersioned.Columns()).ToArray(); + } } - } - return index; - } + return index; + } - public TableIndex AddIndex(Expression> fields, - Expression>? where = null, - Expression>? includeFields = null) where T : Entity - { - var table = Schema.Table(); + public TableIndex AddIndex(Expression> fields, + Expression>? where = null, + Expression>? includeFields = null) where T : Entity + { + var table = Schema.Table(); - IColumn[] columns = IndexKeyColumns.Split(table, fields); + IColumn[] columns = IndexKeyColumns.Split(table, fields); - var index = new TableIndex(table, columns); + var index = new TableIndex(table, columns); - if (where != null) - index.Where = IndexWhereExpressionVisitor.GetIndexWhere(where, table); + if (where != null) + index.Where = IndexWhereExpressionVisitor.GetIndexWhere(where, table); - if (includeFields != null) - { - index.IncludeColumns = IndexKeyColumns.Split(table, includeFields); - if (table.SystemVersioned != null) + if (includeFields != null) { - index.IncludeColumns = index.IncludeColumns.Concat(table.SystemVersioned.Columns()).ToArray(); + index.IncludeColumns = IndexKeyColumns.Split(table, includeFields); + if (table.SystemVersioned != null) + { + index.IncludeColumns = index.IncludeColumns.Concat(table.SystemVersioned.Columns()).ToArray(); + } } - } - AddIndex(index); + AddIndex(index); - return index; - } + return index; + } - public UniqueTableIndex AddUniqueIndexMList(Expression>> toMList, - Expression, object>> fields, - Expression, bool>>? where = null, - Expression, object>>? includeFields = null) - where T : Entity - { - TableMList table = ((FieldMList)Schema.FindField(Schema.Table(typeof(T)), Reflector.GetMemberList(toMList))).TableMList; + public UniqueTableIndex AddUniqueIndexMList(Expression>> toMList, + Expression, object>> fields, + Expression, bool>>? where = null, + Expression, object>>? includeFields = null) + where T : Entity + { + TableMList table = ((FieldMList)Schema.FindField(Schema.Table(typeof(T)), Reflector.GetMemberList(toMList))).TableMList; - IColumn[] columns = IndexKeyColumns.Split(table, fields); + IColumn[] columns = IndexKeyColumns.Split(table, fields); - var index = AddUniqueIndex(table, columns); + var index = AddUniqueIndex(table, columns); - if (where != null) - index.Where = IndexWhereExpressionVisitor.GetIndexWhere(where, table); + if (where != null) + index.Where = IndexWhereExpressionVisitor.GetIndexWhere(where, table); - if (includeFields != null) - { - index.IncludeColumns = IndexKeyColumns.Split(table, includeFields); - if (table.SystemVersioned != null) + if (includeFields != null) { - index.IncludeColumns = index.IncludeColumns.Concat(table.SystemVersioned.Columns()).ToArray(); + index.IncludeColumns = IndexKeyColumns.Split(table, includeFields); + if (table.SystemVersioned != null) + { + index.IncludeColumns = index.IncludeColumns.Concat(table.SystemVersioned.Columns()).ToArray(); + } } - } - return index; - } + return index; + } - public TableIndex AddIndexMList(Expression>> toMList, - Expression, object>> fields, - Expression, bool>>? where = null, - Expression, object>>? includeFields = null) - where T : Entity - { - TableMList table = ((FieldMList)Schema.FindField(Schema.Table(typeof(T)), Reflector.GetMemberList(toMList))).TableMList; + public TableIndex AddIndexMList(Expression>> toMList, + Expression, object>> fields, + Expression, bool>>? where = null, + Expression, object>>? includeFields = null) + where T : Entity + { + TableMList table = ((FieldMList)Schema.FindField(Schema.Table(typeof(T)), Reflector.GetMemberList(toMList))).TableMList; - IColumn[] columns = IndexKeyColumns.Split(table, fields); + IColumn[] columns = IndexKeyColumns.Split(table, fields); - var index = AddIndex(table, columns); + var index = AddIndex(table, columns); - if (where != null) - index.Where = IndexWhereExpressionVisitor.GetIndexWhere(where, table); + if (where != null) + index.Where = IndexWhereExpressionVisitor.GetIndexWhere(where, table); - if (includeFields != null) - { - index.IncludeColumns = IndexKeyColumns.Split(table, includeFields); - if (table.SystemVersioned != null) + if (includeFields != null) { - index.IncludeColumns = index.IncludeColumns.Concat(table.SystemVersioned.Columns()).ToArray(); + index.IncludeColumns = IndexKeyColumns.Split(table, includeFields); + if (table.SystemVersioned != null) + { + index.IncludeColumns = index.IncludeColumns.Concat(table.SystemVersioned.Columns()).ToArray(); + } } - } - return index; - } + return index; + } - public UniqueTableIndex AddUniqueIndex(ITable table, Field[] fields) - { - var index = new UniqueTableIndex(table, TableIndex.GetColumnsFromFields(fields)); - AddIndex(index); - return index; - } + public UniqueTableIndex AddUniqueIndex(ITable table, Field[] fields) + { + var index = new UniqueTableIndex(table, TableIndex.GetColumnsFromFields(fields)); + AddIndex(index); + return index; + } - public UniqueTableIndex AddUniqueIndex(ITable table, IColumn[] columns) - { - var index = new UniqueTableIndex(table, columns); - AddIndex(index); - return index; - } + public UniqueTableIndex AddUniqueIndex(ITable table, IColumn[] columns) + { + var index = new UniqueTableIndex(table, columns); + AddIndex(index); + return index; + } - public TableIndex AddIndex(ITable table, Field[] fields) - { - var index = new TableIndex(table, TableIndex.GetColumnsFromFields(fields)); - AddIndex(index); - return index; - } + public TableIndex AddIndex(ITable table, Field[] fields) + { + var index = new TableIndex(table, TableIndex.GetColumnsFromFields(fields)); + AddIndex(index); + return index; + } - public TableIndex AddIndex(ITable table, IColumn[] columns) - { - var index = new TableIndex(table, columns); - AddIndex(index); - return index; - } + public TableIndex AddIndex(ITable table, IColumn[] columns) + { + var index = new TableIndex(table, columns); + AddIndex(index); + return index; + } - public void AddIndex(TableIndex index) - { - ITable table = index.Table; + public void AddIndex(TableIndex index) + { + ITable table = index.Table; - if (table.MultiColumnIndexes == null) - table.MultiColumnIndexes = new List(); + if (table.MultiColumnIndexes == null) + table.MultiColumnIndexes = new List(); - table.MultiColumnIndexes.Add(index); - } + table.MultiColumnIndexes.Add(index); + } - public FluentInclude Include() where T : Entity - { - var table = Include(typeof(T), null); - return new FluentInclude(table, this); - } + public FluentInclude Include() where T : Entity + { + var table = Include(typeof(T), null); + return new FluentInclude(table, this); + } - public virtual Table Include(Type type) - { - return Include(type, null); - } + public virtual Table Include(Type type) + { + return Include(type, null); + } - internal protected virtual Table Include(Type type, PropertyRoute? route) - { - if (schema.Tables.TryGetValue(type, out var result)) - return result; + internal protected virtual Table Include(Type type, PropertyRoute? route) + { + if (schema.Tables.TryGetValue(type, out var result)) + return result; - if (this.Schema.IsCompleted) //Below for nop includes of Views referencing lites or entities - throw new InvalidOperationException("Schema already completed"); + if (this.Schema.IsCompleted) //Below for nop includes of Views referencing lites or entities + throw new InvalidOperationException("Schema already completed"); - using (HeavyProfiler.LogNoStackTrace("Include", () => type.TypeName())) - { - if (type.IsAbstract) - throw new InvalidOperationException(ErrorIncluding(route) + $"Impossible to include in the Schema the type {type} because is abstract"); + using (HeavyProfiler.LogNoStackTrace("Include", () => type.TypeName())) + { + if (type.IsAbstract) + throw new InvalidOperationException(ErrorIncluding(route) + $"Impossible to include in the Schema the type {type} because is abstract"); - if (!Reflector.IsEntity(type)) - throw new InvalidOperationException(ErrorIncluding(route) + $"Impossible to include in the Schema the type {type} because is not and Entity"); + if (!Reflector.IsEntity(type)) + throw new InvalidOperationException(ErrorIncluding(route) + $"Impossible to include in the Schema the type {type} because is not and Entity"); - string cleanName = schema.Settings.desambiguatedNames?.TryGetC(type) ?? Reflector.CleanTypeName(EnumEntity.Extract(type) ?? type); + string cleanName = schema.Settings.desambiguatedNames?.TryGetC(type) ?? Reflector.CleanTypeName(EnumEntity.Extract(type) ?? type); - if (schema.NameToType.ContainsKey(cleanName)) - throw new InvalidOperationException(ErrorIncluding(route) + @$"Two types have the same cleanName '{cleanName}', desambiguate using Schema.Current.Settings.Desambiguate method: + if (schema.NameToType.ContainsKey(cleanName)) + throw new InvalidOperationException(ErrorIncluding(route) + @$"Two types have the same cleanName '{cleanName}', desambiguate using Schema.Current.Settings.Desambiguate method: {schema.NameToType[cleanName].FullName} {type.FullName}"); - try - { - result = new Table(type); + try + { + result = new Table(type); - schema.Tables.Add(type, result); - schema.NameToType[cleanName] = type; - schema.TypeToName[type] = cleanName; + schema.Tables.Add(type, result); + schema.NameToType[cleanName] = type; + schema.TypeToName[type] = cleanName; - Complete(result); + Complete(result); - return result; - } - catch (Exception) //Avoid half-cooked tables - { - schema.Tables.Remove(type); - schema.NameToType.Remove(cleanName); - schema.TypeToName.Remove(type); - throw; + return result; + } + catch (Exception) //Avoid half-cooked tables + { + schema.Tables.Remove(type); + schema.NameToType.Remove(cleanName); + schema.TypeToName.Remove(type); + throw; + } } } - } - private static string? ErrorIncluding(PropertyRoute? route) - { - return route?.Let(r => $"Error including {r}: "); - } + private static string? ErrorIncluding(PropertyRoute? route) + { + return route?.Let(r => $"Error including {r}: "); + } - void Complete(Table table) - { - using (HeavyProfiler.LogNoStackTrace("Complete", () => table.Type.Name)) - using (var tr = HeavyProfiler.LogNoStackTrace("GetPrimaryKeyAttribute", () => table.Type.Name)) + void Complete(Table table) { - Type type = table.Type; - - table.IdentityBehaviour = GetPrimaryKeyAttribute(type).IdentityBehaviour; - tr.Switch("GenerateTableName"); - table.Name = GenerateTableName(type, Settings.TypeAttribute(type)); - tr.Switch("GenerateCleanTypeName"); - table.CleanTypeName = GenerateCleanTypeName(type); - tr.Switch("GenerateFields"); - table.Fields = GenerateFields(PropertyRoute.Root(type), table, NameSequence.Void, forceNull: false, inMList: false); - tr.Switch("GenerateMixins"); - table.Mixins = GenerateMixins(PropertyRoute.Root(type), table, NameSequence.Void); - tr.Switch("GenerateTemporal"); - table.SystemVersioned = ToSystemVersionedInfo(Settings.TypeAttribute(type), table.Name); - tr.Switch("GenerateColumns"); - table.GenerateColumns(); + using (HeavyProfiler.LogNoStackTrace("Complete", () => table.Type.Name)) + using (var tr = HeavyProfiler.LogNoStackTrace("GetPrimaryKeyAttribute", () => table.Type.Name)) + { + Type type = table.Type; + + table.IdentityBehaviour = GetPrimaryKeyAttribute(type).IdentityBehaviour; + tr.Switch("GenerateTableName"); + table.Name = GenerateTableName(type, Settings.TypeAttribute(type)); + tr.Switch("GenerateCleanTypeName"); + table.CleanTypeName = GenerateCleanTypeName(type); + tr.Switch("GenerateFields"); + table.Fields = GenerateFields(PropertyRoute.Root(type), table, NameSequence.Void, forceNull: false, inMList: false); + tr.Switch("GenerateMixins"); + table.Mixins = GenerateMixins(PropertyRoute.Root(type), table, NameSequence.Void); + tr.Switch("GenerateTemporal"); + table.SystemVersioned = ToSystemVersionedInfo(Settings.TypeAttribute(type), table.Name); + tr.Switch("GenerateColumns"); + table.GenerateColumns(); + } } - } - public SystemVersionedInfo? ToSystemVersionedInfo(SystemVersionedAttribute? att, ObjectName tableName) - { - if (att == null) - return null; + public SystemVersionedInfo? ToSystemVersionedInfo(SystemVersionedAttribute? att, ObjectName tableName) + { + if (att == null) + return null; - var isPostgres = this.schema.Settings.IsPostgres; + var isPostgres = this.schema.Settings.IsPostgres; - var tn = att.TemporalTableName != null ? ObjectName.Parse(att.TemporalTableName, isPostgres) : - new ObjectName(tableName.Schema, tableName.Name + "_History", isPostgres); + var tn = att.TemporalTableName != null ? ObjectName.Parse(att.TemporalTableName, isPostgres) : + new ObjectName(tableName.Schema, tableName.Name + "_History", isPostgres); - if (isPostgres) - return new SystemVersionedInfo(tn, att.PostgreeSysPeriodColumname); + if (isPostgres) + return new SystemVersionedInfo(tn, att.PostgreeSysPeriodColumname); - return new SystemVersionedInfo(tn, att.StartDateColumnName, att.EndDateColumnName); - } + return new SystemVersionedInfo(tn, att.StartDateColumnName, att.EndDateColumnName); + } - private Dictionary? GenerateMixins(PropertyRoute propertyRoute, ITable table, NameSequence nameSequence) - { - Dictionary? mixins = null; - foreach (var t in MixinDeclarations.GetMixinDeclarations(propertyRoute.Type)) + private Dictionary? GenerateMixins(PropertyRoute propertyRoute, ITable table, NameSequence nameSequence) { - if (mixins == null) - mixins = new Dictionary(); + Dictionary? mixins = null; + foreach (var t in MixinDeclarations.GetMixinDeclarations(propertyRoute.Type)) + { + if (mixins == null) + mixins = new Dictionary(); - mixins.Add(t, this.GenerateFieldMixin(propertyRoute.Add(t), nameSequence, table)); + mixins.Add(t, this.GenerateFieldMixin(propertyRoute.Add(t), nameSequence, table)); + } + + return mixins; } - return mixins; - } + public HeavyProfiler.Tracer? Tracer { get; set; } - public HeavyProfiler.Tracer? Tracer { get; set; } + public HashSet<(Type type, string method)> LoadedModules = new HashSet<(Type type, string method)>(); + public bool NotDefined(MethodBase? methodBase) + { + this.Tracer.Switch(methodBase!.DeclaringType!.Name); - public HashSet<(Type type, string method)> LoadedModules = new HashSet<(Type type, string method)>(); - public bool NotDefined(MethodBase? methodBase) - { - this.Tracer.Switch(methodBase!.DeclaringType!.Name); + var methods = methodBase.DeclaringType.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) + .Where(m => !m.HasAttribute()) + .Select(m => m.GetCustomAttribute()?.Name) + .NotNull() + .ToHashSet(); - var methods = methodBase.DeclaringType.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) - .Where(m => !m.HasAttribute()) - .Select(m => m.GetCustomAttribute()?.Name) - .NotNull() - .ToHashSet(); + var fields = methodBase.DeclaringType.GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) + .Where(f => f.Name.EndsWith("Expression") && f.FieldType.IsInstantiationOf(typeof(Expression<>))); - var fields = methodBase.DeclaringType.GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) - .Where(f => f.Name.EndsWith("Expression") && f.FieldType.IsInstantiationOf(typeof(Expression<>))); + foreach (var f in fields) + { + if (!methods.Contains(f.Name)) + throw new InvalidOperationException($"No Method found for expression '{f.Name}' in '{methodBase.DeclaringType.Name}'"); + } - foreach (var f in fields) - { - if (!methods.Contains(f.Name)) - throw new InvalidOperationException($"No Method found for expression '{f.Name}' in '{methodBase.DeclaringType.Name}'"); + return LoadedModules.Add((type: methodBase.DeclaringType, method: methodBase.Name)); } - return LoadedModules.Add((type: methodBase.DeclaringType, method: methodBase.Name)); - } - - public void AssertDefined(MethodBase methodBase) - { - var tulpe = (methodBase.DeclaringType!, methodBase.Name); + public void AssertDefined(MethodBase methodBase) + { + var tulpe = (methodBase.DeclaringType!, methodBase.Name); - if (!LoadedModules.Contains(tulpe)) - throw new ApplicationException("Call {0} first".FormatWith(tulpe)); - } + if (!LoadedModules.Contains(tulpe)) + throw new ApplicationException("Call {0} first".FormatWith(tulpe)); + } - #region Field Generator + #region Field Generator - protected Dictionary GenerateFields(PropertyRoute root, ITable table, NameSequence preName, bool forceNull, bool inMList) - { - using (HeavyProfiler.LogNoStackTrace("SB.GenerateFields", () => root.ToString())) + protected Dictionary GenerateFields(PropertyRoute root, ITable table, NameSequence preName, bool forceNull, bool inMList) { - Dictionary result = new Dictionary(); - var type = root.Type; - - if (type.IsEntity()) + using (HeavyProfiler.LogNoStackTrace("SB.GenerateFields", () => root.ToString())) { + Dictionary result = new Dictionary(); + var type = root.Type; + + if (type.IsEntity()) { - PropertyRoute route = root.Add(fiId); + { + PropertyRoute route = root.Add(fiId); - Field field = GenerateField(table, route, preName, forceNull, inMList); + Field field = GenerateField(table, route, preName, forceNull, inMList); - result.Add(fiId.Name, new EntityField(type, fiId, field)); - } + result.Add(fiId.Name, new EntityField(type, fiId, field)); + } - TicksColumnAttribute? t = type.GetCustomAttribute(); - if (t == null || t.HasTicks) - { - PropertyRoute route = root.Add(fiTicks); + TicksColumnAttribute? t = type.GetCustomAttribute(); + if (t == null || t.HasTicks) + { + PropertyRoute route = root.Add(fiTicks); - Field field = GenerateField(table, route, preName, forceNull, inMList); + Field field = GenerateField(table, route, preName, forceNull, inMList); - result.Add(fiTicks.Name, new EntityField(type, fiTicks, field)); - } + result.Add(fiTicks.Name, new EntityField(type, fiTicks, field)); + } - Expression? exp = ExpressionCleaner.GetFieldExpansion(type, EntityExpression.ToStringMethod); - if (exp == null) - { - PropertyRoute route = root.Add(fiToStr); + Expression? exp = ExpressionCleaner.GetFieldExpansion(type, EntityExpression.ToStringMethod); + if (exp == null) + { + PropertyRoute route = root.Add(fiToStr); - Field field = GenerateField(table, route, preName, forceNull, inMList); + Field field = GenerateField(table, route, preName, forceNull, inMList); - if (result.ContainsKey(fiToStr.Name)) - throw new InvalidOperationException("Duplicated field with name {0} on {1}, shadowing not supported".FormatWith(fiToStr.Name, type.TypeName())); + if (result.ContainsKey(fiToStr.Name)) + throw new InvalidOperationException("Duplicated field with name {0} on {1}, shadowing not supported".FormatWith(fiToStr.Name, type.TypeName())); - result.Add(fiToStr.Name, new EntityField(type, fiToStr, field)); + result.Add(fiToStr.Name, new EntityField(type, fiToStr, field)); + } } - } - - foreach (FieldInfo fi in Reflector.InstanceFieldsInOrder(type)) - { - PropertyRoute route = root.Add(fi); - if (Settings.FieldAttribute(route) == null) + foreach (FieldInfo fi in Reflector.InstanceFieldsInOrder(type)) { - if (Reflector.TryFindPropertyInfo(fi) == null && !fi.IsPublic && !fi.HasAttribute()) - throw new InvalidOperationException("Field '{0}' of type '{1}' has no property".FormatWith(fi.Name, type.Name)); + PropertyRoute route = root.Add(fi); - Field field = GenerateField(table, route, preName, forceNull, inMList); + if (Settings.FieldAttribute(route) == null) + { + if (Reflector.TryFindPropertyInfo(fi) == null && !fi.IsPublic && !fi.HasAttribute()) + throw new InvalidOperationException("Field '{0}' of type '{1}' has no property".FormatWith(fi.Name, type.Name)); + + Field field = GenerateField(table, route, preName, forceNull, inMList); - if (result.ContainsKey(fi.Name)) - throw new InvalidOperationException("Duplicated field with name '{0}' on '{1}', shadowing not supported".FormatWith(fi.Name, type.TypeName())); + if (result.ContainsKey(fi.Name)) + throw new InvalidOperationException("Duplicated field with name '{0}' on '{1}', shadowing not supported".FormatWith(fi.Name, type.TypeName())); - var ef = new EntityField(type, fi, field); + var ef = new EntityField(type, fi, field); - if (field is FieldMList fml) - fml.TableMList.PropertyRoute = route; + if (field is FieldMList fml) + fml.TableMList.PropertyRoute = route; - result.Add(fi.Name, ef); + result.Add(fi.Name, ef); + } } - } - return result; + return result; + } } - } - static readonly FieldInfo fiToStr = ReflectionTools.GetFieldInfo((Entity o) => o.toStr); - static readonly FieldInfo fiTicks = ReflectionTools.GetFieldInfo((Entity o) => o.ticks); - static readonly FieldInfo fiId = ReflectionTools.GetFieldInfo((Entity o) => o.id); + static readonly FieldInfo fiToStr = ReflectionTools.GetFieldInfo((Entity o) => o.toStr); + static readonly FieldInfo fiTicks = ReflectionTools.GetFieldInfo((Entity o) => o.ticks); + static readonly FieldInfo fiId = ReflectionTools.GetFieldInfo((Entity o) => o.id); - protected virtual Field GenerateField(ITable table, PropertyRoute route, NameSequence preName, bool forceNull, bool inMList) - { - using (HeavyProfiler.LogNoStackTrace("GenerateField", () => route.ToString())) + protected virtual Field GenerateField(ITable table, PropertyRoute route, NameSequence preName, bool forceNull, bool inMList) { - KindOfField kof = GetKindOfField(route); - - if (kof == KindOfField.MList && inMList) - throw new InvalidOperationException("Field {0} of type {1} can not be nested in another MList".FormatWith(route, route.Type.TypeName(), kof)); - - //field name generation - NameSequence name; - ColumnNameAttribute? vc = Settings.FieldAttribute(route); - if (vc != null && vc.Name.HasText()) - name = NameSequence.Void.Add(vc.Name); - else if (route.PropertyRouteType != PropertyRouteType.MListItems) - name = preName.Add(GenerateFieldName(route, kof)); - else if (kof == KindOfField.Enum || kof == KindOfField.Reference) - name = preName.Add(GenerateMListFieldName(route, kof)); - else - name = preName; - - switch (kof) + using (HeavyProfiler.LogNoStackTrace("GenerateField", () => route.ToString())) { - case KindOfField.PrimaryKey: - return GenerateFieldPrimaryKey((Table)table, route, name); - case KindOfField.Ticks: - return GenerateFieldTicks((Table)table, route, name); - case KindOfField.Value: - return GenerateFieldValue(table, route, name, forceNull); - case KindOfField.Reference: - { - Implementations at = Settings.GetImplementations(route); - if (at.IsByAll) - return GenerateFieldImplementedByAll(route, table, name, forceNull); - else if (at.Types.Only() == route.Type.CleanType()) - return GenerateFieldReference(table, route, name, forceNull); - else - return GenerateFieldImplementedBy(table, route, name, forceNull, at.Types); - } - case KindOfField.Enum: - return GenerateFieldEnum(table, route, name, forceNull); - case KindOfField.Embedded: - return GenerateFieldEmbedded(table, route, name, forceNull, inMList); - case KindOfField.MList: - return GenerateFieldMList((Table)table, route, name); - default: - throw new NotSupportedException(EngineMessage.NoWayOfMappingType0Found.NiceToString().FormatWith(route.Type)); + KindOfField kof = GetKindOfField(route); + + if (kof == KindOfField.MList && inMList) + throw new InvalidOperationException("Field {0} of type {1} can not be nested in another MList".FormatWith(route, route.Type.TypeName(), kof)); + + //field name generation + NameSequence name; + ColumnNameAttribute? vc = Settings.FieldAttribute(route); + if (vc != null && vc.Name.HasText()) + name = NameSequence.Void.Add(vc.Name); + else if (route.PropertyRouteType != PropertyRouteType.MListItems) + name = preName.Add(GenerateFieldName(route, kof)); + else if (kof == KindOfField.Enum || kof == KindOfField.Reference) + name = preName.Add(GenerateMListFieldName(route, kof)); + else + name = preName; + + switch (kof) + { + case KindOfField.PrimaryKey: + return GenerateFieldPrimaryKey((Table)table, route, name); + case KindOfField.Ticks: + return GenerateFieldTicks((Table)table, route, name); + case KindOfField.Value: + return GenerateFieldValue(table, route, name, forceNull); + case KindOfField.Reference: + { + Implementations at = Settings.GetImplementations(route); + if (at.IsByAll) + return GenerateFieldImplementedByAll(route, table, name, forceNull); + else if (at.Types.Only() == route.Type.CleanType()) + return GenerateFieldReference(table, route, name, forceNull); + else + return GenerateFieldImplementedBy(table, route, name, forceNull, at.Types); + } + case KindOfField.Enum: + return GenerateFieldEnum(table, route, name, forceNull); + case KindOfField.Embedded: + return GenerateFieldEmbedded(table, route, name, forceNull, inMList); + case KindOfField.MList: + return GenerateFieldMList((Table)table, route, name); + default: + throw new NotSupportedException(EngineMessage.NoWayOfMappingType0Found.NiceToString().FormatWith(route.Type)); + } } } - } - public enum KindOfField - { - PrimaryKey, - Ticks, - Value, - Reference, - Enum, - Embedded, - MList, - } + public enum KindOfField + { + PrimaryKey, + Ticks, + Value, + Reference, + Enum, + Embedded, + MList, + } - protected virtual KindOfField GetKindOfField(PropertyRoute route) - { - if (route.FieldInfo != null && ReflectionTools.FieldEquals(route.FieldInfo, fiId)) - return KindOfField.PrimaryKey; + protected virtual KindOfField GetKindOfField(PropertyRoute route) + { + if (route.FieldInfo != null && ReflectionTools.FieldEquals(route.FieldInfo, fiId)) + return KindOfField.PrimaryKey; - if (route.FieldInfo != null && ReflectionTools.FieldEquals(route.FieldInfo, fiTicks)) - return KindOfField.Ticks; + if (route.FieldInfo != null && ReflectionTools.FieldEquals(route.FieldInfo, fiTicks)) + return KindOfField.Ticks; - if (Settings.TryGetSqlDbType(Settings.FieldAttribute(route), route.Type) != null) - return KindOfField.Value; + if (Settings.TryGetSqlDbType(Settings.FieldAttribute(route), route.Type) != null) + return KindOfField.Value; - if (route.Type.UnNullify().IsEnum) - return KindOfField.Enum; + if (route.Type.UnNullify().IsEnum) + return KindOfField.Enum; - if (Reflector.IsIEntity(Lite.Extract(route.Type) ?? route.Type)) - return KindOfField.Reference; + if (Reflector.IsIEntity(Lite.Extract(route.Type) ?? route.Type)) + return KindOfField.Reference; - if (Reflector.IsEmbeddedEntity(route.Type)) - return KindOfField.Embedded; + if (Reflector.IsEmbeddedEntity(route.Type)) + return KindOfField.Embedded; - if (Reflector.IsMList(route.Type)) - return KindOfField.MList; + if (Reflector.IsMList(route.Type)) + return KindOfField.MList; - if (Settings.IsPostgres && route.Type.IsArray) - { - if (Settings.TryGetSqlDbType(Settings.FieldAttribute(route), route.Type.ElementType()!) != null) - return KindOfField.Value; + if (Settings.IsPostgres && route.Type.IsArray) + { + if (Settings.TryGetSqlDbType(Settings.FieldAttribute(route), route.Type.ElementType()!) != null) + return KindOfField.Value; + } + + throw new InvalidOperationException($"Field {route} of type {route.Type.Name} has no database representation"); } - throw new InvalidOperationException($"Field {route} of type {route.Type.Name} has no database representation"); - } + protected virtual Field GenerateFieldPrimaryKey(Table table, PropertyRoute route, NameSequence name) + { + var attr = GetPrimaryKeyAttribute(table.Type); - protected virtual Field GenerateFieldPrimaryKey(Table table, PropertyRoute route, NameSequence name) - { - var attr = GetPrimaryKeyAttribute(table.Type); + PrimaryKey.PrimaryKeyType.SetDefinition(table.Type, attr.Type); - PrimaryKey.PrimaryKeyType.SetDefinition(table.Type, attr.Type); + DbTypePair pair = Settings.GetSqlDbType(attr, attr.Type); - DbTypePair pair = Settings.GetSqlDbType(attr, attr.Type); + return table.PrimaryKey = new FieldPrimaryKey(route, table, attr.Name, attr.Type) + { + DbType = pair.DbType, + Collation = Settings.GetCollate(attr), + UserDefinedTypeName = pair.UserDefinedTypeName, + Default = attr.GetDefault(Settings.IsPostgres), + Identity = attr.Identity, + Size = attr.HasSize ? attr.Size : (int?)null, + }; + } - return table.PrimaryKey = new FieldPrimaryKey(route, table, attr.Name, attr.Type) + private PrimaryKeyAttribute GetPrimaryKeyAttribute(Type type) { - DbType = pair.DbType, - Collation = Settings.GetCollate(attr), - UserDefinedTypeName = pair.UserDefinedTypeName, - Default = attr.GetDefault(Settings.IsPostgres), - Identity = attr.Identity, - Size = attr.HasSize ? attr.Size : (int?)null, - }; - } + var attr = Settings.TypeAttribute(type); - private PrimaryKeyAttribute GetPrimaryKeyAttribute(Type type) - { - var attr = Settings.TypeAttribute(type); + if (attr != null) + return attr; - if (attr != null) - return attr; + if (type.IsEnumEntity()) + return new PrimaryKeyAttribute(Enum.GetUnderlyingType(type.GetGenericArguments().Single())) { Identity = false, IdentityBehaviour = false }; - if (type.IsEnumEntity()) - return new PrimaryKeyAttribute(Enum.GetUnderlyingType(type.GetGenericArguments().Single())) { Identity = false, IdentityBehaviour = false }; + return Settings.DefaultPrimaryKeyAttribute; + } - return Settings.DefaultPrimaryKeyAttribute; - } + protected virtual FieldValue GenerateFieldTicks(Table table, PropertyRoute route, NameSequence name) + { + var ticksAttr = Settings.TypeAttribute(table.Type); - protected virtual FieldValue GenerateFieldTicks(Table table, PropertyRoute route, NameSequence name) - { - var ticksAttr = Settings.TypeAttribute(table.Type); + if (ticksAttr != null && !ticksAttr.HasTicks) + throw new InvalidOperationException("HastTicks is false"); - if (ticksAttr != null && !ticksAttr.HasTicks) - throw new InvalidOperationException("HastTicks is false"); + Type type = ticksAttr?.Type ?? route.Type; - Type type = ticksAttr?.Type ?? route.Type; + DbTypePair pair = Settings.GetSqlDbType(ticksAttr, type); - DbTypePair pair = Settings.GetSqlDbType(ticksAttr, type); + string ticksName = ticksAttr?.Name ?? name.ToString(); - string ticksName = ticksAttr?.Name ?? name.ToString(); + return table.Ticks = new FieldTicks(route, type, ticksName) + { + DbType = pair.DbType, + Collation = Settings.GetCollate(ticksAttr), + UserDefinedTypeName = pair.UserDefinedTypeName, + Nullable = IsNullable.No, + Size = Settings.GetSqlSize(ticksAttr, null, pair.DbType), + Precision = Settings.GetSqlPrecision(ticksAttr, null, pair.DbType), + Scale = Settings.GetSqlScale(ticksAttr, null, pair.DbType), + Default = ticksAttr?.GetDefault(Settings.IsPostgres), + }; + } - return table.Ticks = new FieldTicks(route, type, ticksName) + protected virtual FieldValue GenerateFieldValue(ITable table, PropertyRoute route, NameSequence name, bool forceNull) { - DbType = pair.DbType, - Collation = Settings.GetCollate(ticksAttr), - UserDefinedTypeName = pair.UserDefinedTypeName, - Nullable = IsNullable.No, - Size = Settings.GetSqlSize(ticksAttr, null, pair.DbType), - Scale = Settings.GetSqlScale(ticksAttr, null, pair.DbType), - Default = ticksAttr?.GetDefault(Settings.IsPostgres), - }; - } + var att = Settings.FieldAttribute(route); - protected virtual FieldValue GenerateFieldValue(ITable table, PropertyRoute route, NameSequence name, bool forceNull) - { - var att = Settings.FieldAttribute(route); + DbTypePair pair = Settings.IsPostgres && route.Type.IsArray && route.Type != typeof(byte[]) ? + Settings.GetSqlDbType(att, route.Type.ElementType()!) : + Settings.GetSqlDbType(att, route.Type); - DbTypePair pair = Settings.IsPostgres && route.Type.IsArray && route.Type != typeof(byte[]) ? - Settings.GetSqlDbType(att, route.Type.ElementType()!) : - Settings.GetSqlDbType(att, route.Type); + return new FieldValue(route, null, name.ToString()) + { + DbType = pair.DbType, + Collation = Settings.GetCollate(att), + UserDefinedTypeName = pair.UserDefinedTypeName, + Nullable = Settings.GetIsNullable(route, forceNull), + Size = Settings.GetSqlSize(att, route, pair.DbType), + Precision = Settings.GetSqlPrecision(att, route, pair.DbType), + Scale = Settings.GetSqlScale(att, route, pair.DbType), + Default = att?.GetDefault(Settings.IsPostgres), + }.Do(f => f.UniqueIndex = f.GenerateUniqueIndex(table, Settings.FieldAttribute(route))); + } - return new FieldValue(route, null, name.ToString()) + protected virtual FieldEnum GenerateFieldEnum(ITable table, PropertyRoute route, NameSequence name, bool forceNull) { - DbType = pair.DbType, - Collation = Settings.GetCollate(att), - UserDefinedTypeName = pair.UserDefinedTypeName, - Nullable = Settings.GetIsNullable(route, forceNull), - Size = Settings.GetSqlSize(att, route, pair.DbType), - Scale = Settings.GetSqlScale(att, route, pair.DbType), - Default = att?.GetDefault(Settings.IsPostgres), - }.Do(f => f.UniqueIndex = f.GenerateUniqueIndex(table, Settings.FieldAttribute(route))); - } + var att = Settings.FieldAttribute(route); - protected virtual FieldEnum GenerateFieldEnum(ITable table, PropertyRoute route, NameSequence name, bool forceNull) - { - var att = Settings.FieldAttribute(route); + Type cleanEnum = route.Type.UnNullify(); - Type cleanEnum = route.Type.UnNullify(); + var referenceTable = Include(EnumEntity.Generate(cleanEnum), route); - var referenceTable = Include(EnumEntity.Generate(cleanEnum), route); + return new FieldEnum(route, name.ToString(), referenceTable) + { + Nullable = Settings.GetIsNullable(route, forceNull), + IsLite = false, + AvoidForeignKey = Settings.FieldAttribute(route) != null, + Default = att?.GetDefault(Settings.IsPostgres), + }.Do(f => f.UniqueIndex = f.GenerateUniqueIndex(table, Settings.FieldAttribute(route))); + } - return new FieldEnum(route, name.ToString(), referenceTable) + protected virtual FieldReference GenerateFieldReference(ITable table, PropertyRoute route, NameSequence name, bool forceNull) { - Nullable = Settings.GetIsNullable(route, forceNull), - IsLite = false, - AvoidForeignKey = Settings.FieldAttribute(route) != null, - Default = att?.GetDefault(Settings.IsPostgres), - }.Do(f => f.UniqueIndex = f.GenerateUniqueIndex(table, Settings.FieldAttribute(route))); - } + var referenceTable = Include(Lite.Extract(route.Type) ?? route.Type, route); - protected virtual FieldReference GenerateFieldReference(ITable table, PropertyRoute route, NameSequence name, bool forceNull) - { - var referenceTable = Include(Lite.Extract(route.Type) ?? route.Type, route); + var nullable = Settings.GetIsNullable(route, forceNull); - var nullable = Settings.GetIsNullable(route, forceNull); + return new FieldReference(route, null, name.ToString(), referenceTable) + { + Nullable = nullable, + IsLite = route.Type.IsLite(), + AvoidForeignKey = Settings.FieldAttribute(route) != null, + AvoidExpandOnRetrieving = Settings.FieldAttribute(route) != null, + Default = Settings.FieldAttribute(route)?.GetDefault(Settings.IsPostgres) + }.Do(f => f.UniqueIndex = f.GenerateUniqueIndex(table, Settings.FieldAttribute(route))); + } - return new FieldReference(route, null, name.ToString(), referenceTable) + protected virtual FieldImplementedBy GenerateFieldImplementedBy(ITable table, PropertyRoute route, NameSequence name, bool forceNull, IEnumerable types) { - Nullable = nullable, - IsLite = route.Type.IsLite(), - AvoidForeignKey = Settings.FieldAttribute(route) != null, - AvoidExpandOnRetrieving = Settings.FieldAttribute(route) != null, - Default = Settings.FieldAttribute(route)?.GetDefault(Settings.IsPostgres) - }.Do(f => f.UniqueIndex = f.GenerateUniqueIndex(table, Settings.FieldAttribute(route))); - } + Type cleanType = Lite.Extract(route.Type) ?? route.Type; + string errors = types.Where(t => !cleanType.IsAssignableFrom(t)).ToString(t => t.TypeName(), ", "); + if (errors.Length != 0) + throw new InvalidOperationException("Type {0} do not implement {1}".FormatWith(errors, cleanType)); - protected virtual FieldImplementedBy GenerateFieldImplementedBy(ITable table, PropertyRoute route, NameSequence name, bool forceNull, IEnumerable types) - { - Type cleanType = Lite.Extract(route.Type) ?? route.Type; - string errors = types.Where(t => !cleanType.IsAssignableFrom(t)).ToString(t => t.TypeName(), ", "); - if (errors.Length != 0) - throw new InvalidOperationException("Type {0} do not implement {1}".FormatWith(errors, cleanType)); + var nullable = Settings.GetIsNullable(route, forceNull); - var nullable = Settings.GetIsNullable(route, forceNull); + if (types.Count() > 1 && nullable == IsNullable.No) + nullable = IsNullable.Forced; - if (types.Count() > 1 && nullable == IsNullable.No) - nullable = IsNullable.Forced; + CombineStrategy strategy = Settings.FieldAttribute(route)?.Strategy ?? CombineStrategy.Case; - CombineStrategy strategy = Settings.FieldAttribute(route)?.Strategy ?? CombineStrategy.Case; + bool avoidForeignKey = Settings.FieldAttribute(route) != null; - bool avoidForeignKey = Settings.FieldAttribute(route) != null; + var implementations = types.ToDictionary(t => t, t => + { + var rt = Include(t, route); + + string impName = name.Add(TypeLogic.GetCleanName(t)).ToString(); + return new ImplementationColumn(impName, referenceTable: rt) + { + Nullable = nullable, + AvoidForeignKey = avoidForeignKey, + }; + }); - var implementations = types.ToDictionary(t => t, t => + return new FieldImplementedBy(route, implementations) + { + SplitStrategy = strategy, + IsLite = route.Type.IsLite(), + AvoidExpandOnRetrieving = Settings.FieldAttribute(route) != null + }.Do(f => f.UniqueIndex = f.GenerateUniqueIndex(table, Settings.FieldAttribute(route))); + } + + protected virtual FieldImplementedByAll GenerateFieldImplementedByAll(PropertyRoute route, ITable table, NameSequence preName, bool forceNull) { - var rt = Include(t, route); + var nullable = Settings.GetIsNullable(route, forceNull); - string impName = name.Add(TypeLogic.GetCleanName(t)).ToString(); - return new ImplementationColumn(impName, referenceTable: rt) + var column = new ImplementationStringColumn(preName.ToString()) { Nullable = nullable, - AvoidForeignKey = avoidForeignKey, + Size = Settings.DefaultImplementedBySize, }; - }); - return new FieldImplementedBy(route, implementations) - { - SplitStrategy = strategy, - IsLite = route.Type.IsLite(), - AvoidExpandOnRetrieving = Settings.FieldAttribute(route) != null - }.Do(f => f.UniqueIndex = f.GenerateUniqueIndex(table, Settings.FieldAttribute(route))); - } + var columnType = new ImplementationColumn(preName.Add("Type").ToString(), Include(typeof(TypeEntity), route)) + { + Nullable = nullable, + AvoidForeignKey = Settings.FieldAttribute(route) != null, + }; - protected virtual FieldImplementedByAll GenerateFieldImplementedByAll(PropertyRoute route, ITable table, NameSequence preName, bool forceNull) - { - var nullable = Settings.GetIsNullable(route, forceNull); + return new FieldImplementedByAll(route, column, columnType) + { + IsLite = route.Type.IsLite(), + AvoidExpandOnRetrieving = Settings.FieldAttribute(route) != null + }.Do(f => f.UniqueIndex = f.GenerateUniqueIndex(table, Settings.FieldAttribute(route))); + } - var column = new ImplementationStringColumn(preName.ToString()) + protected virtual FieldMList GenerateFieldMList(Table table, PropertyRoute route, NameSequence name) { - Nullable = nullable, - Size = Settings.DefaultImplementedBySize, - }; + Type elementType = route.Type.ElementType()!; - var columnType = new ImplementationColumn(preName.Add("Type").ToString(), Include(typeof(TypeEntity), route)) - { - Nullable = nullable, - AvoidForeignKey = Settings.FieldAttribute(route) != null, - }; + if (table.Ticks == null) + throw new InvalidOperationException("Type '{0}' has field '{1}' but does not Ticks. MList requires concurrency control.".FormatWith(route.Parent!.Type.TypeName(), route.FieldInfo!.FieldName())); - return new FieldImplementedByAll(route, column, columnType) - { - IsLite = route.Type.IsLite(), - AvoidExpandOnRetrieving = Settings.FieldAttribute(route) != null - }.Do(f => f.UniqueIndex = f.GenerateUniqueIndex(table, Settings.FieldAttribute(route))); - } + var orderAttr = Settings.FieldAttribute(route); - protected virtual FieldMList GenerateFieldMList(Table table, PropertyRoute route, NameSequence name) - { - Type elementType = route.Type.ElementType()!; + FieldValue? order = null; + if (orderAttr != null) + { + var pair = Settings.GetSqlDbTypePair(typeof(int)); + + order = new FieldValue(route: null!, fieldType: typeof(int), orderAttr.Name ?? "Order") + { + DbType = pair.DbType, + Collation = Settings.GetCollate(orderAttr), + UserDefinedTypeName = pair.UserDefinedTypeName, + Nullable = IsNullable.No, + Size = Settings.GetSqlSize(orderAttr, null, pair.DbType), + Precision = Settings.GetSqlPrecision(orderAttr, null, pair.DbType), + Scale = Settings.GetSqlScale(orderAttr, null, pair.DbType), + }; + } - if (table.Ticks == null) - throw new InvalidOperationException("Type '{0}' has field '{1}' but does not Ticks. MList requires concurrency control.".FormatWith(route.Parent!.Type.TypeName(), route.FieldInfo!.FieldName())); + var keyAttr = Settings.FieldAttribute(route) ?? Settings.DefaultPrimaryKeyAttribute; + TableMList.PrimaryKeyColumn primaryKey; + { + var pair = Settings.GetSqlDbType(keyAttr, keyAttr.Type); - var orderAttr = Settings.FieldAttribute(route); + primaryKey = new TableMList.PrimaryKeyColumn(keyAttr.Type, keyAttr.Name) + { + DbType = pair.DbType, + Collation = Settings.GetCollate(orderAttr), + UserDefinedTypeName = pair.UserDefinedTypeName, + Default = keyAttr.GetDefault(Settings.IsPostgres), + Identity = keyAttr.Identity, + }; + } - FieldValue? order = null; - if (orderAttr != null) - { - var pair = Settings.GetSqlDbTypePair(typeof(int)); + var tableName = GenerateTableNameCollection(table, name, Settings.FieldAttribute(route)); - order = new FieldValue(route: null!, fieldType: typeof(int), orderAttr.Name ?? "Order") + var backReference = new FieldReference(route: null!, fieldType: table.Type, + name: GenerateBackReferenceName(table.Type, Settings.FieldAttribute(route)), + referenceTable: table + ) { - DbType = pair.DbType, - Collation = Settings.GetCollate(orderAttr), - UserDefinedTypeName = pair.UserDefinedTypeName, - Nullable = IsNullable.No, - Size = Settings.GetSqlSize(orderAttr, null, pair.DbType), - Scale = Settings.GetSqlScale(orderAttr, null, pair.DbType), + AvoidForeignKey = Settings.FieldAttribute(route) != null, }; - } - var keyAttr = Settings.FieldAttribute(route) ?? Settings.DefaultPrimaryKeyAttribute; - TableMList.PrimaryKeyColumn primaryKey; - { - var pair = Settings.GetSqlDbType(keyAttr, keyAttr.Type); - - primaryKey = new TableMList.PrimaryKeyColumn(keyAttr.Type, keyAttr.Name) + TableMList mlistTable = new TableMList(route.Type, tableName, primaryKey, backReference) { - DbType = pair.DbType, - Collation = Settings.GetCollate(orderAttr), - UserDefinedTypeName = pair.UserDefinedTypeName, - Default = keyAttr.GetDefault(Settings.IsPostgres), - Identity = keyAttr.Identity, + Order = order, }; - } - var tableName = GenerateTableNameCollection(table, name, Settings.FieldAttribute(route)); + mlistTable.Field = GenerateField(mlistTable, route.Add("Item"), NameSequence.Void, forceNull: false, inMList: true); + + var sysAttribute = Settings.FieldAttribute(route) ?? + (Settings.TypeAttribute(table.Type) != null ? new SystemVersionedAttribute() : null); - var backReference = new FieldReference(route: null!, fieldType: table.Type, - name: GenerateBackReferenceName(table.Type, Settings.FieldAttribute(route)), - referenceTable: table - ) - { - AvoidForeignKey = Settings.FieldAttribute(route) != null, - }; + mlistTable.SystemVersioned = ToSystemVersionedInfo(sysAttribute, mlistTable.Name); - TableMList mlistTable = new TableMList(route.Type, tableName, primaryKey, backReference) - { - Order = order, - }; + mlistTable.GenerateColumns(); - mlistTable.Field = GenerateField(mlistTable, route.Add("Item"), NameSequence.Void, forceNull: false, inMList: true); - - var sysAttribute = Settings.FieldAttribute(route) ?? - (Settings.TypeAttribute(table.Type) != null ? new SystemVersionedAttribute() : null); + return new FieldMList(route, mlistTable) + { + TableMList = mlistTable, + }; + } - mlistTable.SystemVersioned = ToSystemVersionedInfo(sysAttribute, mlistTable.Name); + protected virtual FieldEmbedded GenerateFieldEmbedded(ITable table, PropertyRoute route, NameSequence name, bool forceNull, bool inMList) + { + var nullable = Settings.GetIsNullable(route, false); - mlistTable.GenerateColumns(); + var hasValue = nullable.ToBool() ? new FieldEmbedded.EmbeddedHasValueColumn(name.Add("HasValue").ToString()) : null; - return new FieldMList(route, mlistTable) - { - TableMList = mlistTable, - }; - } + var embeddedFields = GenerateFields(route, table, name, forceNull: nullable.ToBool() || forceNull, inMList: inMList); + var mixins = GenerateMixins(route, table, name); + return new FieldEmbedded(route, hasValue, embeddedFields, mixins); + } - protected virtual FieldEmbedded GenerateFieldEmbedded(ITable table, PropertyRoute route, NameSequence name, bool forceNull, bool inMList) - { - var nullable = Settings.GetIsNullable(route, false); + protected virtual FieldMixin GenerateFieldMixin(PropertyRoute route, NameSequence name, ITable table) + { + return new FieldMixin(route, table, GenerateFields(route, table, name, forceNull: false, inMList: false)); + } + #endregion - var hasValue = nullable.ToBool() ? new FieldEmbedded.EmbeddedHasValueColumn(name.Add("HasValue").ToString()) : null; + #region Names - var embeddedFields = GenerateFields(route, table, name, forceNull: nullable.ToBool() || forceNull, inMList: inMList); - var mixins = GenerateMixins(route, table, name); - return new FieldEmbedded(route, hasValue, embeddedFields, mixins); - } + public virtual string GenerateCleanTypeName(Type type) + { + type = CleanType(type); - protected virtual FieldMixin GenerateFieldMixin(PropertyRoute route, NameSequence name, ITable table) - { - return new FieldMixin(route, table, GenerateFields(route, table, name, forceNull: false, inMList: false)); - } - #endregion + var ctn = type.GetCustomAttribute(); + if (ctn != null) + return ctn.Name; - #region Names + return Reflector.CleanTypeName(type); + } - public virtual string GenerateCleanTypeName(Type type) - { - type = CleanType(type); + protected static Type CleanType(Type type) + { + type = Lite.Extract(type) ?? type; + type = EnumEntity.Extract(type) ?? type; + return type; + } - var ctn = type.GetCustomAttribute(); - if (ctn != null) - return ctn.Name; + public virtual ObjectName GenerateTableName(Type type, TableNameAttribute? tn) + { + var isPostgres = Schema.Settings.IsPostgres; - return Reflector.CleanTypeName(type); - } + SchemaName sn = tn != null ? GetSchemaName(tn) : SchemaName.Default(isPostgres); - protected static Type CleanType(Type type) - { - type = Lite.Extract(type) ?? type; - type = EnumEntity.Extract(type) ?? type; - return type; - } + string name = tn?.Name ?? EnumEntity.Extract(type)?.Name ?? Reflector.CleanTypeName(type); - public virtual ObjectName GenerateTableName(Type type, TableNameAttribute? tn) - { - var isPostgres = Schema.Settings.IsPostgres; + return new ObjectName(sn, name, isPostgres); + } - SchemaName sn = tn != null ? GetSchemaName(tn) : SchemaName.Default(isPostgres); + private SchemaName GetSchemaName(TableNameAttribute tn) + { + var isPostgres = Schema.Settings.IsPostgres; - string name = tn?.Name ?? EnumEntity.Extract(type)?.Name ?? Reflector.CleanTypeName(type); + ServerName? server = tn.ServerName == null ? null : new ServerName(tn.ServerName, isPostgres); + DatabaseName? dataBase = tn.DatabaseName == null && server == null ? null : new DatabaseName(server, tn.DatabaseName!, isPostgres); + SchemaName schema = tn.SchemaName == null && dataBase == null ? (tn.Name.StartsWith("#") && isPostgres ? null! : SchemaName.Default(isPostgres)) : new SchemaName(dataBase, tn.SchemaName!, isPostgres); + return schema; + } - return new ObjectName(sn, name, isPostgres); - } + public virtual ObjectName GenerateTableNameCollection(Table table, NameSequence name, TableNameAttribute? tn) + { + var isPostgres = Schema.Settings.IsPostgres; - private SchemaName GetSchemaName(TableNameAttribute tn) - { - var isPostgres = Schema.Settings.IsPostgres; + SchemaName sn = tn != null ? GetSchemaName(tn) : SchemaName.Default(isPostgres); - ServerName? server = tn.ServerName == null ? null : new ServerName(tn.ServerName, isPostgres); - DatabaseName? dataBase = tn.DatabaseName == null && server == null ? null : new DatabaseName(server, tn.DatabaseName!, isPostgres); - SchemaName schema = tn.SchemaName == null && dataBase == null ? (tn.Name.StartsWith("#") && isPostgres ? null! : SchemaName.Default(isPostgres)) : new SchemaName(dataBase, tn.SchemaName!, isPostgres); - return schema; - } + return new ObjectName(sn, tn?.Name ?? (table.Name.Name + name.ToString()), isPostgres); + } - public virtual ObjectName GenerateTableNameCollection(Table table, NameSequence name, TableNameAttribute? tn) - { - var isPostgres = Schema.Settings.IsPostgres; + public virtual string GenerateMListFieldName(PropertyRoute route, KindOfField kindOfField) + { + Type type = Lite.Extract(route.Type) ?? route.Type; - SchemaName sn = tn != null ? GetSchemaName(tn) : SchemaName.Default(isPostgres); + switch (kindOfField) + { + case KindOfField.Value: + case KindOfField.Embedded: + return type.Name; + case KindOfField.Enum: + case KindOfField.Reference: + return (EnumEntity.Extract(type)?.Name ?? Reflector.CleanTypeName(type)) + "ID"; + default: + throw new InvalidOperationException("No field name for type {0} defined".FormatWith(type)); + } + } - return new ObjectName(sn, tn?.Name ?? (table.Name.Name + name.ToString()), isPostgres); - } + public virtual string GenerateFieldName(PropertyRoute route, KindOfField kindOfField) + { + string name = route.PropertyInfo != null ? (route.PropertyInfo.Name.TryAfterLast('.') ?? route.PropertyInfo.Name) + : route.FieldInfo!.Name; - public virtual string GenerateMListFieldName(PropertyRoute route, KindOfField kindOfField) - { - Type type = Lite.Extract(route.Type) ?? route.Type; + switch (kindOfField) + { + case KindOfField.PrimaryKey: + case KindOfField.Ticks: + case KindOfField.Value: + case KindOfField.Embedded: + case KindOfField.MList: //se usa solo para el nombre de la tabla + return name; + case KindOfField.Reference: + case KindOfField.Enum: + return name + "ID"; + default: + throw new InvalidOperationException("No name for {0} defined".FormatWith(route.FieldInfo!.Name)); + } + } - switch (kindOfField) + public virtual string GenerateBackReferenceName(Type type, BackReferenceColumnNameAttribute? attribute) { - case KindOfField.Value: - case KindOfField.Embedded: - return type.Name; - case KindOfField.Enum: - case KindOfField.Reference: - return (EnumEntity.Extract(type)?.Name ?? Reflector.CleanTypeName(type)) + "ID"; - default: - throw new InvalidOperationException("No field name for type {0} defined".FormatWith(type)); + return attribute?.Name ?? "ParentID"; } - } + #endregion - public virtual string GenerateFieldName(PropertyRoute route, KindOfField kindOfField) - { - string name = route.PropertyInfo != null ? (route.PropertyInfo.Name.TryAfterLast('.') ?? route.PropertyInfo.Name) - : route.FieldInfo!.Name; + GlobalLazyManager GlobalLazyManager = new GlobalLazyManager(); - switch (kindOfField) + public void SwitchGlobalLazyManager(GlobalLazyManager manager) { - case KindOfField.PrimaryKey: - case KindOfField.Ticks: - case KindOfField.Value: - case KindOfField.Embedded: - case KindOfField.MList: //se usa solo para el nombre de la tabla - return name; - case KindOfField.Reference: - case KindOfField.Enum: - return name + "ID"; - default: - throw new InvalidOperationException("No name for {0} defined".FormatWith(route.FieldInfo!.Name)); + GlobalLazyManager.AsserNotUsed(); + GlobalLazyManager = manager; } - } - public virtual string GenerateBackReferenceName(Type type, BackReferenceColumnNameAttribute? attribute) - { - return attribute?.Name ?? "ParentID"; - } - #endregion + public ResetLazy GlobalLazy(Func func, InvalidateWith invalidateWith, + Action? onInvalidated = null, + LazyThreadSafetyMode mode = LazyThreadSafetyMode.ExecutionAndPublication) where T : class + { + var result = Signum.Engine.GlobalLazy.WithoutInvalidations(() => + { + GlobalLazyManager.OnLoad(this, invalidateWith); - GlobalLazyManager GlobalLazyManager = new GlobalLazyManager(); + return func(); + }); - public void SwitchGlobalLazyManager(GlobalLazyManager manager) - { - GlobalLazyManager.AsserNotUsed(); - GlobalLazyManager = manager; + GlobalLazyManager.AttachInvalidations(this, invalidateWith, (sender, args) => + { + result.Reset(); + onInvalidated?.Invoke(); + }); + + return result; + } } - public ResetLazy GlobalLazy(Func func, InvalidateWith invalidateWith, - Action? onInvalidated = null, - LazyThreadSafetyMode mode = LazyThreadSafetyMode.ExecutionAndPublication) where T : class + public class GlobalLazyManager { - var result = Signum.Engine.GlobalLazy.WithoutInvalidations(() => - { - GlobalLazyManager.OnLoad(this, invalidateWith); + bool isUsed = false; - return func(); - }); - - GlobalLazyManager.AttachInvalidations(this, invalidateWith, (sender, args) => + public void AsserNotUsed() { - result.Reset(); - onInvalidated?.Invoke(); - }); + if (isUsed) + throw new InvalidOperationException("GlobalLazyManager has already been used"); + } - return result; - } -} + public virtual void AttachInvalidations(SchemaBuilder sb, InvalidateWith invalidateWith, EventHandler invalidate) + { + isUsed = true; -public class GlobalLazyManager -{ - bool isUsed = false; + Action onInvalidation = () => + { + if (Transaction.InTestTransaction) + { + invalidate(this, EventArgs.Empty); + Transaction.Rolledback += dic => invalidate(this, EventArgs.Empty); + } - public void AsserNotUsed() - { - if (isUsed) - throw new InvalidOperationException("GlobalLazyManager has already been used"); - } + Transaction.PostRealCommit += dic => invalidate(this, EventArgs.Empty); + }; - public virtual void AttachInvalidations(SchemaBuilder sb, InvalidateWith invalidateWith, EventHandler invalidate) - { - isUsed = true; + Schema schema = sb.Schema; - Action onInvalidation = () => - { - if (Transaction.InTestTransaction) + foreach (var type in invalidateWith.Types) { - invalidate(this, EventArgs.Empty); - Transaction.Rolledback += dic => invalidate(this, EventArgs.Empty); + giAttachInvalidations.GetInvoker(type)(schema, onInvalidation); } - Transaction.PostRealCommit += dic => invalidate(this, EventArgs.Empty); - }; + var dependants = DirectedGraph.Generate(invalidateWith.Types.Select(t => schema.Table(t)), t => t.DependentTables().Select(kvp => kvp.Key)).Select(t => t.Type).ToHashSet(); + dependants.ExceptWith(invalidateWith.Types); - Schema schema = sb.Schema; - - foreach (var type in invalidateWith.Types) - { - giAttachInvalidations.GetInvoker(type)(schema, onInvalidation); + foreach (var type in dependants) + { + giAttachInvalidationsDependant.GetInvoker(type)(schema, onInvalidation); + } } - var dependants = DirectedGraph
.Generate(invalidateWith.Types.Select(t => schema.Table(t)), t => t.DependentTables().Select(kvp => kvp.Key)).Select(t => t.Type).ToHashSet(); - dependants.ExceptWith(invalidateWith.Types); - foreach (var type in dependants) + static readonly GenericInvoker> giAttachInvalidationsDependant = new((s, a) => AttachInvalidationsDependant(s, a)); + static void AttachInvalidationsDependant(Schema s, Action action) where T : Entity { - giAttachInvalidationsDependant.GetInvoker(type)(schema, onInvalidation); - } - } - + var ee = s.EntityEvents(); - static readonly GenericInvoker> giAttachInvalidationsDependant = new((s, a) => AttachInvalidationsDependant(s, a)); - static void AttachInvalidationsDependant(Schema s, Action action) where T : Entity - { - var ee = s.EntityEvents(); + ee.Saving += e => + { + if (!e.IsNew && e.IsGraphModified) + action(); + }; + ee.PreUnsafeUpdate += (u, q) => { action(); return null; }; + } - ee.Saving += e => + static readonly GenericInvoker> giAttachInvalidations = new((s, a) => AttachInvalidations(s, a)); + static void AttachInvalidations(Schema s, Action action) where T : Entity { - if (!e.IsNew && e.IsGraphModified) - action(); - }; - ee.PreUnsafeUpdate += (u, q) => { action(); return null; }; - } + var ee = s.EntityEvents(); - static readonly GenericInvoker> giAttachInvalidations = new((s, a) => AttachInvalidations(s, a)); - static void AttachInvalidations(Schema s, Action action) where T : Entity - { - var ee = s.EntityEvents(); + ee.Saving += e => + { + if (e.IsGraphModified) + action(); + }; + ee.PreUnsafeUpdate += (u, eq) => { action(); return null; }; + ee.PreUnsafeDelete += (q) => { action(); return null; }; + } - ee.Saving += e => + public virtual void OnLoad(SchemaBuilder sb, InvalidateWith invalidateWith) { - if (e.IsGraphModified) - action(); - }; - ee.PreUnsafeUpdate += (u, eq) => { action(); return null; }; - ee.PreUnsafeDelete += (q) => { action(); return null; }; - } - - public virtual void OnLoad(SchemaBuilder sb, InvalidateWith invalidateWith) - { + } } -} -public class ViewBuilder : SchemaBuilder -{ - public ViewBuilder(Schema schema) - : base(schema) + public class ViewBuilder : SchemaBuilder { - } + public ViewBuilder(Schema schema) + : base(schema) + { + } - public override Table Include(Type type) - { - return Schema.Table(type); - } + public override Table Include(Type type) + { + return Schema.Table(type); + } - public Table NewView(Type type) - { - Table table = new Table(type) + public Table NewView(Type type) { - Name = GenerateTableName(type, Settings.TypeAttribute(type)), - IsView = true - }; + Table table = new Table(type) + { + Name = GenerateTableName(type, Settings.TypeAttribute(type)), + IsView = true + }; - table.Fields = GenerateFields(PropertyRoute.Root(type), table, NameSequence.Void, forceNull: false, inMList: false); + table.Fields = GenerateFields(PropertyRoute.Root(type), table, NameSequence.Void, forceNull: false, inMList: false); - table.GenerateColumns(); + table.GenerateColumns(); - return table; - } + return table; + } - public override ObjectName GenerateTableName(Type type, TableNameAttribute? tn) - { - var name = base.GenerateTableName(type, tn); + public override ObjectName GenerateTableName(Type type, TableNameAttribute? tn) + { + var name = base.GenerateTableName(type, tn); - return Administrator.ReplaceViewName(name); - } + return Administrator.ReplaceViewName(name); + } - protected override FieldReference GenerateFieldReference(ITable table, PropertyRoute route, NameSequence name, bool forceNull) - { - var result = base.GenerateFieldReference(table, route, name, forceNull); + protected override FieldReference GenerateFieldReference(ITable table, PropertyRoute route, NameSequence name, bool forceNull) + { + var result = base.GenerateFieldReference(table, route, name, forceNull); - if (Settings.FieldAttribute(route) != null) - result.PrimaryKey = true; + if (Settings.FieldAttribute(route) != null) + result.PrimaryKey = true; - return result; - } + return result; + } - protected override FieldValue GenerateFieldValue(ITable table, PropertyRoute route, NameSequence name, bool forceNull) - { - var result = base.GenerateFieldValue(table, route, name, forceNull); + protected override FieldValue GenerateFieldValue(ITable table, PropertyRoute route, NameSequence name, bool forceNull) + { + var result = base.GenerateFieldValue(table, route, name, forceNull); - if (Settings.FieldAttribute(route) != null) - result.PrimaryKey = true; + if (Settings.FieldAttribute(route) != null) + result.PrimaryKey = true; - return result; - } + return result; + } - protected override FieldEnum GenerateFieldEnum(ITable table, PropertyRoute route, NameSequence name, bool forceNull) - { - var att = Settings.FieldAttribute(route); + protected override FieldEnum GenerateFieldEnum(ITable table, PropertyRoute route, NameSequence name, bool forceNull) + { + var att = Settings.FieldAttribute(route); - Type cleanEnum = route.Type.UnNullify(); + Type cleanEnum = route.Type.UnNullify(); - //var referenceTable = Include(EnumEntity.Generate(cleanEnum), route); + //var referenceTable = Include(EnumEntity.Generate(cleanEnum), route); - return new FieldEnum(route, name.ToString(), null! /*referenceTable*/) - { - Nullable = Settings.GetIsNullable(route, forceNull), - IsLite = false, - AvoidForeignKey = Settings.FieldAttribute(route) != null, - Default = att?.GetDefault(Settings.IsPostgres), - }.Do(f => f.UniqueIndex = f.GenerateUniqueIndex(table, Settings.FieldAttribute(route))); + return new FieldEnum(route, name.ToString(), null! /*referenceTable*/) + { + Nullable = Settings.GetIsNullable(route, forceNull), + IsLite = false, + AvoidForeignKey = Settings.FieldAttribute(route) != null, + Default = att?.GetDefault(Settings.IsPostgres), + }.Do(f => f.UniqueIndex = f.GenerateUniqueIndex(table, Settings.FieldAttribute(route))); + } } -} diff --git a/Signum.Engine/Schema/SchemaBuilder/SchemaSettings.cs b/Signum.Engine/Schema/SchemaBuilder/SchemaSettings.cs index 497ca9bd27..70d882163f 100644 --- a/Signum.Engine/Schema/SchemaBuilder/SchemaSettings.cs +++ b/Signum.Engine/Schema/SchemaBuilder/SchemaSettings.cs @@ -7,510 +7,541 @@ namespace Signum.Engine.Maps; -public class SchemaSettings -{ - public SchemaSettings() + public class SchemaSettings { - } + public SchemaSettings() + { + } - public bool IsPostgres { get; set; } - public bool PostresVersioningFunctionNoChecks { get; set; } + public bool IsPostgres { get; set; } + public bool PostresVersioningFunctionNoChecks { get; set; } - public PrimaryKeyAttribute DefaultPrimaryKeyAttribute = new PrimaryKeyAttribute(typeof(int), "ID"); - public int DefaultImplementedBySize = 40; + public PrimaryKeyAttribute DefaultPrimaryKeyAttribute = new PrimaryKeyAttribute(typeof(int), "ID"); + public int DefaultImplementedBySize = 40; - public Action AssertNotIncluded = null!; + public Action AssertNotIncluded = null!; - public int MaxNumberOfParameters = 2000; - public int MaxNumberOfStatementsInSaveQueries = 16; + public int MaxNumberOfParameters = 2000; + public int MaxNumberOfStatementsInSaveQueries = 16; - public ConcurrentDictionary FieldAttributesCache = new ConcurrentDictionary(); - public ConcurrentDictionary TypeAttributesCache = new ConcurrentDictionary(); + public ConcurrentDictionary FieldAttributesCache = new ConcurrentDictionary(); + public ConcurrentDictionary TypeAttributesCache = new ConcurrentDictionary(); - public Dictionary CustomOrder = new Dictionary(); - - internal Dictionary? desambiguatedNames; - public void Desambiguate(Type type, string cleanName) - { - if (desambiguatedNames == null) - desambiguatedNames = new Dictionary(); + public Dictionary CustomOrder = new Dictionary(); + + internal Dictionary? desambiguatedNames; + public void Desambiguate(Type type, string cleanName) + { + if (desambiguatedNames == null) + desambiguatedNames = new Dictionary(); - desambiguatedNames[type] = cleanName; - } + desambiguatedNames[type] = cleanName; + } - public Dictionary UdtSqlName = new Dictionary() - { - //{ typeof(SqlHierarchyId), "HierarchyId"}, - //{ typeof(SqlGeography), "Geography"}, - //{ typeof(SqlGeometry), "Geometry"}, - }; + public Dictionary UdtSqlName = new Dictionary() + { + //{ typeof(SqlHierarchyId), "HierarchyId"}, + //{ typeof(SqlGeography), "Geography"}, + //{ typeof(SqlGeometry), "Geometry"}, + }; - public Dictionary TypeValues = new Dictionary - { - {typeof(bool), new AbstractDbType(SqlDbType.Bit, NpgsqlDbType.Boolean)}, - - {typeof(byte), new AbstractDbType(SqlDbType.TinyInt, NpgsqlDbType.Smallint)}, - {typeof(short), new AbstractDbType(SqlDbType.SmallInt, NpgsqlDbType.Smallint)}, - {typeof(int), new AbstractDbType(SqlDbType.Int, NpgsqlDbType.Integer)}, - {typeof(long), new AbstractDbType(SqlDbType.BigInt, NpgsqlDbType.Bigint)}, - - {typeof(float), new AbstractDbType(SqlDbType.Real, NpgsqlDbType.Real)}, - {typeof(double), new AbstractDbType(SqlDbType.Float, NpgsqlDbType.Double)}, - {typeof(decimal), new AbstractDbType(SqlDbType.Decimal, NpgsqlDbType.Numeric)}, - - {typeof(char), new AbstractDbType(SqlDbType.NChar, NpgsqlDbType.Char)}, - {typeof(string), new AbstractDbType(SqlDbType.NVarChar, NpgsqlDbType.Varchar)}, + public Dictionary TypeValues = new Dictionary + { + {typeof(bool), new AbstractDbType(SqlDbType.Bit, NpgsqlDbType.Boolean)}, + + {typeof(byte), new AbstractDbType(SqlDbType.TinyInt, NpgsqlDbType.Smallint)}, + {typeof(short), new AbstractDbType(SqlDbType.SmallInt, NpgsqlDbType.Smallint)}, + {typeof(int), new AbstractDbType(SqlDbType.Int, NpgsqlDbType.Integer)}, + {typeof(long), new AbstractDbType(SqlDbType.BigInt, NpgsqlDbType.Bigint)}, + + {typeof(float), new AbstractDbType(SqlDbType.Real, NpgsqlDbType.Real)}, + {typeof(double), new AbstractDbType(SqlDbType.Float, NpgsqlDbType.Double)}, + {typeof(decimal), new AbstractDbType(SqlDbType.Decimal, NpgsqlDbType.Numeric)}, + + {typeof(char), new AbstractDbType(SqlDbType.NChar, NpgsqlDbType.Char)}, + {typeof(string), new AbstractDbType(SqlDbType.NVarChar, NpgsqlDbType.Varchar)}, {typeof(DateOnly), new AbstractDbType(SqlDbType.Date, NpgsqlDbType.Date)}, - {typeof(DateTime), new AbstractDbType(SqlDbType.DateTime2, NpgsqlDbType.Timestamp)}, - {typeof(DateTimeOffset), new AbstractDbType(SqlDbType.DateTimeOffset, NpgsqlDbType.TimestampTz)}, - {typeof(TimeSpan), new AbstractDbType(SqlDbType.Time, NpgsqlDbType.Time)}, + {typeof(DateTime), new AbstractDbType(SqlDbType.DateTime2, NpgsqlDbType.Timestamp)}, + {typeof(DateTimeOffset), new AbstractDbType(SqlDbType.DateTimeOffset, NpgsqlDbType.TimestampTz)}, + {typeof(TimeSpan), new AbstractDbType(SqlDbType.Time, NpgsqlDbType.Time)}, {typeof(TimeOnly), new AbstractDbType(SqlDbType.Time, NpgsqlDbType.Time)}, - {typeof(byte[]), new AbstractDbType(SqlDbType.VarBinary, NpgsqlDbType.Bytea)}, + {typeof(byte[]), new AbstractDbType(SqlDbType.VarBinary, NpgsqlDbType.Bytea)}, - {typeof(Guid), new AbstractDbType(SqlDbType.UniqueIdentifier, NpgsqlDbType.Uuid)}, - }; + {typeof(Guid), new AbstractDbType(SqlDbType.UniqueIdentifier, NpgsqlDbType.Uuid)}, + }; - readonly Dictionary defaultSizeSqlServer = new Dictionary() - { - {SqlDbType.NVarChar, 200}, - {SqlDbType.VarChar, 200}, - {SqlDbType.VarBinary, int.MaxValue}, - {SqlDbType.Binary, 8000}, - {SqlDbType.Char, 1}, - {SqlDbType.NChar, 1}, - {SqlDbType.Decimal, 18}, - }; - - readonly Dictionary defaultSizePostgreSql = new Dictionary() - { - {NpgsqlDbType.Varbit, 200}, - {NpgsqlDbType.Varchar, 200}, - {NpgsqlDbType.Char, 1}, - {NpgsqlDbType.Numeric, 18}, - }; + readonly Dictionary defaultSizeSqlServer = new Dictionary() + { + {SqlDbType.NVarChar, 200}, + {SqlDbType.VarChar, 200}, + {SqlDbType.VarBinary, int.MaxValue}, + {SqlDbType.Binary, 8000}, + {SqlDbType.Char, 1}, + {SqlDbType.NChar, 1}, +// {SqlDbType.Decimal, 18}, + }; + + readonly Dictionary defaultSizePostgreSql = new Dictionary() + { + {NpgsqlDbType.Varbit, 200}, + {NpgsqlDbType.Varchar, 200}, + {NpgsqlDbType.Char, 1}, +// {NpgsqlDbType.Numeric, 18}, + }; - readonly Dictionary defaultScaleSqlServer = new Dictionary() - { - {SqlDbType.Decimal, 2}, - }; + readonly Dictionary defaultPrecisionSqlServer = new Dictionary() + { + {SqlDbType.Decimal, 18}, + }; - readonly Dictionary defaultScalePostgreSql = new Dictionary() - { - {NpgsqlDbType.Numeric, 2}, - }; + readonly Dictionary defaultPrecisionPostgreSql = new Dictionary() + { + {NpgsqlDbType.Numeric, 18}, + }; - public AttributeCollection FieldAttributes(Expression> propertyRoute) - where T : IRootEntity - { - return FieldAttributes(PropertyRoute.Construct(propertyRoute))!; - } + readonly Dictionary defaultScaleSqlServer = new Dictionary() + { + {SqlDbType.Decimal, 2}, + }; - public AttributeCollection? FieldAttributes(PropertyRoute propertyRoute) - { - using (HeavyProfiler.LogNoStackTrace("FieldAttributes")) + readonly Dictionary defaultScalePostgreSql = new Dictionary() + { + {NpgsqlDbType.Numeric, 2}, + }; + + public AttributeCollection FieldAttributes(Expression> propertyRoute) + where T : IRootEntity + { + return FieldAttributes(PropertyRoute.Construct(propertyRoute))!; + } + + public AttributeCollection? FieldAttributes(PropertyRoute propertyRoute) { - return FieldAttributesCache.GetOrAdd(propertyRoute, pr => + using (HeavyProfiler.LogNoStackTrace("FieldAttributes")) { - switch (propertyRoute.PropertyRouteType) + return FieldAttributesCache.GetOrAdd(propertyRoute, pr => { - case PropertyRouteType.FieldOrProperty: - if (propertyRoute.FieldInfo == null) - return null; - return CreateFieldAttributeCollection(propertyRoute); - case PropertyRouteType.MListItems: - if (propertyRoute.Parent!.FieldInfo == null) - return null; - return CreateFieldAttributeCollection(propertyRoute.Parent!); - default: - throw new InvalidOperationException("Route of type {0} not supported for this method".FormatWith(propertyRoute.PropertyRouteType)); - } - }); + switch (propertyRoute.PropertyRouteType) + { + case PropertyRouteType.FieldOrProperty: + if (propertyRoute.FieldInfo == null) + return null; + return CreateFieldAttributeCollection(propertyRoute); + case PropertyRouteType.MListItems: + if (propertyRoute.Parent!.FieldInfo == null) + return null; + return CreateFieldAttributeCollection(propertyRoute.Parent!); + default: + throw new InvalidOperationException("Route of type {0} not supported for this method".FormatWith(propertyRoute.PropertyRouteType)); + } + }); + } } - } - AttributeCollection CreateFieldAttributeCollection(PropertyRoute route) - { - var fieldAttributes = route.FieldInfo!.GetCustomAttributes(false).Cast(); - var fieldAttributesInProperty = route.PropertyInfo == null ? Enumerable.Empty() : - route.PropertyInfo.GetCustomAttributes(false).Cast().Where(a => AttributeCollection.IsCompatibleWith(a, AttributeTargets.Field)); - return new AttributeCollection(AttributeTargets.Field, fieldAttributes.Concat(fieldAttributesInProperty).ToList(), () => AssertNotIncluded(route.RootType)); - } + AttributeCollection CreateFieldAttributeCollection(PropertyRoute route) + { + var fieldAttributes = route.FieldInfo!.GetCustomAttributes(false).Cast(); + var fieldAttributesInProperty = route.PropertyInfo == null ? Enumerable.Empty() : + route.PropertyInfo.GetCustomAttributes(false).Cast().Where(a => AttributeCollection.IsCompatibleWith(a, AttributeTargets.Field)); + return new AttributeCollection(AttributeTargets.Field, fieldAttributes.Concat(fieldAttributesInProperty).ToList(), () => AssertNotIncluded(route.RootType)); + } - public AttributeCollection TypeAttributes() where T : Entity - { - return TypeAttributes(typeof(T)); - } + public AttributeCollection TypeAttributes() where T : Entity + { + return TypeAttributes(typeof(T)); + } - public AttributeCollection TypeAttributes(Type entityType) - { - if (!typeof(Entity).IsAssignableFrom(entityType) && !typeof(IView).IsAssignableFrom(entityType)) - throw new InvalidOperationException("{0} is not an Entity or View".FormatWith(entityType.Name)); + public AttributeCollection TypeAttributes(Type entityType) + { + if (!typeof(Entity).IsAssignableFrom(entityType) && !typeof(IView).IsAssignableFrom(entityType)) + throw new InvalidOperationException("{0} is not an Entity or View".FormatWith(entityType.Name)); - if (entityType.IsAbstract) - throw new InvalidOperationException("{0} is abstract".FormatWith(entityType.Name)); + if (entityType.IsAbstract) + throw new InvalidOperationException("{0} is abstract".FormatWith(entityType.Name)); - return TypeAttributesCache.GetOrAdd(entityType, t => - { - var list = entityType.GetCustomAttributes(true).Cast().ToList(); + return TypeAttributesCache.GetOrAdd(entityType, t => + { + var list = entityType.GetCustomAttributes(true).Cast().ToList(); - var enumType = EnumEntity.Extract(entityType); + var enumType = EnumEntity.Extract(entityType); - if (enumType != null) - foreach (var at in enumType.GetCustomAttributes(true).Cast().ToList()) - { - list.RemoveAll(a => a.GetType() == at.GetType()); - list.Add(at); - } + if (enumType != null) + foreach (var at in enumType.GetCustomAttributes(true).Cast().ToList()) + { + list.RemoveAll(a => a.GetType() == at.GetType()); + list.Add(at); + } - return new AttributeCollection(AttributeTargets.Class, list, () => AssertNotIncluded(entityType)); - }); - } + return new AttributeCollection(AttributeTargets.Class, list, () => AssertNotIncluded(entityType)); + }); + } - public void AssertNotIgnored(Expression> propertyRoute, string errorContext, string solution = "by using SchemaBuilderSettings.FieldAttributes to remove IgnoreAttribute") where T : Entity - { - var pr = PropertyRoute.Construct(propertyRoute); + public void AssertNotIgnored(Expression> propertyRoute, string errorContext, string solution = "by using SchemaBuilderSettings.FieldAttributes to remove IgnoreAttribute") where T : Entity + { + var pr = PropertyRoute.Construct(propertyRoute); - if (FieldAttribute(pr) != null) - throw new InvalidOperationException($"In order to {errorContext} you need to override the attributes for {pr} {solution}"); - } + if (FieldAttribute(pr) != null) + throw new InvalidOperationException($"In order to {errorContext} you need to override the attributes for {pr} {solution}"); + } - public void AssertIgnored(Expression> propertyRoute, string errorContext, string solution = "by using SchemaBuilderSettings.FieldAttributes to add IgnoreAttribute") where T : Entity - { - var pr = PropertyRoute.Construct(propertyRoute); + public void AssertIgnored(Expression> propertyRoute, string errorContext, string solution = "by using SchemaBuilderSettings.FieldAttributes to add IgnoreAttribute") where T : Entity + { + var pr = PropertyRoute.Construct(propertyRoute); - if (FieldAttribute(pr) == null) - throw new InvalidOperationException($"In order to {errorContext} you need to override the attributes for {pr} {solution}"); - } + if (FieldAttribute(pr) == null) + throw new InvalidOperationException($"In order to {errorContext} you need to override the attributes for {pr} {solution}"); + } - public A? FieldAttribute(PropertyRoute propertyRoute) where A : Attribute - { - using (HeavyProfiler.LogNoStackTrace("FieldAttribute")) + public A? FieldAttribute(PropertyRoute propertyRoute) where A : Attribute { - if (propertyRoute.PropertyRouteType == PropertyRouteType.Root || propertyRoute.PropertyRouteType == PropertyRouteType.LiteEntity) - throw new InvalidOperationException("Route of type {0} not supported for this method".FormatWith(propertyRoute.PropertyRouteType)); + using (HeavyProfiler.LogNoStackTrace("FieldAttribute")) + { + if (propertyRoute.PropertyRouteType == PropertyRouteType.Root || propertyRoute.PropertyRouteType == PropertyRouteType.LiteEntity) + throw new InvalidOperationException("Route of type {0} not supported for this method".FormatWith(propertyRoute.PropertyRouteType)); - return (A?)FieldAttributes(propertyRoute)!.FirstOrDefault(a => a.GetType() == typeof(A)); + return (A?)FieldAttributes(propertyRoute)!.FirstOrDefault(a => a.GetType() == typeof(A)); + } } - } - public V? ValidatorAttribute(PropertyRoute propertyRoute) where V: ValidatorAttribute - { - if (!typeof(ModifiableEntity).IsAssignableFrom(propertyRoute.RootType)) - return null; + public V? ValidatorAttribute(PropertyRoute propertyRoute) where V: ValidatorAttribute + { + if (!typeof(ModifiableEntity).IsAssignableFrom(propertyRoute.RootType)) + return null; - if (propertyRoute.PropertyRouteType == PropertyRouteType.MListItems) - propertyRoute = propertyRoute.Parent!; + if (propertyRoute.PropertyRouteType == PropertyRouteType.MListItems) + propertyRoute = propertyRoute.Parent!; - if (propertyRoute.PropertyInfo == null) - return null; + if (propertyRoute.PropertyInfo == null) + return null; - var pp = Validator.TryGetPropertyValidator(propertyRoute); + var pp = Validator.TryGetPropertyValidator(propertyRoute); - if (pp == null) - return null; + if (pp == null) + return null; - return pp.Validators.OfType().FirstOrDefault(); - } + return pp.Validators.OfType().FirstOrDefault(); + } - public A? TypeAttribute(Type entityType) where A : Attribute - { - return (A?)TypeAttributes(entityType).FirstOrDefault(a => a.GetType() == typeof(A)); - } + public A? TypeAttribute(Type entityType) where A : Attribute + { + return (A?)TypeAttributes(entityType).FirstOrDefault(a => a.GetType() == typeof(A)); + } - internal IsNullable GetIsNullable(PropertyRoute propertyRoute, bool forceNull) - { - var result = GetIsNullablePrivate(propertyRoute); + internal IsNullable GetIsNullable(PropertyRoute propertyRoute, bool forceNull) + { + var result = GetIsNullablePrivate(propertyRoute); - if (result == IsNullable.No && forceNull) - return IsNullable.Forced; + if (result == IsNullable.No && forceNull) + return IsNullable.Forced; - return result; - } + return result; + } - private IsNullable GetIsNullablePrivate(PropertyRoute propertyRoute) - { - if (FieldAttribute(propertyRoute) != null) - return IsNullable.No; + private IsNullable GetIsNullablePrivate(PropertyRoute propertyRoute) + { + if (FieldAttribute(propertyRoute) != null) + return IsNullable.No; - if (FieldAttribute(propertyRoute) != null) - return IsNullable.Yes; + if (FieldAttribute(propertyRoute) != null) + return IsNullable.Yes; - if (propertyRoute.PropertyRouteType == PropertyRouteType.MListItems) - return IsNullable.No; + if (propertyRoute.PropertyRouteType == PropertyRouteType.MListItems) + return IsNullable.No; - if (propertyRoute.Type.IsValueType) - return propertyRoute.Type.IsNullable() ? IsNullable.Yes : IsNullable.No; + if (propertyRoute.Type.IsValueType) + return propertyRoute.Type.IsNullable() ? IsNullable.Yes : IsNullable.No; - var nullable = propertyRoute.FieldInfo?.IsNullable(); - if (nullable == true) - return IsNullable.Yes; + var nullable = propertyRoute.FieldInfo?.IsNullable(); + if (nullable == true) + return IsNullable.Yes; - return IsNullable.No; - } + return IsNullable.No; + } - public bool ImplementedBy(Expression> propertyRoute, Type typeToImplement) where T : Entity - { - var imp = GetImplementations(propertyRoute); - return !imp.IsByAll && imp.Types.Contains(typeToImplement); - } + public bool ImplementedBy(Expression> propertyRoute, Type typeToImplement) where T : Entity + { + var imp = GetImplementations(propertyRoute); + return !imp.IsByAll && imp.Types.Contains(typeToImplement); + } - public void AssertImplementedBy(Expression> propertyRoute, Type typeToImplement) where T : Entity - { - var route = PropertyRoute.Construct(propertyRoute); + public void AssertImplementedBy(Expression> propertyRoute, Type typeToImplement) where T : Entity + { + var route = PropertyRoute.Construct(propertyRoute); - Implementations imp = GetImplementations(route); + Implementations imp = GetImplementations(route); - if (imp.IsByAll || !imp.Types.Contains(typeToImplement)) - throw new InvalidOperationException("Route {0} is not ImplementedBy {1}".FormatWith(route, typeToImplement.Name) + - "\r\n" + - Implementations.ConsiderMessage(route, imp.Types.And(typeToImplement).ToString(t => $"typeof({t.TypeName()})", ", "))); - } + if (imp.IsByAll || !imp.Types.Contains(typeToImplement)) + throw new InvalidOperationException("Route {0} is not ImplementedBy {1}".FormatWith(route, typeToImplement.Name) + + "\r\n" + + Implementations.ConsiderMessage(route, imp.Types.And(typeToImplement).ToString(t => $"typeof({t.TypeName()})", ", "))); + } - public Implementations GetImplementations(Expression> propertyRoute) where T : Entity - { - return GetImplementations(PropertyRoute.Construct(propertyRoute)); - } + public Implementations GetImplementations(Expression> propertyRoute) where T : Entity + { + return GetImplementations(PropertyRoute.Construct(propertyRoute)); + } - public Implementations GetImplementations(PropertyRoute propertyRoute) - { - var cleanType = propertyRoute.Type.CleanType(); - if (!propertyRoute.Type.CleanType().IsIEntity()) - throw new InvalidOperationException("{0} is not a {1}".FormatWith(propertyRoute, typeof(IEntity).Name)); + public Implementations GetImplementations(PropertyRoute propertyRoute) + { + var cleanType = propertyRoute.Type.CleanType(); + if (!propertyRoute.Type.CleanType().IsIEntity()) + throw new InvalidOperationException("{0} is not a {1}".FormatWith(propertyRoute, typeof(IEntity).Name)); - return Implementations.FromAttributes(cleanType, propertyRoute, - FieldAttribute(propertyRoute), - FieldAttribute(propertyRoute)); - } + return Implementations.FromAttributes(cleanType, propertyRoute, + FieldAttribute(propertyRoute), + FieldAttribute(propertyRoute)); + } - public AbstractDbType ToAbstractDbType(DbTypeAttribute att) - { - if (att.HasNpgsqlDbType && att.HasSqlDbType) - return new AbstractDbType(att.SqlDbType, att.NpgsqlDbType); + public AbstractDbType ToAbstractDbType(DbTypeAttribute att) + { + if (att.HasNpgsqlDbType && att.HasSqlDbType) + return new AbstractDbType(att.SqlDbType, att.NpgsqlDbType); - if (att.HasNpgsqlDbType) - return new AbstractDbType(att.NpgsqlDbType); + if (att.HasNpgsqlDbType) + return new AbstractDbType(att.NpgsqlDbType); - if (att.HasSqlDbType) - return new AbstractDbType(att.SqlDbType); + if (att.HasSqlDbType) + return new AbstractDbType(att.SqlDbType); - throw new InvalidOperationException("Not type found in DbTypeAttribute"); - } + throw new InvalidOperationException("Not type found in DbTypeAttribute"); + } - internal DbTypePair GetSqlDbType(DbTypeAttribute? att, Type type) - { - if (att != null && (att.HasSqlDbType || att.HasNpgsqlDbType)) - return new DbTypePair(ToAbstractDbType(att), att.UserDefinedTypeName); + internal DbTypePair GetSqlDbType(DbTypeAttribute? att, Type type) + { + if (att != null && (att.HasSqlDbType || att.HasNpgsqlDbType)) + return new DbTypePair(ToAbstractDbType(att), att.UserDefinedTypeName); - return GetSqlDbTypePair(type.UnNullify()); - } + return GetSqlDbTypePair(type.UnNullify()); + } - internal DbTypePair? TryGetSqlDbType(DbTypeAttribute? att, Type type) - { - if (att != null && (att.HasSqlDbType || att.HasNpgsqlDbType)) - return new DbTypePair(ToAbstractDbType(att), att.UserDefinedTypeName); + internal DbTypePair? TryGetSqlDbType(DbTypeAttribute? att, Type type) + { + if (att != null && (att.HasSqlDbType || att.HasNpgsqlDbType)) + return new DbTypePair(ToAbstractDbType(att), att.UserDefinedTypeName); - return TryGetSqlDbTypePair(type.UnNullify()); - } + return TryGetSqlDbTypePair(type.UnNullify()); + } - internal int? GetSqlSize(DbTypeAttribute? att, PropertyRoute? route, AbstractDbType dbType) - { - if (this.IsPostgres && dbType.PostgreSql == NpgsqlDbType.Bytea) - return null; + internal int? GetSqlSize(DbTypeAttribute? att, PropertyRoute? route, AbstractDbType dbType) + { + if (this.IsPostgres && dbType.PostgreSql == NpgsqlDbType.Bytea) + return null; - if (att != null && att.HasSize) - return att.Size; + if (att != null && att.HasSize) + return att.Size; + + if (route != null && route.Type == typeof(string)) + { + var sla = ValidatorAttribute(route); + if (sla != null) + return sla.Max == -1 ? int.MaxValue : sla.Max; + } + + if (!this.IsPostgres) + return defaultSizeSqlServer.TryGetS(dbType.SqlServer); + else + return defaultSizePostgreSql.TryGetS(dbType.PostgreSql); + } - if (route != null && route.Type == typeof(string)) + internal byte? GetSqlPrecision(DbTypeAttribute? att, PropertyRoute? route, AbstractDbType dbType) { - var sla = ValidatorAttribute(route); - if (sla != null) - return sla.Max == -1 ? int.MaxValue : sla.Max; + if (this.IsPostgres && dbType.PostgreSql == NpgsqlDbType.Bytea) + return null; + + if (att != null && att.HasPrecision) + return att.Precision; + +/* if (route != null && route.Type == typeof(string)) + { + var sla = ValidatorAttribute(route); + if (sla != null) + return sla.Max == -1 ? int.MaxValue : sla.Max; + }*/ + + if (!this.IsPostgres) + return defaultPrecisionSqlServer.TryGetS(dbType.SqlServer); + else + return defaultPrecisionPostgreSql.TryGetS(dbType.PostgreSql); } - if (!this.IsPostgres) - return defaultSizeSqlServer.TryGetS(dbType.SqlServer); - else - return defaultSizePostgreSql.TryGetS(dbType.PostgreSql); - } + internal byte? GetSqlScale(DbTypeAttribute? att, PropertyRoute? route, AbstractDbType dbType) + { + bool isDecimal = dbType.IsDecimal(); + if (att != null && att.HasScale) + { + if (!isDecimal) + throw new InvalidOperationException($"{dbType} can not have Scale"); - internal int? GetSqlScale(DbTypeAttribute? att, PropertyRoute? route, AbstractDbType dbType) - { - bool isDecimal = dbType.IsDecimal(); - if (att != null && att.HasScale) + return att.Scale; + } + + if(isDecimal && route != null) + { + var dv = ValidatorAttribute(route); + if (dv != null) + return dv.DecimalPlaces; + } + + if (!this.IsPostgres) + return defaultScaleSqlServer.TryGetS(dbType.SqlServer); + else + return defaultScalePostgreSql.TryGetS(dbType.PostgreSql); + } + internal string? GetCollate(DbTypeAttribute? att) { - if (!isDecimal) - throw new InvalidOperationException($"{dbType} can not have Scale"); + if (att != null && att.Collation != null) + return att.Collation; - return att.Scale; + return null; } - if(isDecimal && route != null) + internal AbstractDbType DefaultSqlType(Type type) { - var dv = ValidatorAttribute(route); - if (dv != null) - return dv.DecimalPlaces; + return this.TypeValues.GetOrThrow(type, "Type {0} not registered"); } - if (!this.IsPostgres) - return defaultScaleSqlServer.TryGetS(dbType.SqlServer); - else - return defaultScalePostgreSql.TryGetS(dbType.PostgreSql); - } - internal string? GetCollate(DbTypeAttribute? att) - { - if (att != null && att.Collation != null) - return att.Collation; - - return null; - } + public DbTypePair GetSqlDbTypePair(Type type) + { + var result = TryGetSqlDbTypePair(type); + if (result == null) + throw new ArgumentException($"Type {type.Name} has no DB representation"); - internal AbstractDbType DefaultSqlType(Type type) - { - return this.TypeValues.GetOrThrow(type, "Type {0} not registered"); - } + return result; + } - public DbTypePair GetSqlDbTypePair(Type type) - { - var result = TryGetSqlDbTypePair(type); - if (result == null) - throw new ArgumentException($"Type {type.Name} has no DB representation"); + public DbTypePair? TryGetSqlDbTypePair(Type type) + { + if (TypeValues.TryGetValue(type, out AbstractDbType result)) + return new DbTypePair(result, null); - return result; - } + string? udtTypeName = GetUdtName(type); + if (udtTypeName != null) + return new DbTypePair(new AbstractDbType(SqlDbType.Udt), udtTypeName); - public DbTypePair? TryGetSqlDbTypePair(Type type) - { - if (TypeValues.TryGetValue(type, out AbstractDbType result)) - return new DbTypePair(result, null); + return null; + } - string? udtTypeName = GetUdtName(type); - if (udtTypeName != null) - return new DbTypePair(new AbstractDbType(SqlDbType.Udt), udtTypeName); + public string? GetUdtName(Type udtType) + { + var att = udtType.GetCustomAttribute(); - return null; - } + if (att == null) + return null; - public string? GetUdtName(Type udtType) - { - var att = udtType.GetCustomAttribute(); + return UdtSqlName[udtType]; + } - if (att == null) - return null; + public bool IsDbType(Type type) + { + return type.IsEnum || TryGetSqlDbTypePair(type) != null; + } - return UdtSqlName[udtType]; + public void RegisterCustomOrder(Expression> customOrder) where T : Entity + { + this.CustomOrder.Add(typeof(T), customOrder); + } } - public bool IsDbType(Type type) - { - return type.IsEnum || TryGetSqlDbTypePair(type) != null; - } - public void RegisterCustomOrder(Expression> customOrder) where T : Entity + + public class DbTypePair { - this.CustomOrder.Add(typeof(T), customOrder); + public AbstractDbType DbType { get; private set; } + public string? UserDefinedTypeName { get; private set; } + + public DbTypePair(AbstractDbType type, string? udtTypeName) + { + this.DbType = type; + this.UserDefinedTypeName = udtTypeName; + } } -} - - -public class DbTypePair -{ - public AbstractDbType DbType { get; private set; } - public string? UserDefinedTypeName { get; private set; } - - public DbTypePair(AbstractDbType type, string? udtTypeName) + public class AttributeCollection : Collection { - this.DbType = type; - this.UserDefinedTypeName = udtTypeName; - } -} + readonly AttributeTargets Targets; + readonly Action assertNotIncluded; -public class AttributeCollection : Collection -{ - readonly AttributeTargets Targets; - readonly Action assertNotIncluded; + public AttributeCollection(AttributeTargets targets, IList attributes, Action assertNotIncluded):base(attributes) + { + this.Targets = targets; + this.assertNotIncluded = assertNotIncluded; + } - public AttributeCollection(AttributeTargets targets, IList attributes, Action assertNotIncluded):base(attributes) - { - this.Targets = targets; - this.assertNotIncluded = assertNotIncluded; - } + protected override void InsertItem(int index, Attribute item) + { + assertNotIncluded(); - protected override void InsertItem(int index, Attribute item) - { - assertNotIncluded(); + if (!IsCompatibleWith(item, Targets)) + throw new InvalidOperationException("The attribute {0} is not compatible with targets {1}".FormatWith(item, Targets)); - if (!IsCompatibleWith(item, Targets)) - throw new InvalidOperationException("The attribute {0} is not compatible with targets {1}".FormatWith(item, Targets)); + base.InsertItem(index, item); + } - base.InsertItem(index, item); - } + static readonly Dictionary AttributeUssageCache = new Dictionary(); - static readonly Dictionary AttributeUssageCache = new Dictionary(); + public static bool IsCompatibleWith(Attribute a, AttributeTargets targets) + { + using (HeavyProfiler.LogNoStackTrace("IsCompatibleWith")) + { + var au = AttributeUssageCache.GetOrCreate(a.GetType(), t => t.GetCustomAttribute()!); - public static bool IsCompatibleWith(Attribute a, AttributeTargets targets) - { - using (HeavyProfiler.LogNoStackTrace("IsCompatibleWith")) + return au != null && (au.ValidOn & targets) != 0; + } + } + + public new AttributeCollection Add(Attribute attr) { - var au = AttributeUssageCache.GetOrCreate(a.GetType(), t => t.GetCustomAttribute()!); + base.Add(attr); - return au != null && (au.ValidOn & targets) != 0; + return this; } - } - public new AttributeCollection Add(Attribute attr) - { - base.Add(attr); + public AttributeCollection Replace(Attribute attr) + { + if (attr is ImplementedByAttribute || attr is ImplementedByAllAttribute) + this.RemoveAll(a => a is ImplementedByAttribute || a is ImplementedByAllAttribute); + else + this.RemoveAll(a => a.GetType() == attr.GetType()); - return this; - } + this.Add(attr); - public AttributeCollection Replace(Attribute attr) - { - if (attr is ImplementedByAttribute || attr is ImplementedByAllAttribute) - this.RemoveAll(a => a is ImplementedByAttribute || a is ImplementedByAllAttribute); - else - this.RemoveAll(a => a.GetType() == attr.GetType()); + return this; + } - this.Add(attr); + public AttributeCollection Remove() where A : Attribute + { + this.RemoveAll(a=>a is A); - return this; - } + return this; + } - public AttributeCollection Remove() where A : Attribute - { - this.RemoveAll(a=>a is A); + protected override void ClearItems() + { + assertNotIncluded(); - return this; - } + base.ClearItems(); + } - protected override void ClearItems() - { - assertNotIncluded(); + protected override void SetItem(int index, Attribute item) + { + assertNotIncluded(); - base.ClearItems(); - } + base.SetItem(index, item); + } - protected override void SetItem(int index, Attribute item) - { - assertNotIncluded(); + protected override void RemoveItem(int index) + { + assertNotIncluded(); - base.SetItem(index, item); + base.RemoveItem(index); + } } - protected override void RemoveItem(int index) + internal enum ReferenceFieldType { - assertNotIncluded(); - - base.RemoveItem(index); + Reference, + ImplementedBy, + ImplmentedByAll, } -} - -internal enum ReferenceFieldType -{ - Reference, - ImplementedBy, - ImplmentedByAll, -} diff --git a/Signum.Engine/Signum.Engine.csproj b/Signum.Engine/Signum.Engine.csproj index 0d8ce62727..2b2520c258 100644 --- a/Signum.Engine/Signum.Engine.csproj +++ b/Signum.Engine/Signum.Engine.csproj @@ -15,7 +15,7 @@ - + diff --git a/Signum.Entities/FieldAttributes.cs b/Signum.Entities/FieldAttributes.cs index 5867fda6ab..b586f91416 100644 --- a/Signum.Entities/FieldAttributes.cs +++ b/Signum.Entities/FieldAttributes.cs @@ -5,438 +5,446 @@ namespace Signum.Entities; -[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] -public sealed class UniqueIndexAttribute : Attribute -{ - public bool AllowMultipleNulls { get; set; } + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] + public sealed class UniqueIndexAttribute : Attribute + { + public bool AllowMultipleNulls { get; set; } - public bool AvoidAttachToUniqueIndexes { get; set; } -} + public bool AvoidAttachToUniqueIndexes { get; set; } + } -[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] -public sealed class AttachToUniqueIndexesAttribute : Attribute -{ -} + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] + public sealed class AttachToUniqueIndexesAttribute : Attribute + { + } public struct Implementations : IEquatable -{ - object? arrayOrType; - - public bool IsByAll { get { return arrayOrType == null; } } - public IEnumerable Types { - get + object? arrayOrType; + + public bool IsByAll { get { return arrayOrType == null; } } + public IEnumerable Types { - if (arrayOrType == null) - throw new InvalidOperationException("ImplementedByAll"); + get + { + if (arrayOrType == null) + throw new InvalidOperationException("ImplementedByAll"); - return Enumerate(); + return Enumerate(); + } } - } - private IEnumerable Enumerate() - { - if (arrayOrType is Type t) + private IEnumerable Enumerate() { - yield return t; - } - else if (arrayOrType is Type[] ts) - { - foreach (var item in ts) - yield return item; + if (arrayOrType is Type t) + { + yield return t; + } + else if (arrayOrType is Type[] ts) + { + foreach (var item in ts) + yield return item; + } + else + throw new InvalidOperationException("IsByAll"); } - else - throw new InvalidOperationException("IsByAll"); - } - public static Implementations? TryFromAttributes(Type t, PropertyRoute route, ImplementedByAttribute? ib, ImplementedByAllAttribute? iba) - { - if (ib != null && iba != null) - throw new NotSupportedException("Route {0} contains both {1} and {2}".FormatWith(route, ib.GetType().Name, iba.GetType().Name)); - - if (ib != null) return Implementations.By(ib.ImplementedTypes); - if (iba != null) return Implementations.ByAll; + public static Implementations? TryFromAttributes(Type t, PropertyRoute route, ImplementedByAttribute? ib, ImplementedByAllAttribute? iba) + { + if (ib != null && iba != null) + throw new NotSupportedException("Route {0} contains both {1} and {2}".FormatWith(route, ib.GetType().Name, iba.GetType().Name)); - if (Error(t) == null) - return Implementations.By(t); + if (ib != null) return Implementations.By(ib.ImplementedTypes); + if (iba != null) return Implementations.ByAll; - return null; - } + if (Error(t) == null) + return Implementations.By(t); + return null; + } - public static Implementations FromAttributes(Type t, PropertyRoute route, ImplementedByAttribute? ib, ImplementedByAllAttribute? iba) - { - Implementations? imp = TryFromAttributes(t, route, ib, iba); - if (imp == null) + public static Implementations FromAttributes(Type t, PropertyRoute route, ImplementedByAttribute? ib, ImplementedByAllAttribute? iba) { - var message = Error(t) + @". Set implementations for {0}.".FormatWith(route); + Implementations? imp = TryFromAttributes(t, route, ib, iba); - if (t.IsInterface || t.IsAbstract) + if (imp == null) { - message += @"\r\n" + ConsiderMessage(route, "typeof(YourConcrete" + t.TypeName() + ")"); + var message = Error(t) + @". Set implementations for {0}.".FormatWith(route); + + if (t.IsInterface || t.IsAbstract) + { + message += @"\r\n" + ConsiderMessage(route, "typeof(YourConcrete" + t.TypeName() + ")"); + } + + throw new InvalidOperationException(message); } - throw new InvalidOperationException(message); + return imp.Value; } - return imp.Value; - } - - internal static string ConsiderMessage(PropertyRoute route, string targetTypes) - { - return $@"Consider writing something like this in your Starter class: + internal static string ConsiderMessage(PropertyRoute route, string targetTypes) + { + return $@"Consider writing something like this in your Starter class: sb.Schema.Settings.FieldAttributes(({route.RootType.TypeName()} a) => a.{route.PropertyString().Replace("/", ".First().")}).Replace(new ImplementedByAttribute({targetTypes}))"; - } + } - public static Implementations ByAll { get { return new Implementations(); } } + public static Implementations ByAll { get { return new Implementations(); } } - public static Implementations By(Type type) - { - var error = Error(type); + public static Implementations By(Type type) + { + var error = Error(type); - if (error.HasText()) - throw new InvalidOperationException(error); + if (error.HasText()) + throw new InvalidOperationException(error); - return new Implementations { arrayOrType = type }; - } + return new Implementations { arrayOrType = type }; + } - public static Implementations By(params Type[] types) - { - if (types == null || types.Length == 0) - return new Implementations { arrayOrType = types ?? Array.Empty() }; + public static Implementations By(params Type[] types) + { + if (types == null || types.Length == 0) + return new Implementations { arrayOrType = types ?? Array.Empty() }; - if (types.Length == 1) - return By(types[0]); + if (types.Length == 1) + return By(types[0]); - var error = types.Select(Error).NotNull().ToString("\r\n"); + var error = types.Select(Error).NotNull().ToString("\r\n"); - if (error.HasText()) - throw new InvalidOperationException(error); + if (error.HasText()) + throw new InvalidOperationException(error); return new Implementations { arrayOrType = types.ToArray() }; - } + } - static string? Error(Type type) - { - if (type.IsInterface) - return "{0} is an interface".FormatWith(type.Name); + static string? Error(Type type) + { + if (type.IsInterface) + return "{0} is an interface".FormatWith(type.Name); - if (type.IsAbstract) - return "{0} is abstract".FormatWith(type.Name); + if (type.IsAbstract) + return "{0} is abstract".FormatWith(type.Name); - if (!type.IsEntity()) - return "{0} is not {1}".FormatWith(type.Name, typeof(Entity).Name); + if (!type.IsEntity()) + return "{0} is not {1}".FormatWith(type.Name, typeof(Entity).Name); - return null; - } + return null; + } - public string Key() - { - if (IsByAll) - return "[ALL]"; + public string Key() + { + if (IsByAll) + return "[ALL]"; - return Types.ToString(TypeEntity.GetCleanName, ", "); - } + return Types.ToString(TypeEntity.GetCleanName, ", "); + } - public override string ToString() - { - if (IsByAll) - return "ImplementedByAll"; + public override string ToString() + { + if (IsByAll) + return "ImplementedByAll"; - return "ImplementedBy({0})".FormatWith(Types.ToString(t => t.Name, ", ")); - } + return "ImplementedBy({0})".FormatWith(Types.ToString(t => t.Name, ", ")); + } - public override bool Equals(object? obj) - { - return obj is Implementations imp && Equals(imp); - } + public override bool Equals(object? obj) + { + return obj is Implementations imp && Equals(imp); + } - public bool Equals(Implementations other) - { - return IsByAll && other.IsByAll || - arrayOrType == other.arrayOrType || + public bool Equals(Implementations other) + { + return IsByAll && other.IsByAll || + arrayOrType == other.arrayOrType || Enumerable.SequenceEqual(Types.OrderBy(a => a.FullName), other.Types.OrderBy(a => a.FullName)); - } + } - public override int GetHashCode() - { - return arrayOrType == null ? 0 : Types.Aggregate(0, (acum, type) => acum ^ type.GetHashCode()); - } + public override int GetHashCode() + { + return arrayOrType == null ? 0 : Types.Aggregate(0, (acum, type) => acum ^ type.GetHashCode()); + } - public static bool operator ==(Implementations left, Implementations right) - { - return left.Equals(right); - } + public static bool operator ==(Implementations left, Implementations right) + { + return left.Equals(right); + } - public static bool operator !=(Implementations left, Implementations right) - { - return !(left == right); + public static bool operator !=(Implementations left, Implementations right) + { + return !(left == right); + } } -} [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] -public sealed class ImplementedByAttribute : Attribute -{ - Type[] implementedTypes; - - public Type[] ImplementedTypes + public sealed class ImplementedByAttribute : Attribute { - get { return implementedTypes; } - } + Type[] implementedTypes; - public ImplementedByAttribute(params Type[] types) - { - implementedTypes = types; + public Type[] ImplementedTypes + { + get { return implementedTypes; } + } + + public ImplementedByAttribute(params Type[] types) + { + implementedTypes = types; + } } -} [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] -public sealed class ImplementedByAllAttribute : Attribute -{ - public ImplementedByAllAttribute() + public sealed class ImplementedByAllAttribute : Attribute { + public ImplementedByAllAttribute() + { + } } -} -/// -/// Avoids that an Entity field has database representation (column or MList table) -/// -[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] -public sealed class IgnoreAttribute : Attribute -{ -} - -[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] -public sealed class FieldWithoutPropertyAttribute : Attribute -{ -} - -[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] -public sealed class ForceNotNullableAttribute : Attribute -{ -} - - -/// -/// Very rare. Reference types (classes) or Nullable are already nullable in the database. -/// This attribute is only necessary in the case an entity field is not-nullable but you can not make the DB column nullable because of legacy data, or cycles in a graph of entities. -/// -[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] -public sealed class ForceNullableAttribute: Attribute -{ -} - - -[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] -public class DbTypeAttribute : Attribute -{ - SqlDbType? sqlDbType; - public bool HasSqlDbType => sqlDbType.HasValue; - public SqlDbType SqlDbType + /// + /// Avoids that an Entity field has database representation (column or MList table) + /// + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] + public sealed class IgnoreAttribute : Attribute { - get { return sqlDbType!.Value; } - set { sqlDbType = value; } } - NpgsqlDbType? npgsqlDbType; - public bool HasNpgsqlDbType => npgsqlDbType.HasValue; - public NpgsqlDbType NpgsqlDbType + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] + public sealed class FieldWithoutPropertyAttribute : Attribute { - get { return npgsqlDbType!.Value; } - set { npgsqlDbType = value; } } - int? size; - public bool HasSize => size.HasValue; - public int Size + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] + public sealed class ForceNotNullableAttribute : Attribute { - get { return size!.Value; } - set { size = value; } } - int? scale; - public bool HasScale => scale.HasValue; - public int Scale + /// + /// Very rare. Reference types (classes) or Nullable are already nullable in the database. + /// This attribute is only necessary in the case an entity field is not-nullable but you can not make the DB column nullable because of legacy data, or cycles in a graph of entities. + /// + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] + public sealed class ForceNullableAttribute: Attribute { - get { return scale!.Value; } - set { scale = value; } } - public string? UserDefinedTypeName { get; set; } + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] + public class DbTypeAttribute : Attribute + { + SqlDbType? sqlDbType; + public bool HasSqlDbType => sqlDbType.HasValue; + public SqlDbType SqlDbType + { + get { return sqlDbType!.Value; } + set { sqlDbType = value; } + } + + NpgsqlDbType? npgsqlDbType; + public bool HasNpgsqlDbType => npgsqlDbType.HasValue; + public NpgsqlDbType NpgsqlDbType + { + get { return npgsqlDbType!.Value; } + set { npgsqlDbType = value; } + } + + int? size; + public bool HasSize => size.HasValue; + public int Size + { + get { return size!.Value; } + set { size = value; } + } - public string? Default { get; set; } + byte? precision; + public bool HasPrecision => precision.HasValue; + public byte Precision + { + get { return precision!.Value; } + set { precision = value; } + } - public string? DefaultSqlServer { get; set; } - public string? DefaultPostgres { get; set; } - public string? GetDefault(bool isPostgres) - { - return (isPostgres ? DefaultPostgres : DefaultSqlServer) ?? Default; - } + byte? scale; + public bool HasScale => scale.HasValue; + public byte Scale + { + get { return scale!.Value; } + set { scale = value; } + } - public string? Collation { get; set; } - public const string SqlServer_NewId = "NEWID()"; - public const string SqlServer_NewSequentialId = "NEWSEQUENTIALID()"; - public const string Postgres_UuidGenerateV1= "uuid_generate_v1()"; -} + public string? UserDefinedTypeName { get; set; } -[AttributeUsage(AttributeTargets.Class | AttributeTargets.Enum | AttributeTargets.Field | AttributeTargets.Property /*MList fields*/, Inherited = true, AllowMultiple = false)] -public sealed class PrimaryKeyAttribute : DbTypeAttribute -{ - public Type Type { get; set; } + public string? Default { get; set; } - public string Name { get; set; } + public string? DefaultSqlServer { get; set; } + public string? DefaultPostgres { get; set; } - public bool Identity { get; set; } + public string? GetDefault(bool isPostgres) + { + return (isPostgres ? DefaultPostgres : DefaultSqlServer) ?? Default; + } - bool identityBehaviour; + public string? Collation { get; set; } + + public const string SqlServer_NewId = "NEWID()"; + public const string SqlServer_NewSequentialId = "NEWSEQUENTIALID()"; + public const string Postgres_UuidGenerateV1= "uuid_generate_v1()"; + } - public bool IdentityBehaviour + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Enum | AttributeTargets.Field | AttributeTargets.Property /*MList fields*/, Inherited = true, AllowMultiple = false)] + public sealed class PrimaryKeyAttribute : DbTypeAttribute { - get { return identityBehaviour; } - set + public Type Type { get; set; } + + public string Name { get; set; } + + public bool Identity { get; set; } + + bool identityBehaviour; + + public bool IdentityBehaviour { - identityBehaviour = value; - if (Type == typeof(Guid) && identityBehaviour) + get { return identityBehaviour; } + set { - this.DefaultSqlServer = SqlServer_NewId; - this.DefaultPostgres = Postgres_UuidGenerateV1; + identityBehaviour = value; + if (Type == typeof(Guid) && identityBehaviour) + { + this.DefaultSqlServer = SqlServer_NewId; + this.DefaultPostgres = Postgres_UuidGenerateV1; + } } } + + public PrimaryKeyAttribute(Type type, string name = "ID") + { + this.Type = type; + this.Name = name; + this.Identity = type != typeof(Guid); + this.IdentityBehaviour = true; + } } - public PrimaryKeyAttribute(Type type, string name = "ID") + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, Inherited = true, AllowMultiple = false)] + public sealed class ColumnNameAttribute : Attribute { - this.Type = type; - this.Name = name; - this.Identity = type != typeof(Guid); - this.IdentityBehaviour = true; - } -} + public string Name { get; set; } -[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, Inherited = true, AllowMultiple = false)] -public sealed class ColumnNameAttribute : Attribute -{ - public string Name { get; set; } + public ColumnNameAttribute(string name) + { + this.Name = name; + } + } - public ColumnNameAttribute(string name) + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, Inherited = true, AllowMultiple = false)] + public sealed class BackReferenceColumnNameAttribute : Attribute { - this.Name = name; + public string Name { get; set; } + + public BackReferenceColumnNameAttribute(string name) + { + this.Name = name; + } } -} -[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, Inherited = true, AllowMultiple = false)] -public sealed class BackReferenceColumnNameAttribute : Attribute -{ - public string Name { get; set; } + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] + public sealed class ViewPrimaryKeyAttribute : Attribute + { + } - public BackReferenceColumnNameAttribute(string name) + [AttributeUsage(AttributeTargets.Class)] + public sealed class CacheViewMetadataAttribute : Attribute { - this.Name = name; } -} -[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] -public sealed class ViewPrimaryKeyAttribute : Attribute -{ -} - -[AttributeUsage(AttributeTargets.Class)] -public sealed class CacheViewMetadataAttribute : Attribute -{ -} - -[AttributeUsage(AttributeTargets.Class | AttributeTargets.Enum | AttributeTargets.Field | AttributeTargets.Property /*MList fields*/, Inherited = true, AllowMultiple = false)] -public sealed class TableNameAttribute : Attribute -{ - public string Name { get; set; } - public string? SchemaName { get; set; } - public string? DatabaseName { get; set; } - public string? ServerName { get; set; } - - public TableNameAttribute(string fullName) + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Enum | AttributeTargets.Field | AttributeTargets.Property /*MList fields*/, Inherited = true, AllowMultiple = false)] + public sealed class TableNameAttribute : Attribute { - var parts = fullName.Split('.'); - this.Name = parts.ElementAt(parts.Length - 1).Trim('[', ']'); - this.SchemaName = parts.ElementAtOrDefault(parts.Length - 2)?.Trim('[', ']'); - this.DatabaseName = parts.ElementAtOrDefault(parts.Length - 3)?.Trim('[', ']'); - this.ServerName = parts.ElementAtOrDefault(parts.Length - 4)?.Trim('[', ']'); + public string Name { get; set; } + public string? SchemaName { get; set; } + public string? DatabaseName { get; set; } + public string? ServerName { get; set; } + + public TableNameAttribute(string fullName) + { + var parts = fullName.Split('.'); + this.Name = parts.ElementAt(parts.Length - 1).Trim('[', ']'); + this.SchemaName = parts.ElementAtOrDefault(parts.Length - 2)?.Trim('[', ']'); + this.DatabaseName = parts.ElementAtOrDefault(parts.Length - 3)?.Trim('[', ']'); + this.ServerName = parts.ElementAtOrDefault(parts.Length - 4)?.Trim('[', ']'); + } } -} -[AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = false)] -public sealed class TicksColumnAttribute : DbTypeAttribute -{ - public bool HasTicks { get; private set; } + [AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = false)] + public sealed class TicksColumnAttribute : DbTypeAttribute + { + public bool HasTicks { get; private set; } - public string? Name { get; set; } + public string? Name { get; set; } - public Type? Type { get; set; } + public Type? Type { get; set; } - public TicksColumnAttribute(bool hasTicks = true) - { - this.HasTicks = hasTicks; + public TicksColumnAttribute(bool hasTicks = true) + { + this.HasTicks = hasTicks; + } } -} - -/// -/// Activates SQL Server 2016 Temporal Tables -/// -[AttributeUsage(AttributeTargets.Class | AttributeTargets.Field | AttributeTargets.Property /*MList fields*/, Inherited = true, AllowMultiple = false)] -public sealed class SystemVersionedAttribute : Attribute -{ - public string? TemporalTableName { get; set; } - public string StartDateColumnName { get; set; } = "SysStartDate"; - public string EndDateColumnName { get; set; } = "SysEndDate"; - public string PostgreeSysPeriodColumname { get; set; } = "sys_period"; -} -[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] -public class AvoidForeignKeyAttribute : Attribute -{ + /// + /// Activates SQL Server 2016 Temporal Tables + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Field | AttributeTargets.Property /*MList fields*/, Inherited = true, AllowMultiple = false)] + public sealed class SystemVersionedAttribute : Attribute + { + public string? TemporalTableName { get; set; } + public string StartDateColumnName { get; set; } = "SysStartDate"; + public string EndDateColumnName { get; set; } = "SysEndDate"; + public string PostgreeSysPeriodColumname { get; set; } = "sys_period"; + } -} + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] + public class AvoidForeignKeyAttribute : Attribute + { -[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] -public class AvoidExpandQueryAttribute : Attribute -{ + } -} + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] + public class AvoidExpandQueryAttribute : Attribute + { + } -[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] -public class CombineStrategyAttribute : Attribute -{ - public readonly CombineStrategy Strategy; - public CombineStrategyAttribute(CombineStrategy strategy) + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] + public class CombineStrategyAttribute : Attribute { - this.Strategy = strategy; - } -} + public readonly CombineStrategy Strategy; -public enum CombineStrategy -{ - Union, - Case, -} + public CombineStrategyAttribute(CombineStrategy strategy) + { + this.Strategy = strategy; + } + } -public static class LinqHintEntities -{ - public static T CombineCase(this T value) where T : IEntity + public enum CombineStrategy { - return value; + Union, + Case, } - public static T CombineUnion(this T value) where T : IEntity + public static class LinqHintEntities { - return value; + public static T CombineCase(this T value) where T : IEntity + { + return value; + } + + public static T CombineUnion(this T value) where T : IEntity + { + return value; + } } -} diff --git a/Signum.Entities/ValidationAttributes.cs b/Signum.Entities/ValidationAttributes.cs index f20ecce8f4..1adfd39274 100644 --- a/Signum.Entities/ValidationAttributes.cs +++ b/Signum.Entities/ValidationAttributes.cs @@ -8,782 +8,782 @@ namespace Signum.Entities; -[AttributeUsage(AttributeTargets.Property, Inherited = true, AllowMultiple = true)] -public abstract class ValidatorAttribute : Attribute -{ - public Func? IsApplicable; - public Func? ErrorMessage { get; set; } - - public string? UnlocalizableErrorMessage + [AttributeUsage(AttributeTargets.Property, Inherited = true, AllowMultiple = true)] + public abstract class ValidatorAttribute : Attribute { - get { return ErrorMessage == null ? null : ErrorMessage(); } - set { ErrorMessage = () => value!; } - } + public Func? IsApplicable; + public Func? ErrorMessage { get; set; } - public int Order { get; set; } + public string? UnlocalizableErrorMessage + { + get { return ErrorMessage == null ? null : ErrorMessage(); } + set { ErrorMessage = () => value!; } + } - public bool DisabledInModelBinder { get; set; } = false; + public int Order { get; set; } - //Descriptive information that continues the sentence: The property should {HelpMessage} - //Used for documentation purposes only - public abstract string HelpMessage { get; } + public bool DisabledInModelBinder { get; set; } = false; - public string? Error(ModifiableEntity entity, PropertyInfo property, object? value) - { - if (DisabledInModelBinder && Validator.InModelBinder) - return null; + //Descriptive information that continues the sentence: The property should {HelpMessage} + //Used for documentation purposes only + public abstract string HelpMessage { get; } - if (IsApplicable != null && !IsApplicable(entity)) - return null; + public string? Error(ModifiableEntity entity, PropertyInfo property, object? value) + { + if (DisabledInModelBinder && Validator.InModelBinder) + return null; - string? defaultError = OverrideError(value); + if (IsApplicable != null && !IsApplicable(entity)) + return null; - if (defaultError == null) - return null; + string? defaultError = OverrideError(value); - string error = ErrorMessage == null ? defaultError : ErrorMessage(); - if (error != null) - error = error.FormatWith(property.NiceName()); + if (defaultError == null) + return null; - return error; - } + string error = ErrorMessage == null ? defaultError : ErrorMessage(); + if (error != null) + error = error.FormatWith(property.NiceName()); + return error; + } - /// - /// When overriden, validates the value against this validator rule - /// - /// - /// returns an string with the error message, using {0} if you want the property name to be inserted - protected abstract string? OverrideError(object? value); -} -public class NotNullValidatorAttribute : ValidatorAttribute -{ - public bool Disabled { get; set; } + /// + /// When overriden, validates the value against this validator rule + /// + /// + /// returns an string with the error message, using {0} if you want the property name to be inserted + protected abstract string? OverrideError(object? value); + } - protected override string? OverrideError(object? obj) + public class NotNullValidatorAttribute : ValidatorAttribute { - if (Disabled) - return null; + public bool Disabled { get; set; } - if (obj == null || obj is string s && s == "") - return ValidationMessage._0IsNotSet.NiceToString(); + protected override string? OverrideError(object? obj) + { + if (Disabled) + return null; - return null; - } + if (obj == null || obj is string s && s == "") + return ValidationMessage._0IsNotSet.NiceToString(); + + return null; + } - public override string HelpMessage => ValidationMessage.BeNotNull.NiceToString(); + public override string HelpMessage => ValidationMessage.BeNotNull.NiceToString(); -} + } -public static class NotNullValidatorExtensions -{ - public static string? IsSetOnlyWhen(this (PropertyInfo pi, object? nullableValue) tuple, bool shouldBeSet) + public static class NotNullValidatorExtensions { - var isNull = tuple.nullableValue == null || - tuple.nullableValue is string s && string.IsNullOrEmpty(s) || - tuple.nullableValue is ICollection col && col.Count == 0; + public static string? IsSetOnlyWhen(this (PropertyInfo pi, object? nullableValue) tuple, bool shouldBeSet) + { + var isNull = tuple.nullableValue == null || + tuple.nullableValue is string s && string.IsNullOrEmpty(s) || + tuple.nullableValue is ICollection col && col.Count == 0; - if (isNull && shouldBeSet) - return ValidationMessage._0IsNotSet.NiceToString(tuple.pi.NiceName()); + if (isNull && shouldBeSet) + return ValidationMessage._0IsNotSet.NiceToString(tuple.pi.NiceName()); - else if (!isNull && !shouldBeSet) - return ValidationMessage._0ShouldBeNull.NiceToString(tuple.pi.NiceName()); + else if (!isNull && !shouldBeSet) + return ValidationMessage._0ShouldBeNull.NiceToString(tuple.pi.NiceName()); - return null; + return null; + } } -} - + -public class StringLengthValidatorAttribute : ValidatorAttribute -{ - bool? allowLeadingSpaces; - public bool AllowLeadingSpaces + public class StringLengthValidatorAttribute : ValidatorAttribute { - get { return allowLeadingSpaces ?? MultiLine; } - set { this.allowLeadingSpaces = value; } - } + bool? allowLeadingSpaces; + public bool AllowLeadingSpaces + { + get { return allowLeadingSpaces ?? MultiLine; } + set { this.allowLeadingSpaces = value; } + } - bool? allowTrailingSpaces; - public bool AllowTrailingSpaces - { - get { return allowTrailingSpaces ?? MultiLine; } - set { this.allowTrailingSpaces = value; } - } + bool? allowTrailingSpaces; + public bool AllowTrailingSpaces + { + get { return allowTrailingSpaces ?? MultiLine; } + set { this.allowTrailingSpaces = value; } + } - public bool MultiLine { get; set; } + public bool MultiLine { get; set; } - int min = -1; - public int Min - { - get { return min; } - set { min = value; } - } + int min = -1; + public int Min + { + get { return min; } + set { min = value; } + } - int max = -1; - public int Max - { - get { return max; } - set { max = value; } - } + int max = -1; + public int Max + { + get { return max; } + set { max = value; } + } - protected override string? OverrideError(object? value) - { - string val = (string)value!; + protected override string? OverrideError(object? value) + { + string val = (string)value!; - if (string.IsNullOrEmpty(val)) - return null; + if (string.IsNullOrEmpty(val)) + return null; - if(!MultiLine && (val.Contains('\n') || val.Contains('\r'))) - return ValidationMessage._0ShouldHaveJustOneLine.NiceToString(); + if(!MultiLine && (val.Contains('\n') || val.Contains('\r'))) + return ValidationMessage._0ShouldHaveJustOneLine.NiceToString(); - if (!AllowLeadingSpaces && Regex.IsMatch(val, @"^\s+")) - return ValidationMessage._0ShouldNotHaveInitialSpaces.NiceToString(); + if (!AllowLeadingSpaces && Regex.IsMatch(val, @"^\s+")) + return ValidationMessage._0ShouldNotHaveInitialSpaces.NiceToString(); - if (!AllowTrailingSpaces && Regex.IsMatch(val, @"\s+$")) - return ValidationMessage._0ShouldNotHaveFinalSpaces.NiceToString(); + if (!AllowTrailingSpaces && Regex.IsMatch(val, @"\s+$")) + return ValidationMessage._0ShouldNotHaveFinalSpaces.NiceToString(); - if (min == max && min != -1 && val.Length != min) - return ValidationMessage.TheLenghtOf0HasToBeEqualTo1.NiceToString("{0}", min); + if (min == max && min != -1 && val.Length != min) + return ValidationMessage.TheLenghtOf0HasToBeEqualTo1.NiceToString("{0}", min); - if (min != -1 && val.Length < min) - return ValidationMessage.TheLengthOf0HasToBeGreaterOrEqualTo1.NiceToString("{0}", min); + if (min != -1 && val.Length < min) + return ValidationMessage.TheLengthOf0HasToBeGreaterOrEqualTo1.NiceToString("{0}", min); - if (max != -1 && val.Length > max) - return ValidationMessage.TheLengthOf0HasToBeLesserOrEqualTo1.NiceToString("{0}", max); + if (max != -1 && val.Length > max) + return ValidationMessage.TheLengthOf0HasToBeLesserOrEqualTo1.NiceToString("{0}", max); - return null; - } + return null; + } - public override string HelpMessage - { - get + public override string HelpMessage { - string result = - min != -1 && max != -1 ? ValidationMessage.HaveBetween0And1Characters.NiceToString().FormatWith(min, max) : - min != -1 ? ValidationMessage.HaveMinimum0Characters.NiceToString().FormatWith(min) : - max != -1 ? ValidationMessage.HaveMaximum0Characters.NiceToString().FormatWith(max) : - MultiLine ? ValidationMessage.BeAMultilineString.NiceToString() : - ValidationMessage.BeAString.NiceToString(); - - return result; + get + { + string result = + min != -1 && max != -1 ? ValidationMessage.HaveBetween0And1Characters.NiceToString().FormatWith(min, max) : + min != -1 ? ValidationMessage.HaveMinimum0Characters.NiceToString().FormatWith(min) : + max != -1 ? ValidationMessage.HaveMaximum0Characters.NiceToString().FormatWith(max) : + MultiLine ? ValidationMessage.BeAMultilineString.NiceToString() : + ValidationMessage.BeAString.NiceToString(); + + return result; + } } } -} -public abstract class RegexValidatorAttribute : ValidatorAttribute -{ - Regex[] regexList; - public RegexValidatorAttribute(Regex regex) + public abstract class RegexValidatorAttribute : ValidatorAttribute { - this.regexList = new[] { regex }; - } + Regex[] regexList; + public RegexValidatorAttribute(Regex regex) + { + this.regexList = new[] { regex }; + } - public RegexValidatorAttribute(Regex[] regex) - { - this.regexList = regex; - } + public RegexValidatorAttribute(Regex[] regex) + { + this.regexList = regex; + } - public RegexValidatorAttribute(string regexExpresion) - { - this.regexList = new[] { new Regex(regexExpresion) }; - } + public RegexValidatorAttribute(string regexExpresion) + { + this.regexList = new[] { new Regex(regexExpresion) }; + } - public abstract string FormatName - { - get; - } + public abstract string FormatName + { + get; + } - protected override string? OverrideError(object? value) - { - string? str = (string?)value; - if (string.IsNullOrEmpty(str)) - return null; + protected override string? OverrideError(object? value) + { + string? str = (string?)value; + if (string.IsNullOrEmpty(str)) + return null; - if (regexList.Any(a => a.IsMatch(str))) - return null; + if (regexList.Any(a => a.IsMatch(str))) + return null; - return ValidationMessage._0DoesNotHaveAValid1Format.NiceToString().FormatWith("{0}", FormatName); + return ValidationMessage._0DoesNotHaveAValid1Format.NiceToString().FormatWith("{0}", FormatName); + } + + public override string HelpMessage => ValidationMessage.HaveValid0Format.NiceToString().FormatWith(FormatName); } - public override string HelpMessage => ValidationMessage.HaveValid0Format.NiceToString().FormatWith(FormatName); -} + public class EMailValidatorAttribute : RegexValidatorAttribute + { + public static Regex EmailRegex = new Regex( + @"^(([^<>()[\]\\.,;:\s@\""]+" + + @"(\.[^<>()[\]\\.,;:\s@\""]+)*)|(\"".+\""))@" + + @"((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}" + + @"\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+" + + @"[a-zA-Z]{2,}))$", RegexOptions.IgnoreCase); -public class EMailValidatorAttribute : RegexValidatorAttribute -{ - public static Regex EmailRegex = new Regex( - @"^(([^<>()[\]\\.,;:\s@\""]+" - + @"(\.[^<>()[\]\\.,;:\s@\""]+)*)|(\"".+\""))@" - + @"((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}" - + @"\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+" - + @"[a-zA-Z]{2,}))$", RegexOptions.IgnoreCase); + public EMailValidatorAttribute() + : base(EmailRegex) + { + } - public EMailValidatorAttribute() - : base(EmailRegex) - { + public override string FormatName + { + get { return "e-Mail"; } + } } - public override string FormatName + public class TelephoneValidatorAttribute : RegexValidatorAttribute { - get { return "e-Mail"; } - } -} + public static string BasicRegex = @"((\+)\p{Nd}\p{Nd})? *(\([ \p{Nd}]+\))?([ \p{Nd}]+ */)? *[\p{Nd}][ \-\.\p{Nd}]+"; -public class TelephoneValidatorAttribute : RegexValidatorAttribute -{ - public static string BasicRegex = @"((\+)\p{Nd}\p{Nd})? *(\([ \p{Nd}]+\))?([ \p{Nd}]+ */)? *[\p{Nd}][ \-\.\p{Nd}]+"; + public static Regex TelephoneRegex = new Regex($@"^{BasicRegex}$"); - public static Regex TelephoneRegex = new Regex($@"^{BasicRegex}$"); + public TelephoneValidatorAttribute() + : base(TelephoneRegex) + { + } - public TelephoneValidatorAttribute() - : base(TelephoneRegex) - { + public override string FormatName + { + get { return ValidationMessage.Telephone.NiceToString(); } + } } - - public override string FormatName + public class AlphanumericOnlyValidatorAttribute : RegexValidatorAttribute { - get { return ValidationMessage.Telephone.NiceToString(); } - } -} -public class AlphanumericOnlyValidatorAttribute : RegexValidatorAttribute -{ - public static Regex AlphanumericOnlyRegex = new Regex($@"[A-Za-z0-9]"); + public static Regex AlphanumericOnlyRegex = new Regex($@"[A-Za-z0-9]"); - public AlphanumericOnlyValidatorAttribute() - : base(AlphanumericOnlyRegex) - { + public AlphanumericOnlyValidatorAttribute() + : base(AlphanumericOnlyRegex) + { + } + + public override string FormatName + { + get { return ValidationMessage._0IsNotAllowed.NiceToString(@"Special characters (-_#+%&...)"); } + } } - public override string FormatName + public class MultipleTelephoneValidatorAttribute : RegexValidatorAttribute { - get { return ValidationMessage._0IsNotAllowed.NiceToString(@"Special characters (-_#+%&...)"); } - } -} + public static Regex MultipleTelephoneRegex = new Regex($@"^{TelephoneValidatorAttribute.BasicRegex}(,\s*{TelephoneValidatorAttribute.BasicRegex})*"); -public class MultipleTelephoneValidatorAttribute : RegexValidatorAttribute -{ - public static Regex MultipleTelephoneRegex = new Regex($@"^{TelephoneValidatorAttribute.BasicRegex}(,\s*{TelephoneValidatorAttribute.BasicRegex})*"); + public MultipleTelephoneValidatorAttribute() + : base(MultipleTelephoneRegex) + { + } - public MultipleTelephoneValidatorAttribute() - : base(MultipleTelephoneRegex) - { + public override string FormatName + { + get { return ValidationMessage.Telephone.NiceToString(); } + } } - public override string FormatName - { - get { return ValidationMessage.Telephone.NiceToString(); } - } -} - -public class IdentifierValidatorAttribute : RegexValidatorAttribute -{ - public static Regex PascalAscii = new Regex(@"^[A-Z[_a-zA-Z0-9]*$"); - public static Regex Ascii = new Regex(@"^[_a-zA-Z[_a-zA-Z0-9]*$"); - public static Regex International = new Regex(@"^[_\p{Ll}\p{Lu}\p{Lt}\p{Lo}\p{Nl}][_\p{Ll}\p{Lu}\p{Lt}\p{Lo}\p{Nl}\p{Nd}]*$"); - - public IdentifierType type; - public IdentifierValidatorAttribute(IdentifierType type) - : base( - type == IdentifierType.PascalAscii ? PascalAscii : - type == IdentifierType.Ascii ? Ascii: - type == IdentifierType.International ? International : - throw new UnexpectedValueException(type) - ) + public class IdentifierValidatorAttribute : RegexValidatorAttribute { - this.type = type; - } - - public override string FormatName => this.type.ToString(); -} - -public enum IdentifierType -{ - PascalAscii, - Ascii, - International -} + public static Regex PascalAscii = new Regex(@"^[A-Z[_a-zA-Z0-9]*$"); + public static Regex Ascii = new Regex(@"^[_a-zA-Z[_a-zA-Z0-9]*$"); + public static Regex International = new Regex(@"^[_\p{Ll}\p{Lu}\p{Lt}\p{Lo}\p{Nl}][_\p{Ll}\p{Lu}\p{Lt}\p{Lo}\p{Nl}\p{Nd}]*$"); -public class NumericTextValidatorAttribute : RegexValidatorAttribute -{ - public static Regex NumericTextRegex = new Regex(@"^[\p{Nd}]*$"); + public IdentifierType type; + public IdentifierValidatorAttribute(IdentifierType type) + : base( + type == IdentifierType.PascalAscii ? PascalAscii : + type == IdentifierType.Ascii ? Ascii: + type == IdentifierType.International ? International : + throw new UnexpectedValueException(type) + ) + { + this.type = type; + } - public NumericTextValidatorAttribute() - : base(NumericTextRegex) - { + public override string FormatName => this.type.ToString(); } - public override string FormatName - { - get { return ValidationMessage.Numeric.NiceToString(); } - } -} - -public class URLValidatorAttribute : RegexValidatorAttribute -{ - public static Regex AbsoluteUrlRegex = new Regex( - "^(https?://)" - + "?(([0-9a-z_!~*'().&=+$%-]+: )?[0-9a-z_!~*'().&=+$%-]+@)?" //user@ - + @"(([0-9]{1,3}\.){3}[0-9]{1,3}" // IP- 199.194.52.184 - + "|" // allows either IP or domain - + @"([0-9a-z_!~*'()-]+\.)*" // tertiary domain(s)- www. - + @"([0-9a-z][0-9a-z-]{0,61})?[0-9a-z]" // second level domain - + @"(\.[a-z]{2,6})?)" // first level domain- .com or .museum - + "(:[0-9]{1,4})?" // port number- :80 - + "((/?)|" // a slash isn't required if there is no file name - + "(/[0-9a-z_!~*'().;?:@&=+$,%#-]+)+/?)$", RegexOptions.IgnoreCase); - - public static Regex SiteRelativeRegex = new Regex( - "^/|(/[0-9a-z_!~*'().;?:@&=+$,%#-]+)+$", RegexOptions.IgnoreCase); - - public static Regex AspNetRelativeRegex = new Regex( - "^~(/|(/[0-9a-z_!~*'().;?:@&=+$,%#-]+)+)$", RegexOptions.IgnoreCase); - - - public static Regex DocumentRelativeRegex = new Regex( - "^[0-9a-z_!~*'().;?:@&=+$,%#-](/[0-9a-z_!~*'().;?:@&=+$,%#-]+)+$", RegexOptions.IgnoreCase); - - public URLValidatorAttribute(bool absolute = true, bool siteRelative = false, bool aspNetSiteRelative = false, bool documentRelative = false) - : base(new []{ - absolute ? AbsoluteUrlRegex: null, - siteRelative ? SiteRelativeRegex: null, - aspNetSiteRelative ? AspNetRelativeRegex: null, - documentRelative ? DocumentRelativeRegex: null, - }.NotNull().ToArray()) + public enum IdentifierType { + PascalAscii, + Ascii, + International } - public override string FormatName + public class NumericTextValidatorAttribute : RegexValidatorAttribute { - get { return "URL"; } - } -} + public static Regex NumericTextRegex = new Regex(@"^[\p{Nd}]*$"); -public class AzureStorageCollectionNameValidationAttribute : RegexValidatorAttribute -{ - static Regex CollectionNameRegex = new Regex(@"^[a-z\-]+"); + public NumericTextValidatorAttribute() + : base(NumericTextRegex) + { + } - public AzureStorageCollectionNameValidationAttribute() : base(CollectionNameRegex) - { + public override string FormatName + { + get { return ValidationMessage.Numeric.NiceToString(); } + } } - public override string FormatName => "Azure Storage Collection Name"; -} - -public class FileNameValidatorAttribute : ValidatorAttribute -{ - public static char[] InvalidCharts = Path.GetInvalidPathChars(); - - static Regex invalidChartsRegex = new Regex("[" + Regex.Escape(new string(Path.GetInvalidFileNameChars())) + "]"); - - public FileNameValidatorAttribute() + public class URLValidatorAttribute : RegexValidatorAttribute { - } + public static Regex AbsoluteUrlRegex = new Regex( + "^(https?://)" + + "?(([0-9a-z_!~*'().&=+$%-]+: )?[0-9a-z_!~*'().&=+$%-]+@)?" //user@ + + @"(([0-9]{1,3}\.){3}[0-9]{1,3}" // IP- 199.194.52.184 + + "|" // allows either IP or domain + + @"([0-9a-z_!~*'()-]+\.)*" // tertiary domain(s)- www. + + @"([0-9a-z][0-9a-z-]{0,61})?[0-9a-z]" // second level domain + + @"(\.[a-z]{2,6})?)" // first level domain- .com or .museum + + "(:[0-9]{1,4})?" // port number- :80 + + "((/?)|" // a slash isn't required if there is no file name + + "(/[0-9a-z_!~*'().;?:@&=+$,%#-]+)+/?)$", RegexOptions.IgnoreCase); - public string FormatName => ValidationMessage.FileName.NiceToString(); + public static Regex SiteRelativeRegex = new Regex( + "^/|(/[0-9a-z_!~*'().;?:@&=+$,%#-]+)+$", RegexOptions.IgnoreCase); - public override string HelpMessage => ValidationMessage.HaveValid0Format.NiceToString().FormatWith(FormatName); + public static Regex AspNetRelativeRegex = new Regex( + "^~(/|(/[0-9a-z_!~*'().;?:@&=+$,%#-]+)+)$", RegexOptions.IgnoreCase); - protected override string? OverrideError(object? value) - { - string? str = (string?)value; - if (str == null) - return null; + public static Regex DocumentRelativeRegex = new Regex( + "^[0-9a-z_!~*'().;?:@&=+$,%#-](/[0-9a-z_!~*'().;?:@&=+$,%#-]+)+$", RegexOptions.IgnoreCase); - if (str.IndexOfAny(InvalidCharts) == -1) - return null; + public URLValidatorAttribute(bool absolute = true, bool siteRelative = false, bool aspNetSiteRelative = false, bool documentRelative = false) + : base(new []{ + absolute ? AbsoluteUrlRegex: null, + siteRelative ? SiteRelativeRegex: null, + aspNetSiteRelative ? AspNetRelativeRegex: null, + documentRelative ? DocumentRelativeRegex: null, + }.NotNull().ToArray()) + { + } - return ValidationMessage._0DoesNotHaveAValid1Format.NiceToString().FormatWith("{0}", FormatName); + public override string FormatName + { + get { return "URL"; } + } } - public static string RemoveInvalidCharts(string a) + public class AzureStorageCollectionNameValidationAttribute : RegexValidatorAttribute { - return invalidChartsRegex.Replace(a, ""); - } -} + static Regex CollectionNameRegex = new Regex(@"^[a-z\-]+"); -public class DecimalsValidatorAttribute : ValidatorAttribute -{ - public int DecimalPlaces { get; set; } + public AzureStorageCollectionNameValidationAttribute() : base(CollectionNameRegex) + { + } - public DecimalsValidatorAttribute() - { - DecimalPlaces = 2; + public override string FormatName => "Azure Storage Collection Name"; } - public DecimalsValidatorAttribute(int decimalPlaces) + public class FileNameValidatorAttribute : ValidatorAttribute { - this.DecimalPlaces = decimalPlaces; - } + public static char[] InvalidCharts = Path.GetInvalidPathChars(); - protected override string? OverrideError(object? value) - { - if (value == null) - return null; + static Regex invalidChartsRegex = new Regex("[" + Regex.Escape(new string(Path.GetInvalidFileNameChars())) + "]"); - if (value is decimal && Math.Round((decimal)value, DecimalPlaces) != (decimal)value) + public FileNameValidatorAttribute() { - return ValidationMessage._0HasMoreThan1DecimalPlaces.NiceToString("{0}", DecimalPlaces); } - return null; - } + public string FormatName => ValidationMessage.FileName.NiceToString(); - public override string HelpMessage => ValidationMessage.Have0Decimals.NiceToString().FormatWith(DecimalPlaces); -} + public override string HelpMessage => ValidationMessage.HaveValid0Format.NiceToString().FormatWith(FormatName); + protected override string? OverrideError(object? value) + { + string? str = (string?)value; -public class NumberIsValidatorAttribute : ValidatorAttribute -{ - public ComparisonType ComparisonType; - public IComparable number; + if (str == null) + return null; - public NumberIsValidatorAttribute(ComparisonType comparison, float number) - { - this.ComparisonType = comparison; - this.number = number; - } + if (str.IndexOfAny(InvalidCharts) == -1) + return null; - public NumberIsValidatorAttribute(ComparisonType comparison, double number) - { - this.ComparisonType = comparison; - this.number = number; - } + return ValidationMessage._0DoesNotHaveAValid1Format.NiceToString().FormatWith("{0}", FormatName); + } - public NumberIsValidatorAttribute(ComparisonType comparison, byte number) - { - this.ComparisonType = comparison; - this.number = number; + public static string RemoveInvalidCharts(string a) + { + return invalidChartsRegex.Replace(a, ""); + } } - public NumberIsValidatorAttribute(ComparisonType comparison, short number) + public class DecimalsValidatorAttribute : ValidatorAttribute { - this.ComparisonType = comparison; - this.number = number; - } + public byte DecimalPlaces { get; set; } - public NumberIsValidatorAttribute(ComparisonType comparison, int number) - { - this.ComparisonType = comparison; - this.number = number; - } + public DecimalsValidatorAttribute() + { + DecimalPlaces = 2; + } - public NumberIsValidatorAttribute(ComparisonType comparison, long number) - { - this.ComparisonType = comparison; - this.number = number; + public DecimalsValidatorAttribute(byte decimalPlaces) + { + this.DecimalPlaces = decimalPlaces; + } + + protected override string? OverrideError(object? value) + { + if (value == null) + return null; + + if (value is decimal && Math.Round((decimal)value, DecimalPlaces) != (decimal)value) + { + return ValidationMessage._0HasMoreThan1DecimalPlaces.NiceToString("{0}", DecimalPlaces); + } + + return null; + } + + public override string HelpMessage => ValidationMessage.Have0Decimals.NiceToString().FormatWith(DecimalPlaces); } - protected override string? OverrideError(object? value) + + public class NumberIsValidatorAttribute : ValidatorAttribute { - if (value == null) - return null; + public ComparisonType ComparisonType; + public IComparable number; - IComparable val = (IComparable)value; + public NumberIsValidatorAttribute(ComparisonType comparison, float number) + { + this.ComparisonType = comparison; + this.number = number; + } - if (number.GetType() != value.GetType()) - number = (IComparable)Convert.ChangeType(number, value.GetType()); // made just once + public NumberIsValidatorAttribute(ComparisonType comparison, double number) + { + this.ComparisonType = comparison; + this.number = number; + } - bool ok = (ComparisonType == ComparisonType.EqualTo && val.CompareTo(number) == 0) || - (ComparisonType == ComparisonType.DistinctTo && val.CompareTo(number) != 0) || - (ComparisonType == ComparisonType.GreaterThan && val.CompareTo(number) > 0) || - (ComparisonType == ComparisonType.GreaterThanOrEqualTo && val.CompareTo(number) >= 0) || - (ComparisonType == ComparisonType.LessThan && val.CompareTo(number) < 0) || - (ComparisonType == ComparisonType.LessThanOrEqualTo && val.CompareTo(number) <= 0); + public NumberIsValidatorAttribute(ComparisonType comparison, byte number) + { + this.ComparisonType = comparison; + this.number = number; + } - if (ok) - return null; + public NumberIsValidatorAttribute(ComparisonType comparison, short number) + { + this.ComparisonType = comparison; + this.number = number; + } - return ValidationMessage._0ShouldBe12.NiceToString().FormatWith("{0}", ComparisonType.NiceToString().ToLower(), number.ToString()); - } + public NumberIsValidatorAttribute(ComparisonType comparison, int number) + { + this.ComparisonType = comparison; + this.number = number; + } - public override string HelpMessage => ValidationMessage.Be0.NiceToString(ComparisonType.NiceToString().ToLower() + " " + number.ToString()); -} + public NumberIsValidatorAttribute(ComparisonType comparison, long number) + { + this.ComparisonType = comparison; + this.number = number; + } -//Not using C intervals to please user! -public class NumberBetweenValidatorAttribute : ValidatorAttribute -{ - IComparable min; - IComparable max; + protected override string? OverrideError(object? value) + { + if (value == null) + return null; - public NumberBetweenValidatorAttribute(float min, float max) - { - this.min = min; - this.max = max; - } + IComparable val = (IComparable)value; - public NumberBetweenValidatorAttribute(double min, double max) - { - this.min = min; - this.max = max; - } + if (number.GetType() != value.GetType()) + number = (IComparable)Convert.ChangeType(number, value.GetType()); // made just once - public NumberBetweenValidatorAttribute(byte min, byte max) - { - this.min = min; - this.max = max; - } + bool ok = (ComparisonType == ComparisonType.EqualTo && val.CompareTo(number) == 0) || + (ComparisonType == ComparisonType.DistinctTo && val.CompareTo(number) != 0) || + (ComparisonType == ComparisonType.GreaterThan && val.CompareTo(number) > 0) || + (ComparisonType == ComparisonType.GreaterThanOrEqualTo && val.CompareTo(number) >= 0) || + (ComparisonType == ComparisonType.LessThan && val.CompareTo(number) < 0) || + (ComparisonType == ComparisonType.LessThanOrEqualTo && val.CompareTo(number) <= 0); - public NumberBetweenValidatorAttribute(short min, short max) - { - this.min = min; - this.max = max; - } + if (ok) + return null; - public NumberBetweenValidatorAttribute(int min, int max) - { - this.min = min; - this.max = max; - } + return ValidationMessage._0ShouldBe12.NiceToString().FormatWith("{0}", ComparisonType.NiceToString().ToLower(), number.ToString()); + } - public NumberBetweenValidatorAttribute(long min, long max) - { - this.min = min; - this.max = max; + public override string HelpMessage => ValidationMessage.Be0.NiceToString(ComparisonType.NiceToString().ToLower() + " " + number.ToString()); } - protected override string? OverrideError(object? value) + //Not using C intervals to please user! + public class NumberBetweenValidatorAttribute : ValidatorAttribute { - if (value == null) - return null; - - IComparable val = (IComparable)value; + IComparable min; + IComparable max; - if (min.GetType() != value.GetType()) + public NumberBetweenValidatorAttribute(float min, float max) { - min = (IComparable)Convert.ChangeType(min, val.GetType()); // asi se hace solo una vez - max = (IComparable)Convert.ChangeType(max, val.GetType()); + this.min = min; + this.max = max; } - if (min.CompareTo(val) <= 0 && - val.CompareTo(max) <= 0) - return null; + public NumberBetweenValidatorAttribute(double min, double max) + { + this.min = min; + this.max = max; + } - return ValidationMessage._0HasToBeBetween1And2.NiceToString("{0}", min, max); - } + public NumberBetweenValidatorAttribute(byte min, byte max) + { + this.min = min; + this.max = max; + } - public override string HelpMessage => ValidationMessage.BeBetween0And1.NiceToString(min, max); -} + public NumberBetweenValidatorAttribute(short min, short max) + { + this.min = min; + this.max = max; + } + public NumberBetweenValidatorAttribute(int min, int max) + { + this.min = min; + this.max = max; + } -public class NumberPowerOfTwoValidatorAttribute : ValidatorAttribute -{ - static bool IsPowerOfTwo(long n) - { - if (n == 0) - return false; + public NumberBetweenValidatorAttribute(long min, long max) + { + this.min = min; + this.max = max; + } - while (n != 1) + protected override string? OverrideError(object? value) { - if (n % 2 != 0) - return false; + if (value == null) + return null; - n = n / 2; + IComparable val = (IComparable)value; + + if (min.GetType() != value.GetType()) + { + min = (IComparable)Convert.ChangeType(min, val.GetType()); // asi se hace solo una vez + max = (IComparable)Convert.ChangeType(max, val.GetType()); + } + + if (min.CompareTo(val) <= 0 && + val.CompareTo(max) <= 0) + return null; + + return ValidationMessage._0HasToBeBetween1And2.NiceToString("{0}", min, max); } - return true; + + public override string HelpMessage => ValidationMessage.BeBetween0And1.NiceToString(min, max); } - protected override string? OverrideError(object? value) + + public class NumberPowerOfTwoValidatorAttribute : ValidatorAttribute { - if (value == null) - return null; + static bool IsPowerOfTwo(long n) + { + if (n == 0) + return false; - if (!IsPowerOfTwo(Convert.ToInt64(value))) - return ValidationMessage._0ShouldBe12.NiceToString().FormatWith("{0}", ValidationMessage.PowerOf.NiceToString(), 2); + while (n != 1) + { + if (n % 2 != 0) + return false; - return null; - } + n = n / 2; + } + return true; + } + + protected override string? OverrideError(object? value) + { + if (value == null) + return null; - public override string HelpMessage => ValidationMessage.Be0.NiceToString(ValidationMessage.PowerOf.NiceToString() + " " + 2); -} + if (!IsPowerOfTwo(Convert.ToInt64(value))) + return ValidationMessage._0ShouldBe12.NiceToString().FormatWith("{0}", ValidationMessage.PowerOf.NiceToString(), 2); -public class NoRepeatValidatorAttribute : ValidatorAttribute -{ - protected override string? OverrideError(object? value) - { - IList? list = (IList?)value; - if (list == null || list.Count <= 1) return null; - string ex = list.Cast().GroupCount().Where(kvp => kvp.Value > 1).ToString(e => "{0} x {1}".FormatWith(e.Key, e.Value), ", "); - if (ex.HasText()) - return ValidationMessage._0HasSomeRepeatedElements1.NiceToString("{0}", ex); - return null; - } + } - public override string HelpMessage => ValidationMessage.HaveNoRepeatedElements.NiceToString(); + public override string HelpMessage => ValidationMessage.Be0.NiceToString(ValidationMessage.PowerOf.NiceToString() + " " + 2); + } - public static string? ByKey(IEnumerable collection, Func keySelector) + public class NoRepeatValidatorAttribute : ValidatorAttribute { - var errors = collection.GroupBy(keySelector) - .Select(gr => new { gr.Key, Count = gr.Count() }) - .Where(a => a.Count > 1) - .ToString(e => "{0} x {1}".FormatWith(e.Key, e.Count), ", "); + protected override string? OverrideError(object? value) + { + IList? list = (IList?)value; + if (list == null || list.Count <= 1) + return null; + string ex = list.Cast().GroupCount().Where(kvp => kvp.Value > 1).ToString(e => "{0} x {1}".FormatWith(e.Key, e.Value), ", "); + if (ex.HasText()) + return ValidationMessage._0HasSomeRepeatedElements1.NiceToString("{0}", ex); + return null; + } - return errors.DefaultText(null!); - } -} + public override string HelpMessage => ValidationMessage.HaveNoRepeatedElements.NiceToString(); -public class CountIsValidatorAttribute : ValidatorAttribute -{ - public ComparisonType ComparisonType; - public int Number; + public static string? ByKey(IEnumerable collection, Func keySelector) + { + var errors = collection.GroupBy(keySelector) + .Select(gr => new { gr.Key, Count = gr.Count() }) + .Where(a => a.Count > 1) + .ToString(e => "{0} x {1}".FormatWith(e.Key, e.Count), ", "); - public CountIsValidatorAttribute(ComparisonType comparison, int number) - { - this.ComparisonType = comparison; - this.Number = number; + return errors.DefaultText(null!); + } } - protected override string? OverrideError(object? value) + public class CountIsValidatorAttribute : ValidatorAttribute { - IList? list = (IList?)value; + public ComparisonType ComparisonType; + public int Number; - int val = list == null ? 0 : list.Count; + public CountIsValidatorAttribute(ComparisonType comparison, int number) + { + this.ComparisonType = comparison; + this.Number = number; + } - if ((ComparisonType == ComparisonType.EqualTo && val.CompareTo(Number) == 0) || - (ComparisonType == ComparisonType.DistinctTo && val.CompareTo(Number) != 0) || - (ComparisonType == ComparisonType.GreaterThan && val.CompareTo(Number) > 0) || - (ComparisonType == ComparisonType.GreaterThanOrEqualTo && val.CompareTo(Number) >= 0) || - (ComparisonType == ComparisonType.LessThan && val.CompareTo(Number) < 0) || - (ComparisonType == ComparisonType.LessThanOrEqualTo && val.CompareTo(Number) <= 0)) - return null; + protected override string? OverrideError(object? value) + { + IList? list = (IList?)value; - return ValidationMessage.TheNumberOfElementsOf0HasToBe12.NiceToString().FormatWith("{0}", ComparisonType.NiceToString().FirstLower(), Number.ToString()); - } + int val = list == null ? 0 : list.Count; + + if ((ComparisonType == ComparisonType.EqualTo && val.CompareTo(Number) == 0) || + (ComparisonType == ComparisonType.DistinctTo && val.CompareTo(Number) != 0) || + (ComparisonType == ComparisonType.GreaterThan && val.CompareTo(Number) > 0) || + (ComparisonType == ComparisonType.GreaterThanOrEqualTo && val.CompareTo(Number) >= 0) || + (ComparisonType == ComparisonType.LessThan && val.CompareTo(Number) < 0) || + (ComparisonType == ComparisonType.LessThanOrEqualTo && val.CompareTo(Number) <= 0)) + return null; - public bool IsGreaterThanZero => - ComparisonType == ComparisonType.GreaterThan && Number == 0 || - ComparisonType == ComparisonType.GreaterThanOrEqualTo && Number == 1; + return ValidationMessage.TheNumberOfElementsOf0HasToBe12.NiceToString().FormatWith("{0}", ComparisonType.NiceToString().FirstLower(), Number.ToString()); + } + + public bool IsGreaterThanZero => + ComparisonType == ComparisonType.GreaterThan && Number == 0 || + ComparisonType == ComparisonType.GreaterThanOrEqualTo && Number == 1; - public override string HelpMessage => ValidationMessage.HaveANumberOfElements01.NiceToString().FormatWith(ComparisonType.NiceToString().FirstLower(), Number.ToString()); -} + public override string HelpMessage => ValidationMessage.HaveANumberOfElements01.NiceToString().FormatWith(ComparisonType.NiceToString().FirstLower(), Number.ToString()); + } [InTypeScript(true)] -[DescriptionOptions(DescriptionOptions.Members)] -public enum ComparisonType -{ - EqualTo, - DistinctTo, - GreaterThan, - GreaterThanOrEqualTo, - LessThan, - LessThanOrEqualTo, -} - -public class DateTimePrecisionValidatorAttribute : ValidatorAttribute -{ - public DateTimePrecision Precision { get; private set; } - - public DateTimePrecisionValidatorAttribute(DateTimePrecision precision) + [DescriptionOptions(DescriptionOptions.Members)] + public enum ComparisonType { - this.Precision = precision; + EqualTo, + DistinctTo, + GreaterThan, + GreaterThanOrEqualTo, + LessThan, + LessThanOrEqualTo, } - protected override string? OverrideError(object? value) + public class DateTimePrecisionValidatorAttribute : ValidatorAttribute { - if (value == null) - return null; + public DateTimePrecision Precision { get; private set; } + public DateTimePrecisionValidatorAttribute(DateTimePrecision precision) + { + this.Precision = precision; + } - var dto = ToDateTime(value); + protected override string? OverrideError(object? value) + { + if (value == null) + return null; - var prec = dto.GetPrecision(); - if (prec > Precision) - return ValidationMessage._0HasAPrecisionOf1InsteadOf2.NiceToString("{0}", prec, Precision); - return null; - } + var dto = ToDateTime(value); - public string FormatString - { - get + var prec = dto.GetPrecision(); + if (prec > Precision) + return ValidationMessage._0HasAPrecisionOf1InsteadOf2.NiceToString("{0}", prec, Precision); + + return null; + } + + public string FormatString { - var dtfi = CultureInfo.CurrentCulture.DateTimeFormat; - switch (Precision) + get { - case DateTimePrecision.Days: return "d"; - case DateTimePrecision.Hours: return dtfi.ShortDatePattern + " " + "HH"; - case DateTimePrecision.Minutes: return "g"; - case DateTimePrecision.Seconds: return "G"; - case DateTimePrecision.Milliseconds: return dtfi.ShortDatePattern + " " + dtfi.LongTimePattern.Replace("ss", "ss.fff"); - default: return ""; + var dtfi = CultureInfo.CurrentCulture.DateTimeFormat; + switch (Precision) + { + case DateTimePrecision.Days: return "d"; + case DateTimePrecision.Hours: return dtfi.ShortDatePattern + " " + "HH"; + case DateTimePrecision.Minutes: return "g"; + case DateTimePrecision.Seconds: return "G"; + case DateTimePrecision.Milliseconds: return dtfi.ShortDatePattern + " " + dtfi.LongTimePattern.Replace("ss", "ss.fff"); + default: return ""; + } } } - } - internal static DateTime ToDateTime(object? value) - { - return value is DateTime dt ? dt : + internal static DateTime ToDateTime(object? value) + { + return value is DateTime dt ? dt : value is DateOnly d ? d.ToDateTime() : - value is DateTimeOffset dto ? dto.DateTime : - throw new UnexpectedValueException(value); - } + value is DateTimeOffset dto ? dto.DateTime : + throw new UnexpectedValueException(value); + } - public override string HelpMessage => ValidationMessage.HaveAPrecisionOf0.NiceToString(Precision.NiceToString().ToLower()); -} + public override string HelpMessage => ValidationMessage.HaveAPrecisionOf0.NiceToString(Precision.NiceToString().ToLower()); + } -public class DateInPastValidatorAttribute : ValidatorAttribute -{ - protected override string? OverrideError(object? value) + public class DateInPastValidatorAttribute : ValidatorAttribute { - if (value == null) - return null; + protected override string? OverrideError(object? value) + { + if (value == null) + return null; - DateTime dateTime = DateTimePrecisionValidatorAttribute.ToDateTime(value); + DateTime dateTime = DateTimePrecisionValidatorAttribute.ToDateTime(value); if (dateTime > Clock.Now) - return ValidationMessage._0ShouldBeADateInThePast.NiceToString(); + return ValidationMessage._0ShouldBeADateInThePast.NiceToString(); - return null; - } - - - - public override string HelpMessage => ValidationMessage.BeInThePast.NiceToString(); -} + return null; + } -public class YearGreaterThanValidatorAttribute : ValidatorAttribute -{ - public int MinYear { get; set; } + - public YearGreaterThanValidatorAttribute(int minYear) - { - this.MinYear = minYear; + public override string HelpMessage => ValidationMessage.BeInThePast.NiceToString(); } - protected override string? OverrideError(object? value) + public class YearGreaterThanValidatorAttribute : ValidatorAttribute { - if (value == null) - return null; + public int MinYear { get; set; } - DateTime dateTime = DateTimePrecisionValidatorAttribute.ToDateTime(value); + public YearGreaterThanValidatorAttribute(int minYear) + { + this.MinYear = minYear; + } + protected override string? OverrideError(object? value) + { + if (value == null) + return null; - if (dateTime.Year < MinYear) - return ValidationMessage._0ShouldBe12.NiceToString("{0}", ComparisonType.GreaterThan.NiceToString(), MinYear); + DateTime dateTime = DateTimePrecisionValidatorAttribute.ToDateTime(value); - return null; - } - public override string HelpMessage => ValidationMessage.BeInThePast.NiceToString(); -} + if (dateTime.Year < MinYear) + return ValidationMessage._0ShouldBe12.NiceToString("{0}", ComparisonType.GreaterThan.NiceToString(), MinYear); + + return null; + } + + public override string HelpMessage => ValidationMessage.BeInThePast.NiceToString(); + } public class TimePrecisionValidatorAttribute : ValidatorAttribute -{ - public DateTimePrecision Precision { get; private set; } + { + public DateTimePrecision Precision { get; private set; } public TimePrecisionValidatorAttribute(DateTimePrecision precision) - { - this.Precision = precision; - } + { + this.Precision = precision; + } - protected override string? OverrideError(object? value) - { - if (value == null) - return null; + protected override string? OverrideError(object? value) + { + if (value == null) + return null; if (value is TimeSpan ts) { @@ -801,23 +801,23 @@ public TimePrecisionValidatorAttribute(DateTimePrecision precision) return "{0} has a precision of {1} instead of {2}".FormatWith("{0}", prec, Precision); } - return null; - } + return null; + } public string FormatString_TimeSpan - { - get { - switch (Precision) + get { - case DateTimePrecision.Hours: return "hh"; - case DateTimePrecision.Minutes: return @"hh\:mm"; - case DateTimePrecision.Seconds: return @"hh\:mm\:ss"; - case DateTimePrecision.Milliseconds: return "c"; - default: return ""; + switch (Precision) + { + case DateTimePrecision.Hours: return "hh"; + case DateTimePrecision.Minutes: return @"hh\:mm"; + case DateTimePrecision.Seconds: return @"hh\:mm\:ss"; + case DateTimePrecision.Milliseconds: return "c"; + default: return ""; + } } } - } public string FormatString_TimeOnly { @@ -832,365 +832,365 @@ public string FormatString_TimeOnly default: return ""; } } - } + } public override string HelpMessage => ValidationMessage.HaveAPrecisionOf0.NiceToString(Precision.NiceToString().ToLower()); -} - -public class StringCaseValidatorAttribute : ValidatorAttribute -{ - private StringCase textCase; - public StringCase TextCase - { - get { return this.textCase; } - set { this.textCase = value; } } - public StringCaseValidatorAttribute(StringCase textCase) + public class StringCaseValidatorAttribute : ValidatorAttribute { - this.textCase = textCase; - } + private StringCase textCase; + public StringCase TextCase + { + get { return this.textCase; } + set { this.textCase = value; } + } - protected override string? OverrideError(object? value) - { - string? str = (string?)value; - if (!str.HasText()) - return null; - - if ((this.textCase == StringCase.Uppercase) && (str != str.ToUpper())) - return ValidationMessage._0HasToBeUppercase.NiceToString(); + public StringCaseValidatorAttribute(StringCase textCase) + { + this.textCase = textCase; + } - if ((this.textCase == StringCase.Lowercase) && (str != str.ToLower())) - return ValidationMessage._0HasToBeLowercase.NiceToString(); + protected override string? OverrideError(object? value) + { + string? str = (string?)value; + if (!str.HasText()) + return null; + + if ((this.textCase == StringCase.Uppercase) && (str != str.ToUpper())) + return ValidationMessage._0HasToBeUppercase.NiceToString(); - return null; - } + if ((this.textCase == StringCase.Lowercase) && (str != str.ToLower())) + return ValidationMessage._0HasToBeLowercase.NiceToString(); - public override string HelpMessage => ValidationMessage.Be0.NiceToString(textCase.NiceToString()); -} + return null; + } -[DescriptionOptions(DescriptionOptions.Members)] -public enum StringCase -{ - Uppercase, - Lowercase -} + public override string HelpMessage => ValidationMessage.Be0.NiceToString(textCase.NiceToString()); + } + [DescriptionOptions(DescriptionOptions.Members)] + public enum StringCase + { + Uppercase, + Lowercase + } -public class IsAssignableToValidatorAttribute : ValidatorAttribute -{ - public Type Type { get; set; } - public IsAssignableToValidatorAttribute(Type type) + public class IsAssignableToValidatorAttribute : ValidatorAttribute { - this.Type = type; - } + public Type Type { get; set; } - protected override string? OverrideError(object? value) - { - if (value == null) - return null; + public IsAssignableToValidatorAttribute(Type type) + { + this.Type = type; + } - var t = (TypeEntity)value; - if (!Type.IsAssignableFrom(t.ToType())) + protected override string? OverrideError(object? value) { - return ValidationMessage._0IsNotA1_G.NiceToString().ForGenderAndNumber(Type.GetGender()).FormatWith(t.ToType().NiceName(), Type.NiceName()); + if (value == null) + return null; + + var t = (TypeEntity)value; + if (!Type.IsAssignableFrom(t.ToType())) + { + return ValidationMessage._0IsNotA1_G.NiceToString().ForGenderAndNumber(Type.GetGender()).FormatWith(t.ToType().NiceName(), Type.NiceName()); + } + + return null; } - return null; + public override string HelpMessage => ValidationMessage.BeA0_G.NiceToString().ForGenderAndNumber(Type.GetGender()).FormatWith(Type.NiceName()); } - public override string HelpMessage => ValidationMessage.BeA0_G.NiceToString().ForGenderAndNumber(Type.GetGender()).FormatWith(Type.NiceName()); -} + public class IpValidatorAttribute : RegexValidatorAttribute + { + public static Regex IpRegex = new Regex(@"\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b"); -public class IpValidatorAttribute : RegexValidatorAttribute -{ - public static Regex IpRegex = new Regex(@"\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b"); + public IpValidatorAttribute() + : base(IpRegex) + { + } - public IpValidatorAttribute() - : base(IpRegex) - { + public override string FormatName + { + get { return "IP"; } + } } - public override string FormatName + public class StateValidator : IEnumerable + where E : ModifiableEntity + where S : struct { - get { return "IP"; } - } -} + Func getState; + string[] propertyNames; + PropertyInfo[] properties; + Func[] getters; -public class StateValidator : IEnumerable - where E : ModifiableEntity - where S : struct -{ - Func getState; - string[] propertyNames; - PropertyInfo[] properties; - Func[] getters; + Dictionary dictionary = new Dictionary(); - Dictionary dictionary = new Dictionary(); + public StateValidator(Func getState, params Expression>[] propertyGetters) + { + this.getState = getState; + this.properties = propertyGetters.Select(p => ReflectionTools.GetPropertyInfo(p)).ToArray(); + this.propertyNames = this.properties.Select(pi => pi.Name).ToArray(); + this.getters = propertyGetters.Select(p => p.Compile()).ToArray(); + } - public StateValidator(Func getState, params Expression>[] propertyGetters) - { - this.getState = getState; - this.properties = propertyGetters.Select(p => ReflectionTools.GetPropertyInfo(p)).ToArray(); - this.propertyNames = this.properties.Select(pi => pi.Name).ToArray(); - this.getters = propertyGetters.Select(p => p.Compile()).ToArray(); - } + public void Add(S state, params bool?[] necessary) + { + if (necessary.Length != propertyNames.Length) + throw new ArgumentException("The StateValidator {0} for state {1} has {2} values instead of {3}" + .FormatWith(GetType().TypeName(), state, necessary.Length, propertyNames.Length)); - public void Add(S state, params bool?[] necessary) - { - if (necessary.Length != propertyNames.Length) - throw new ArgumentException("The StateValidator {0} for state {1} has {2} values instead of {3}" - .FormatWith(GetType().TypeName(), state, necessary.Length, propertyNames.Length)); + dictionary.Add(state, necessary); + } - dictionary.Add(state, necessary); - } + public string? Validate(E entity, PropertyInfo pi) + { + return Validate(entity, pi, true); + } - public string? Validate(E entity, PropertyInfo pi) - { - return Validate(entity, pi, true); - } + public bool? IsAllowed(S state, PropertyInfo pi) + { + int index = propertyNames.IndexOf(pi.Name); + if (index == -1) + return null; - public bool? IsAllowed(S state, PropertyInfo pi) - { - int index = propertyNames.IndexOf(pi.Name); - if (index == -1) - return null; + return Necessary(state, index); + } - return Necessary(state, index); - } + public string? Validate(E entity, PropertyInfo pi, bool showState) + { + int index = propertyNames.IndexOf(pi.Name); + if (index == -1) + return null; - public string? Validate(E entity, PropertyInfo pi, bool showState) - { - int index = propertyNames.IndexOf(pi.Name); - if (index == -1) - return null; + S state = getState(entity); - S state = getState(entity); + return GetMessage(entity, state, showState, index); + } - return GetMessage(entity, state, showState, index); - } + private string? GetMessage(E entity, S state, bool showState, int index) + { + bool? necessary = Necessary(state, index); - private string? GetMessage(E entity, S state, bool showState, int index) - { - bool? necessary = Necessary(state, index); + if (necessary == null) + return null; - if (necessary == null) - return null; + object? val = getters[index](entity); + if (val is IList && ((IList)val).Count == 0 || val is string && ((string)val).Length == 0) //both are indistinguible after retrieving + val = null; - object? val = getters[index](entity); - if (val is IList && ((IList)val).Count == 0 || val is string && ((string)val).Length == 0) //both are indistinguible after retrieving - val = null; + if (val != null && !necessary.Value) + return showState ? ValidationMessage._0IsNotAllowedOnState1.NiceToString().FormatWith(properties[index].NiceName(), state) : + ValidationMessage._0IsNotAllowed.NiceToString().FormatWith(properties[index].NiceName()); - if (val != null && !necessary.Value) - return showState ? ValidationMessage._0IsNotAllowedOnState1.NiceToString().FormatWith(properties[index].NiceName(), state) : - ValidationMessage._0IsNotAllowed.NiceToString().FormatWith(properties[index].NiceName()); + if (val == null && necessary.Value) + return showState ? ValidationMessage._0IsNecessaryOnState1.NiceToString().FormatWith(properties[index].NiceName(), state) : + ValidationMessage._0IsNecessary.NiceToString().FormatWith(properties[index].NiceName()); - if (val == null && necessary.Value) - return showState ? ValidationMessage._0IsNecessaryOnState1.NiceToString().FormatWith(properties[index].NiceName(), state) : - ValidationMessage._0IsNecessary.NiceToString().FormatWith(properties[index].NiceName()); + return null; + } - return null; - } + public bool? Necessary(S state, PropertyInfo pi) + { + int index = propertyNames.IndexOf(pi.Name); + if (index == -1) + throw new ArgumentException("The property is not registered"); - public bool? Necessary(S state, PropertyInfo pi) - { - int index = propertyNames.IndexOf(pi.Name); - if (index == -1) - throw new ArgumentException("The property is not registered"); + return Necessary(state, index); + } - return Necessary(state, index); - } + bool? Necessary(S state, int index) + { + return dictionary.GetOrThrow(state, "State {0} not registered in StateValidator")[index]; + } - bool? Necessary(S state, int index) - { - return dictionary.GetOrThrow(state, "State {0} not registered in StateValidator")[index]; - } + public IEnumerator GetEnumerator() //just to use object initializer + { + throw new NotImplementedException(); + } - public IEnumerator GetEnumerator() //just to use object initializer - { - throw new NotImplementedException(); + public string? PreviewErrors(E entity, S targetState, bool showState) + { + string result = propertyNames.Select((pn, i) => GetMessage(entity, targetState, showState, i)).NotNull().ToString("\r\n"); + + return string.IsNullOrEmpty(result) ? null : result; + } } - public string? PreviewErrors(E entity, S targetState, bool showState) - { - string result = propertyNames.Select((pn, i) => GetMessage(entity, targetState, showState, i)).NotNull().ToString("\r\n"); - return string.IsNullOrEmpty(result) ? null : result; - } -} - - -public enum ValidationMessage -{ - [Description("{0} does not have a valid {1} format")] - _0DoesNotHaveAValid1Format, - [Description("'{0}' does not have a valid {1} identifier format")] - _0DoesNotHaveAValid1IdentifierFormat, - [Description("{0} has an invalid format")] - _0HasAnInvalidFormat, - [Description("{0} has more than {1} decimal places")] - _0HasMoreThan1DecimalPlaces, - [Description("{0} has some repeated elements: {1}")] - _0HasSomeRepeatedElements1, - [Description("{0} should be {1} {2}")] - _0ShouldBe12, - [Description("{0} should be {1}")] - _0ShouldBe1, - [Description("{0} should be {1} instead of {2}")] - _0ShouldBe1InsteadOf2, - [Description("{0} has to be between {1} and {2}")] - _0HasToBeBetween1And2, - [Description("{0} has to be lowercase")] - _0HasToBeLowercase, - [Description("{0} has to be uppercase")] - _0HasToBeUppercase, - [Description("{0} is necessary")] - _0IsNecessary, - [Description("{0} is necessary on state {1}")] - _0IsNecessaryOnState1, - [Description("{0} is not allowed")] - _0IsNotAllowed, - [Description("{0} is not allowed on state {1}")] - _0IsNotAllowedOnState1, - [Description("{0} is not set")] - _0IsNotSet, - [Description("{0} are not set")] - _0AreNotSet, - [Description("{0} is set")] - _0IsSet, - [Description("{0} is not a {1}")] - _0IsNotA1_G, - [Description("be a {0}")] - BeA0_G, - [Description("be {0}")] - Be0, - [Description("be between {0} and {1}")] - BeBetween0And1, - [Description("be not null")] - BeNotNull, - [Description("file name")] - FileName, - [Description("have {0} decimals")] - Have0Decimals, - [Description("have a number of elements {0} {1}")] - HaveANumberOfElements01, - [Description("have a precision of {0}")] - HaveAPrecisionOf0, - [Description("have between {0} and {1} characters")] - HaveBetween0And1Characters, - [Description("have maximum {0} characters")] - HaveMaximum0Characters, - [Description("have minimum {0} characters")] - HaveMinimum0Characters, - [Description("have no repeated elements")] - HaveNoRepeatedElements, - [Description("have a valid {0} format")] - HaveValid0Format, - InvalidDateFormat, - InvalidFormat, - [Description("Not possible to assign {0}")] - NotPossibleToaAssign0, - Numeric, - [Description("or be null")] - OrBeNull, - Telephone, - [Description("{0} should have just one line")] - _0ShouldHaveJustOneLine, - [Description("{0} should not have initial spaces")] - _0ShouldNotHaveInitialSpaces, - [Description("{0} should not have final spaces")] - _0ShouldNotHaveFinalSpaces, - [Description("The lenght of {0} has to be equal to {1}")] - TheLenghtOf0HasToBeEqualTo1, - [Description("The length of {0} has to be greater than or equal to {1}")] - TheLengthOf0HasToBeGreaterOrEqualTo1, - [Description("The length of {0} has to be less than or equal to {1}")] - TheLengthOf0HasToBeLesserOrEqualTo1, - [Description("The number of {0} is being multiplied by {1}")] - TheNumberOf0IsBeingMultipliedBy1, - [Description("The rows are being grouped by {0}")] - TheRowsAreBeingGroupedBy0, - [Description("The number of elements of {0} has to be {1} {2}")] - TheNumberOfElementsOf0HasToBe12, - [Description("Type {0} not allowed")] - Type0NotAllowed, - - [Description("{0} is mandatory when {1} is not set")] - _0IsMandatoryWhen1IsNotSet, - [Description("{0} is mandatory when {1} is not set to {2}.")] - _0IsMandatoryWhen1IsNotSetTo2, - [Description("{0} is mandatory when {1} is set")] - _0IsMandatoryWhen1IsSet, - [Description("{0} is mandatory when {1} is set to {2}.")] - _0IsMandatoryWhen1IsSetTo2, - [Description("{0} should be null when {1} is not set")] - _0ShouldBeNullWhen1IsNotSet, - [Description("{0} should be null when {1} is not set to {2}.")] - _0ShouldBeNullWhen1IsNotSetTo2, - [Description("{0} should be null when {1} is set")] - _0ShouldBeNullWhen1IsSet, - [Description("{0} should be null when {1} is set to {2}.")] - _0ShouldBeNullWhen1IsSetTo2, - [Description("{0} should be null")] - _0ShouldBeNull, - [Description("{0} should be a date in the past")] - _0ShouldBeADateInThePast, - BeInThePast, - [Description("{0} should be greater than {1}")] - _0ShouldBeGreaterThan1, - [Description("{0} should be greater than or equal {1}")] - _0ShouldBeGreaterThanOrEqual1, - [Description("{0} should be less than {1}")] - _0ShouldBeLessThan1, - [Description("{0} should be less than or equal {1}")] - _0ShouldBeLessThanOrEqual1, - [Description("{0} has a precision of {1} instead of {2}")] - _0HasAPrecisionOf1InsteadOf2, - [Description("{0} should be of type {1}")] - _0ShouldBeOfType1, - [Description("{0} should not be of type {1}")] - _0ShouldNotBeOfType1, - [Description("{0} and {1} can not be set at the same time")] - _0And1CanNotBeSetAtTheSameTime, - [Description("{0} or {1} should be set")] - _0Or1ShouldBeSet, - [Description("{0} and {1} and {2} can not be set at the same time")] - _0And1And2CanNotBeSetAtTheSameTime, - [Description("{0} have {1} elements, but allowed only {2}")] - _0Have1ElementsButAllowedOnly2, - [Description("{0} is empty")] - _0IsEmpty, - [Description("{0} should be empty")] - _0ShouldBeEmpty, - [Description("At least one value is needed")] - _AtLeastOneValueIsNeeded, - PowerOf, - BeAString, - BeAMultilineString, - IsATimeOfTheDay, - - [Description("There are {0} in state {1}")] - ThereAre0InState1, - [Description("There are {0} that reference this {1}")] - ThereAre0ThatReferenceThis1, - - [Description("{0} is not compatible with {1}")] - _0IsNotCompatibleWith1, - [Description("{0} is repeated")] - _0IsRepeated, -} - -public static class ValidationMessageHelper -{ - public static string ShouldBe(this PropertyInfo pi, ComparisonType ct, object value) - { - return ValidationMessage._0ShouldBe12.NiceToString(pi.NiceName(), ct.NiceToString(), - value is PropertyInfo pi2 ? pi2.NiceName() : - value is Type t ? t.NiceName() : - value is Enum e ? e.NiceToString() : - value is null ? "null" : - value?.ToString()); + public enum ValidationMessage + { + [Description("{0} does not have a valid {1} format")] + _0DoesNotHaveAValid1Format, + [Description("'{0}' does not have a valid {1} identifier format")] + _0DoesNotHaveAValid1IdentifierFormat, + [Description("{0} has an invalid format")] + _0HasAnInvalidFormat, + [Description("{0} has more than {1} decimal places")] + _0HasMoreThan1DecimalPlaces, + [Description("{0} has some repeated elements: {1}")] + _0HasSomeRepeatedElements1, + [Description("{0} should be {1} {2}")] + _0ShouldBe12, + [Description("{0} should be {1}")] + _0ShouldBe1, + [Description("{0} should be {1} instead of {2}")] + _0ShouldBe1InsteadOf2, + [Description("{0} has to be between {1} and {2}")] + _0HasToBeBetween1And2, + [Description("{0} has to be lowercase")] + _0HasToBeLowercase, + [Description("{0} has to be uppercase")] + _0HasToBeUppercase, + [Description("{0} is necessary")] + _0IsNecessary, + [Description("{0} is necessary on state {1}")] + _0IsNecessaryOnState1, + [Description("{0} is not allowed")] + _0IsNotAllowed, + [Description("{0} is not allowed on state {1}")] + _0IsNotAllowedOnState1, + [Description("{0} is not set")] + _0IsNotSet, + [Description("{0} are not set")] + _0AreNotSet, + [Description("{0} is set")] + _0IsSet, + [Description("{0} is not a {1}")] + _0IsNotA1_G, + [Description("be a {0}")] + BeA0_G, + [Description("be {0}")] + Be0, + [Description("be between {0} and {1}")] + BeBetween0And1, + [Description("be not null")] + BeNotNull, + [Description("file name")] + FileName, + [Description("have {0} decimals")] + Have0Decimals, + [Description("have a number of elements {0} {1}")] + HaveANumberOfElements01, + [Description("have a precision of {0}")] + HaveAPrecisionOf0, + [Description("have between {0} and {1} characters")] + HaveBetween0And1Characters, + [Description("have maximum {0} characters")] + HaveMaximum0Characters, + [Description("have minimum {0} characters")] + HaveMinimum0Characters, + [Description("have no repeated elements")] + HaveNoRepeatedElements, + [Description("have a valid {0} format")] + HaveValid0Format, + InvalidDateFormat, + InvalidFormat, + [Description("Not possible to assign {0}")] + NotPossibleToaAssign0, + Numeric, + [Description("or be null")] + OrBeNull, + Telephone, + [Description("{0} should have just one line")] + _0ShouldHaveJustOneLine, + [Description("{0} should not have initial spaces")] + _0ShouldNotHaveInitialSpaces, + [Description("{0} should not have final spaces")] + _0ShouldNotHaveFinalSpaces, + [Description("The lenght of {0} has to be equal to {1}")] + TheLenghtOf0HasToBeEqualTo1, + [Description("The length of {0} has to be greater than or equal to {1}")] + TheLengthOf0HasToBeGreaterOrEqualTo1, + [Description("The length of {0} has to be less than or equal to {1}")] + TheLengthOf0HasToBeLesserOrEqualTo1, + [Description("The number of {0} is being multiplied by {1}")] + TheNumberOf0IsBeingMultipliedBy1, + [Description("The rows are being grouped by {0}")] + TheRowsAreBeingGroupedBy0, + [Description("The number of elements of {0} has to be {1} {2}")] + TheNumberOfElementsOf0HasToBe12, + [Description("Type {0} not allowed")] + Type0NotAllowed, + + [Description("{0} is mandatory when {1} is not set")] + _0IsMandatoryWhen1IsNotSet, + [Description("{0} is mandatory when {1} is not set to {2}.")] + _0IsMandatoryWhen1IsNotSetTo2, + [Description("{0} is mandatory when {1} is set")] + _0IsMandatoryWhen1IsSet, + [Description("{0} is mandatory when {1} is set to {2}.")] + _0IsMandatoryWhen1IsSetTo2, + [Description("{0} should be null when {1} is not set")] + _0ShouldBeNullWhen1IsNotSet, + [Description("{0} should be null when {1} is not set to {2}.")] + _0ShouldBeNullWhen1IsNotSetTo2, + [Description("{0} should be null when {1} is set")] + _0ShouldBeNullWhen1IsSet, + [Description("{0} should be null when {1} is set to {2}.")] + _0ShouldBeNullWhen1IsSetTo2, + [Description("{0} should be null")] + _0ShouldBeNull, + [Description("{0} should be a date in the past")] + _0ShouldBeADateInThePast, + BeInThePast, + [Description("{0} should be greater than {1}")] + _0ShouldBeGreaterThan1, + [Description("{0} should be greater than or equal {1}")] + _0ShouldBeGreaterThanOrEqual1, + [Description("{0} should be less than {1}")] + _0ShouldBeLessThan1, + [Description("{0} should be less than or equal {1}")] + _0ShouldBeLessThanOrEqual1, + [Description("{0} has a precision of {1} instead of {2}")] + _0HasAPrecisionOf1InsteadOf2, + [Description("{0} should be of type {1}")] + _0ShouldBeOfType1, + [Description("{0} should not be of type {1}")] + _0ShouldNotBeOfType1, + [Description("{0} and {1} can not be set at the same time")] + _0And1CanNotBeSetAtTheSameTime, + [Description("{0} or {1} should be set")] + _0Or1ShouldBeSet, + [Description("{0} and {1} and {2} can not be set at the same time")] + _0And1And2CanNotBeSetAtTheSameTime, + [Description("{0} have {1} elements, but allowed only {2}")] + _0Have1ElementsButAllowedOnly2, + [Description("{0} is empty")] + _0IsEmpty, + [Description("{0} should be empty")] + _0ShouldBeEmpty, + [Description("At least one value is needed")] + _AtLeastOneValueIsNeeded, + PowerOf, + BeAString, + BeAMultilineString, + IsATimeOfTheDay, + + [Description("There are {0} in state {1}")] + ThereAre0InState1, + [Description("There are {0} that reference this {1}")] + ThereAre0ThatReferenceThis1, + + [Description("{0} is not compatible with {1}")] + _0IsNotCompatibleWith1, + [Description("{0} is repeated")] + _0IsRepeated, + } + + public static class ValidationMessageHelper + { + public static string ShouldBe(this PropertyInfo pi, ComparisonType ct, object value) + { + return ValidationMessage._0ShouldBe12.NiceToString(pi.NiceName(), ct.NiceToString(), + value is PropertyInfo pi2 ? pi2.NiceName() : + value is Type t ? t.NiceName() : + value is Enum e ? e.NiceToString() : + value is null ? "null" : + value?.ToString()); + } } -}