// // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. // #nullable disable using System.Collections.Generic; using Microsoft.Data.Tools.Sql.DesignServices.TableDesigner; using Dac = Microsoft.Data.Tools.Sql.DesignServices.TableDesigner; using TableDesignerIssue = Microsoft.SqlTools.ServiceLayer.TableDesigner.Contracts.TableDesignerIssue; using System.Linq; namespace Microsoft.SqlTools.ServiceLayer.TableDesigner { public static class TableDesignerValidator { private static List Rules = new List() { new IndexMustHaveColumnsRule(), new ForeignKeyMustHaveColumnsRule(), new ColumnCanOnlyAppearOnceInForeignKeyRule(), new ColumnCanOnlyAppearOnceInIndexRule(), new NoDuplicateColumnNameRule(), new NoDuplicateConstraintNameRule(), new NoDuplicateIndexNameRule(), new EdgeConstraintMustHaveClausesRule(), new EdgeConstraintNoRepeatingClausesRule(), new MemoryOptimizedCannotBeEnabledWhenNotSupportedRule(), new MemoryOptimizedTableMustHaveNonClusteredPrimaryKeyRule(), new TemporalTableMustHavePeriodColumnsRule(), new PeriodColumnsRule(), new ColumnsInPrimaryKeyCannotBeNullableRule(), new OnlyDurableMemoryOptimizedTableCanBeSystemVersionedRule(), new TemporalTableMustHavePrimaryKeyRule(), new TableMustHaveAtLeastOneColumnRule(), new MemoryOptimizedTableIdentityColumnRule(), new TableShouldAvoidHavingMultipleEdgeConstraintsRule(), new ColumnCannotBeListedMoreThanOnceInPrimaryKeyRule(), new MutipleCreateTableStatementsInScriptRule(), new ClusteredIndexCannotHaveFilterPredicate(), new ClusteredIndexCannotHaveIncludedColumnsRule(), new ColumnCanOnlyAppearOnceInIndexIncludedColumnsRule(), new ColumnCannotDuplicateWitIndexKeyColumnsRule(), new ComputedColumnNeedToBePersistedAndNotNullInPrimaryKeyRule(), new ComputedColumnNeedToBePersistedInForeignKeyRule(), new HashIndexNotSupportedInNonMemoryOptimizedTableRule(), new HashIndexMustHaveBucketCountRule(), new ColumnCanOnlyAppearOnceInNonClusteredColumnStoreIndexRule(), new NonClusteredColumnStoreIndexMustHaveColumnsRule() }; /// /// Validate the table and return the validation errors. /// public static List Validate(Dac.TableDesigner designer) { var errors = new List(); foreach (var rule in Rules) { errors.AddRange(rule.Run(designer)); } return errors; } } public interface ITableDesignerValidationRule { List Run(Dac.TableDesigner designer); } public class IndexMustHaveColumnsRule : ITableDesignerValidationRule { public List Run(Dac.TableDesigner designer) { var table = designer.TableViewModel; var errors = new List(); for (int i = 0; i < table.Indexes.Items.Count; i++) { var index = table.Indexes.Items[i]; if (index.Columns.Count == 0) { errors.Add(new TableDesignerIssue() { Description = SR.IndexMustHaveColumnsRuleDescription(index.Name), PropertyPath = new object[] { TablePropertyNames.Indexes, i } }); } } return errors; } } public class ClusteredIndexCannotHaveIncludedColumnsRule : ITableDesignerValidationRule { public List Run(Dac.TableDesigner designer) { var table = designer.TableViewModel; var errors = new List(); for (int i = 0; i < table.Indexes.Items.Count; i++) { var index = table.Indexes.Items[i]; if (index.IsClustered && index.IncludedColumns.Count != 0) { errors.Add(new TableDesignerIssue() { Description = SR.ClusteredIndexCannotHaveIncludedColumnsRuleDescription, PropertyPath = new object[] { TablePropertyNames.Indexes, i, IndexPropertyNames.IncludedColumns, 0 } }); } } return errors; } } public class ClusteredIndexCannotHaveFilterPredicate : ITableDesignerValidationRule { public List Run(Dac.TableDesigner designer) { var table = designer.TableViewModel; var errors = new List(); for (int i = 0; i < table.Indexes.Items.Count; i++) { var index = table.Indexes.Items[i]; if (index.IsClustered && index.FilterPredicate != null) { errors.Add(new TableDesignerIssue() { Description = SR.ClusteredIndexCannotHaveFilterPredicateRuleDescription, PropertyPath = new object[] { TablePropertyNames.Indexes, i, IndexPropertyNames.FilterPredicate } }); } } return errors; } } public class ForeignKeyMustHaveColumnsRule : ITableDesignerValidationRule { public List Run(Dac.TableDesigner designer) { var table = designer.TableViewModel; var errors = new List(); for (int i = 0; i < table.ForeignKeys.Items.Count; i++) { var foreignKey = table.ForeignKeys.Items[i]; if (foreignKey.Columns.Count == 0) { errors.Add(new TableDesignerIssue() { Description = SR.ForeignKeyMustHaveColumnsRuleDescription(foreignKey.Name), PropertyPath = new object[] { TablePropertyNames.ForeignKeys, i } }); } } return errors; } } public class ColumnCanOnlyAppearOnceInIndexRule : ITableDesignerValidationRule { public List Run(Dac.TableDesigner designer) { var table = designer.TableViewModel; var errors = new List(); for (int i = 0; i < table.Indexes.Items.Count; i++) { var index = table.Indexes.Items[i]; var existingColumns = new HashSet(); for (int j = 0; j < index.Columns.Count; j++) { var columnSpec = index.Columns[j]; if (existingColumns.Contains(columnSpec.Column)) { errors.Add(new TableDesignerIssue() { Description = SR.ColumnCanOnlyAppearOnceInIndexRuleDescription(columnSpec.Column, index.Name, j + 1), PropertyPath = new object[] { TablePropertyNames.Indexes, i, IndexPropertyNames.Columns, j } }); } else { existingColumns.Add(columnSpec.Column); } } } return errors; } } public class ColumnCanOnlyAppearOnceInIndexIncludedColumnsRule : ITableDesignerValidationRule { public List Run(Dac.TableDesigner designer) { var table = designer.TableViewModel; var errors = new List(); for (int i = 0; i < table.Indexes.Items.Count; i++) { var index = table.Indexes.Items[i]; var existingColumns = new HashSet(); for (int j = 0; j < index.IncludedColumns.Count; j++) { var col = index.IncludedColumns[j]; if (existingColumns.Contains(col)) { errors.Add(new TableDesignerIssue() { Description = SR.ColumnCanOnlyAppearOnceInIndexIncludedColumnsRuleDescription(col, index.Name, j + 1), PropertyPath = new object[] { TablePropertyNames.Indexes, i, IndexPropertyNames.IncludedColumns, j } }); } else { existingColumns.Add(col); } } } return errors; } } public class ColumnCannotDuplicateWitIndexKeyColumnsRule : ITableDesignerValidationRule { public List Run(Dac.TableDesigner designer) { var table = designer.TableViewModel; var errors = new List(); for (int i = 0; i < table.Indexes.Items.Count; i++) { var index = table.Indexes.Items[i]; var keyCols = new HashSet(index.Columns.Select(s => s.Column)); for (int j = 0; j < index.IncludedColumns.Count; j++) { var col = index.IncludedColumns[j]; if (keyCols.Contains(col)) { errors.Add(new TableDesignerIssue() { Description = SR.ColumnCannotDuplicateWitIndexKeyColumnsRuleDescription(col, index.Name, j + 1), PropertyPath = new object[] { TablePropertyNames.Indexes, i, IndexPropertyNames.IncludedColumns, j } }); } } } return errors; } } public class ColumnCanOnlyAppearOnceInForeignKeyRule : ITableDesignerValidationRule { public List Run(Dac.TableDesigner designer) { var table = designer.TableViewModel; var errors = new List(); for (int i = 0; i < table.ForeignKeys.Items.Count; i++) { var foreignKey = table.ForeignKeys.Items[i]; var existingColumns = new HashSet(); for (int j = 0; j < foreignKey.Columns.Count; j++) { var column = foreignKey.Columns[j]; if (existingColumns.Contains(column)) { errors.Add(new TableDesignerIssue() { Description = SR.ColumnCanOnlyAppearOnceInForeignKeyRuleDescription(column, foreignKey.Name, j + 1), PropertyPath = new object[] { TablePropertyNames.ForeignKeys, i, ForeignKeyPropertyNames.ColumnMapping, j, ForeignKeyColumnMappingPropertyNames.Column } }); } else { existingColumns.Add(column); } } var existingForeignColumns = new HashSet(); for (int j = 0; j < foreignKey.ForeignColumns.Count; j++) { var foreignColumn = foreignKey.ForeignColumns[j]; if (existingForeignColumns.Contains(foreignColumn)) { errors.Add(new TableDesignerIssue() { Description = SR.ColumnCanOnlyAppearOnceInForeignKeyRuleForeignColumnDescription(foreignColumn, foreignKey.Name, j + 1), PropertyPath = new object[] { TablePropertyNames.ForeignKeys, i, ForeignKeyPropertyNames.ColumnMapping, j, ForeignKeyColumnMappingPropertyNames.ForeignColumn } }); } else { existingForeignColumns.Add(foreignColumn); } } } return errors; } } public class NoDuplicateConstraintNameRule : ITableDesignerValidationRule { public List Run(Dac.TableDesigner designer) { var errors = new List(); var table = designer.TableViewModel; var existingNames = new HashSet(); for (int i = 0; i < table.ForeignKeys.Items.Count; i++) { var foreignKey = table.ForeignKeys.Items[i]; if (existingNames.Contains(foreignKey.SystemName)) { errors.Add(new TableDesignerIssue() { Description = SR.NoDuplicateConstraintNameRuleDescription(foreignKey.SystemName, i + 1), PropertyPath = new object[] { TablePropertyNames.ForeignKeys, i, ForeignKeyPropertyNames.Name } }); } else { existingNames.Add(foreignKey.SystemName); } } for (int i = 0; i < table.CheckConstraints.Items.Count; i++) { var checkConstraint = table.CheckConstraints.Items[i]; if (existingNames.Contains(checkConstraint.SystemName)) { errors.Add(new TableDesignerIssue() { Description = SR.NoDuplicateConstraintNameRuleDescription(checkConstraint.SystemName, i + 1), PropertyPath = new object[] { TablePropertyNames.CheckConstraints, i, CheckConstraintPropertyNames.Name } }); } else { existingNames.Add(checkConstraint.SystemName); } } for (int i = 0; i < table.EdgeConstraints.Items.Count; i++) { var edgeConstraint = table.EdgeConstraints.Items[i]; if (existingNames.Contains(edgeConstraint.SystemName)) { errors.Add(new TableDesignerIssue() { Description = SR.NoDuplicateConstraintNameRuleDescription(edgeConstraint.SystemName, i + 1), PropertyPath = new object[] { TablePropertyNames.EdgeConstraints, i, EdgeConstraintPropertyNames.Name } }); } else { existingNames.Add(edgeConstraint.SystemName); } } return errors; } } public class NoDuplicateColumnNameRule : ITableDesignerValidationRule { public List Run(Dac.TableDesigner designer) { var table = designer.TableViewModel; var errors = new List(); var existingNames = new HashSet(); for (int i = 0; i < table.Columns.Items.Count; i++) { var column = table.Columns.Items[i]; if (existingNames.Contains(column.Name)) { errors.Add(new TableDesignerIssue() { Description = SR.NoDuplicateColumnNameRuleDescription(column.Name, i + 1), PropertyPath = new object[] { TablePropertyNames.Columns, i, TableColumnPropertyNames.Name } }); } else { existingNames.Add(column.Name); } } return errors; } } public class NoDuplicateIndexNameRule : ITableDesignerValidationRule { public List Run(Dac.TableDesigner designer) { var table = designer.TableViewModel; var errors = new List(); var existingNames = new HashSet(); for (int i = 0; i < table.Indexes.Items.Count; i++) { var index = table.Indexes.Items[i]; if (existingNames.Contains(index.Name)) { errors.Add(new TableDesignerIssue() { Description = SR.NoDuplicateIndexNameRuleDescription(index.Name, i + 1), PropertyPath = new object[] { TablePropertyNames.Indexes, i, IndexPropertyNames.Name } }); } else { existingNames.Add(index.Name); } } return errors; } } public class EdgeConstraintMustHaveClausesRule : ITableDesignerValidationRule { public List Run(Dac.TableDesigner designer) { var table = designer.TableViewModel; var errors = new List(); for (int i = 0; i < table.EdgeConstraints.Items.Count; i++) { var edgeConstraint = table.EdgeConstraints.Items[i]; if (edgeConstraint.Clauses.Count == 0) { errors.Add(new TableDesignerIssue() { Description = SR.EdgeConstraintMustHaveClausesRuleDescription(edgeConstraint.Name), PropertyPath = new object[] { TablePropertyNames.EdgeConstraints, i } }); } } return errors; } } public class EdgeConstraintNoRepeatingClausesRule : ITableDesignerValidationRule { public List Run(Dac.TableDesigner designer) { var table = designer.TableViewModel; var errors = new List(); for (int i = 0; i < table.EdgeConstraints.Items.Count; i++) { var edgeConstraint = table.EdgeConstraints.Items[i]; var existingPairs = new HashSet(); for (int j = 0; j < edgeConstraint.Clauses.Count; j++) { var clause = edgeConstraint.Clauses[j]; var pair = string.Format("{0} - {1}", clause.FromTable, clause.ToTable); if (existingPairs.Contains(pair)) { errors.Add(new TableDesignerIssue() { Description = SR.EdgeConstraintNoRepeatingClausesRuleDescription(pair, j + 1), PropertyPath = new object[] { TablePropertyNames.EdgeConstraints, i, EdgeConstraintPropertyNames.Clauses, j, EdgeConstraintClausePropertyNames.FromTable } }); } else { existingPairs.Add(pair); } } } return errors; } } public class MemoryOptimizedTableMustHaveNonClusteredPrimaryKeyRule : ITableDesignerValidationRule { public List Run(Dac.TableDesigner designer) { var table = designer.TableViewModel; var errors = new List(); if (table.IsMemoryOptimized && (table.PrimaryKey == null || table.PrimaryKey.IsClustered)) { errors.Add(new TableDesignerIssue() { Description = SR.MemoryOptimizedTableMustHaveNonClusteredPrimaryKeyRuleDescription, PropertyPath = new object[] { TablePropertyNames.PrimaryKeyIsClustered } }); } return errors; } } public class TemporalTableMustHavePrimaryKeyRule : ITableDesignerValidationRule { public List Run(Dac.TableDesigner designer) { var table = designer.TableViewModel; var errors = new List(); if (table.SystemVersioningHistoryTable != null && table.PrimaryKey == null) { errors.Add(new TableDesignerIssue() { Description = SR.TemporalTableMustHavePrimaryKeyRuleDescription }); } return errors; } } public class TemporalTableMustHavePeriodColumnsRule : ITableDesignerValidationRule { public List Run(Dac.TableDesigner designer) { var table = designer.TableViewModel; var errors = new List(); if (table.SystemVersioningHistoryTable != null && !table.PeriodColumnsDefined) { errors.Add(new TableDesignerIssue() { Description = SR.TemporalTableMustHavePeriodColumnsRuleDescription, MoreInfoLink = "https://docs.microsoft.com/sql/relational-databases/tables/creating-a-system-versioned-temporal-table" }); } return errors; } } public class PeriodColumnsRule : ITableDesignerValidationRule { public List Run(Dac.TableDesigner designer) { var table = designer.TableViewModel; var errors = new List(); var rowStart = table.Columns.Items.Where(c => c.GeneratedAlwaysAs == ColumnGeneratedAlwaysAsType.GeneratedAlwaysAsRowStart); var rowEnd = table.Columns.Items.Where(c => c.GeneratedAlwaysAs == ColumnGeneratedAlwaysAsType.GeneratedAlwaysAsRowEnd); if (rowStart.Count() > 1 || rowEnd.Count() > 1) { errors.Add(new TableDesignerIssue() { Description = SR.PeriodColumnsRuleMoreThanOneIssueDescription }); } else if (rowEnd.Count() != rowStart.Count()) { errors.Add(new TableDesignerIssue() { Description = SR.PeriodColumnsRuleNotMatchIssueDescription }); } return errors; } } public class ColumnsInPrimaryKeyCannotBeNullableRule : ITableDesignerValidationRule { public List Run(Dac.TableDesigner designer) { var table = designer.TableViewModel; var errors = new List(); for (int i = 0; i < table.Columns.Items.Count; i++) { var column = table.Columns.Items[i]; if (column.IsPrimaryKey && column.IsNullable) { errors.Add(new TableDesignerIssue() { Description = SR.ColumnsInPrimaryKeyCannotBeNullableRuleDescription, PropertyPath = new object[] { TablePropertyNames.Columns, i } }); } } return errors; } } public class OnlyDurableMemoryOptimizedTableCanBeSystemVersionedRule : ITableDesignerValidationRule { public List Run(Dac.TableDesigner designer) { var table = designer.TableViewModel; var errors = new List(); if (table.Durability == TableDurability.SchemaOnly && table.IsMemoryOptimized && table.IsSystemVersioningEnabled) { errors.Add(new TableDesignerIssue() { Description = SR.OnlyDurableMemoryOptimizedTableCanBeSystemVersionedRuleDescription }); } return errors; } } public class TableMustHaveAtLeastOneColumnRule : ITableDesignerValidationRule { public List Run(Dac.TableDesigner designer) { var table = designer.TableViewModel; var errors = new List(); if (!table.IsEdge && table.Columns.Items.Where(c => !c.IsComputed).Count() == 0) { errors.Add(new TableDesignerIssue() { Description = SR.TableMustHaveAtLeastOneColumnRuleDescription }); } return errors; } } public class MemoryOptimizedTableIdentityColumnRule : ITableDesignerValidationRule { public List Run(Dac.TableDesigner designer) { var table = designer.TableViewModel; var errors = new List(); if (table.IsMemoryOptimized) { for (int i = 0; i < table.Columns.Items.Count; i++) { var column = table.Columns.Items[i]; if (column.IsIdentity && (column.IdentitySeed != 1 || column.IdentityIncrement != 1)) { var propertyName = column.IdentitySeed != 1 ? TableColumnPropertyNames.IdentitySeed : TableColumnPropertyNames.IdentityIncrement; errors.Add(new TableDesignerIssue() { Description = SR.MemoryOptimizedTableIdentityColumnRuleDescription, PropertyPath = new object[] { TablePropertyNames.Columns, i, propertyName } }); } } } return errors; } } public class TableShouldAvoidHavingMultipleEdgeConstraintsRule : ITableDesignerValidationRule { public List Run(Dac.TableDesigner designer) { var table = designer.TableViewModel; var errors = new List(); if (table.EdgeConstraints.Items.Count > 1) { errors.Add(new TableDesignerIssue() { Description = SR.TableShouldAvoidHavingMultipleEdgeConstraintsRuleDescription, Severity = Contracts.IssueSeverity.Warning, MoreInfoLink = "https://docs.microsoft.com/sql/relational-databases/tables/graph-edge-constraints" }); } return errors; } } public class ColumnCannotBeListedMoreThanOnceInPrimaryKeyRule : ITableDesignerValidationRule { public List Run(Dac.TableDesigner designer) { var table = designer.TableViewModel; var errors = new List(); if (table.PrimaryKey != null) { var existingNames = new HashSet(); for (int i = 0; i < table.PrimaryKey.Columns.Count; i++) { var columnSpec = table.PrimaryKey.Columns[i]; if (existingNames.Contains(columnSpec.Column)) { errors.Add(new TableDesignerIssue() { Description = SR.ColumnCannotBeListedMoreThanOnceInPrimaryKeyRuleDescription(columnSpec.Column), PropertyPath = new object[] { TablePropertyNames.PrimaryKeyColumns, i, IndexColumnSpecificationPropertyNames.Column } }); } else { existingNames.Add(columnSpec.Column); } } } return errors; } } public class ComputedColumnNeedToBePersistedAndNotNullInPrimaryKeyRule : ITableDesignerValidationRule { public List Run(Dac.TableDesigner designer) { var table = designer.TableViewModel; var errors = new List(); if (table.PrimaryKey != null) { for (int i = 0; i < table.PrimaryKey.Columns.Count; i++) { var columnSpec = table.PrimaryKey.Columns[i]; var col = table.Columns.Items.First(item => item.Name == columnSpec.Column); if (col != null && col.IsComputed && !(col.IsComputedPersisted == true && col.IsComputedPersistedNullable == false)) { errors.Add(new TableDesignerIssue() { Description = SR.ComputedColumnNeedToBePersistedAndNotNullInPrimaryKeyRuleDescription(col.Name), PropertyPath = new object[] { TablePropertyNames.PrimaryKeyColumns, i, IndexColumnSpecificationPropertyNames.Column } }); } } } return errors; } } public class ComputedColumnNeedToBePersistedInForeignKeyRule : ITableDesignerValidationRule { public List Run(Dac.TableDesigner designer) { var table = designer.TableViewModel; var errors = new List(); for (int i = 0; i < table.ForeignKeys.Items.Count; i++) { for (int j = 0; j < table.ForeignKeys.Items[i].Columns.Count; j++) { var col = table.Columns.Items.First(item => item.Name == table.ForeignKeys.Items[i].Columns[j]); if (col != null && col.IsComputed && col.IsComputedPersisted != true) { errors.Add(new TableDesignerIssue() { Description = SR.ComputedColumnNeedToBePersistedInForeignKeyRuleDescription(col.Name, table.ForeignKeys.Items[i].SystemName), PropertyPath = new object[] { TablePropertyNames.ForeignKeys, i, ForeignKeyPropertyNames.ColumnMapping, j, ForeignKeyColumnMappingPropertyNames.Column } }); } } } return errors; } } public class MemoryOptimizedCannotBeEnabledWhenNotSupportedRule : ITableDesignerValidationRule { public List Run(Dac.TableDesigner designer) { var table = designer.TableViewModel; var errors = new List(); if (!designer.IsMemoryOptimizedTableSupported && designer.TableViewModel.IsMemoryOptimized) { errors.Add(new TableDesignerIssue() { Description = SR.MemoryOptimizedCannotBeEnabledWhenNotSupportedRuleDescription, PropertyPath = new object[] { TablePropertyNames.IsMemoryOptimized }, MoreInfoLink = designer.IsAzure ? "https://docs.microsoft.com/en-us/azure/azure-sql/in-memory-oltp-overview" : "https://docs.microsoft.com/en-us/sql/relational-databases/in-memory-oltp/overview-and-usage-scenarios" }); } return errors; } } public class MutipleCreateTableStatementsInScriptRule : ITableDesignerValidationRule { public List Run(Dac.TableDesigner designer) { var errors = new List(); if (designer.ScriptContainsMultipleTableDefinition) { errors.Add(new TableDesignerIssue() { Description = SR.MutipleCreateTableStatementsInScriptRuleDescription, Severity = Contracts.IssueSeverity.Information }); } return errors; } } public class HashIndexNotSupportedInNonMemoryOptimizedTableRule : ITableDesignerValidationRule { public List Run(Dac.TableDesigner designer) { var table = designer.TableViewModel; var errors = new List(); if (!table.IsMemoryOptimized) { for (int i = 0; i < table.Indexes.Items.Count; i++) { var index = table.Indexes.Items[i]; if (index.IsHash) { errors.Add(new TableDesignerIssue() { Description = SR.HashIndexNotSupportedInNonMemoryOptimizedTableRuleDescription(index.Name), PropertyPath = new object[] { TablePropertyNames.Indexes, i, IndexPropertyNames.IsHash } }); } } } return errors; } } public class HashIndexMustHaveBucketCountRule : ITableDesignerValidationRule { public List Run(Dac.TableDesigner designer) { var table = designer.TableViewModel; var errors = new List(); for (int i = 0; i < table.Indexes.Items.Count; i++) { var index = table.Indexes.Items[i]; if (index.IsHash && !index.BucketCount.HasValue) { errors.Add(new TableDesignerIssue() { Description = SR.HashIndexMustHaveBucketCountRuleDescription(index.Name), PropertyPath = new object[] { TablePropertyNames.Indexes, i, IndexPropertyNames.BucketCount } }); } } return errors; } } public class ColumnCanOnlyAppearOnceInNonClusteredColumnStoreIndexRule : ITableDesignerValidationRule { public List Run(Dac.TableDesigner designer) { var table = designer.TableViewModel; var errors = new List(); for (int i = 0; i < table.ColumnStoreIndexes.Items.Count; i++) { var index = table.ColumnStoreIndexes.Items[i]; if (!index.IsClustered) { var existingColumns = new HashSet(); for (int j = 0; j < index.Columns.Count; j++) { var columnSpec = index.Columns[j]; if (existingColumns.Contains(columnSpec.Column)) { errors.Add(new TableDesignerIssue() { Description = SR.ColumnCanOnlyAppearOnceInNonClusteredColumnStoreIndexRuleDescription(columnSpec.Column, index.Name, j + 1), PropertyPath = new object[] { TablePropertyNames.ColumnStoreIndexes, i, ColumnStoreIndexPropertyNames.Columns, j } }); } else { existingColumns.Add(columnSpec.Column); } } } } return errors; } } public class NonClusteredColumnStoreIndexMustHaveColumnsRule : ITableDesignerValidationRule { public List Run(Dac.TableDesigner designer) { var table = designer.TableViewModel; var errors = new List(); for (int i = 0; i < table.ColumnStoreIndexes.Items.Count; i++) { var index = table.ColumnStoreIndexes.Items[i]; if (!index.IsClustered && index.Columns.Count == 0) { errors.Add(new TableDesignerIssue() { Description = SR.NonClusteredColumnStoreIndexMustHaveColumnsRuleDescription(index.Name), PropertyPath = new object[] { TablePropertyNames.ColumnStoreIndexes, i } }); } } return errors; } } }