Exemple #1
0
        internal EduHubEntity(EduHubSchema Schema, string Name, string Description)
        {
            this.Schema      = Schema;
            this.Name        = Name;
            this.Description = Description;

            fields  = new List <EduHubField>();
            indexes = new List <EduHubIndex>();
        }
Exemple #2
0
        public static EduHubSchema Parse(IEnumerable<string> C7Schema)
        {
            var schema = new EduHubSchema();

            var filteredC7Schema = FilterSchemaDirectives(C7Schema);

            ParseEntities(filteredC7Schema, schema);

            return schema;
        }
Exemple #3
0
        internal EduHubEntity(EduHubSchema Schema, string Name, string Description)
        {
            this.Schema = Schema;
            this.Name = Name;
            this.Description = Description;

            fields = new List<EduHubField>();
            Fields = fields.AsReadOnly();

            indexes = new List<EduHubIndex>();
            Indexes = indexes.AsReadOnly();
        }
Exemple #4
0
        public static void TestForeignKeys(EduHubSchema Schema)
        {
            foreach (var childField in Schema.Entities.SelectMany(e => e.Fields).Where(f => f.ForeignParentKey != null))
            {
                var foreignParent = childField.GetForeignParent();
                if (foreignParent == null)
                    throw new InvalidOperationException("Foreign parent not found");

                var foreignIndex = foreignParent.GetIndex();
                if (foreignIndex == null)
                    throw new InvalidOperationException("Foreign parent is not indexed");

                if (!foreignIndex.IsUnique)
                    throw new InvalidOperationException("Foreign index is not unique");
            }
        }
Exemple #5
0
        public static EduHubSchema FromC7Schema(IList <IC7Element> Elements)
        {
            var schema = new EduHubSchema();

            foreach (var element in Elements)
            {
                switch (element)
                {
                case IC7Entity c7entity:
                    var entity = new EduHubEntity(schema, c7entity.Name, c7entity.Description.Trim());
                    entity.AddFields(c7entity.ToEduHubFields(entity));
                    schema.AddEntity(entity);
                    break;

                default:
                    break;
                }
            }

            return(schema);
        }
Exemple #6
0
        private static void ParseEntities(IEnumerable<string> C7Schema, EduHubSchema Schema)
        {
            using (var schemaIterator = C7Schema.GetEnumerator())
            {
                if (!schemaIterator.MoveNext())
                    return;

                while (true)
                {
                    // Match Entities
                    var entityMatch = TestEntity.Match(schemaIterator.Current);
                    if (entityMatch.Success)
                    {
                        var entity = Schema.AddEntity(
                            Name: entityMatch.Groups[1].Value.Trim(),
                            Description: entityMatch.Groups[2].Value.Trim());

                        // Match Fields
                        entity.AddFields(ParseFields(entity, schemaIterator));

                        // Match Directives
                        ParseDirectives(entity, schemaIterator);
                    }
                    else
                    {
                        if (!schemaIterator.MoveNext())
                            return;
                    }
                };
            }
        }
Exemple #7
0
        public static void AugmentSchemaFromSql(string ConnectionString, EduHubSchema Schema)
        {
            var tables = new List<SysTable>();
            var types = new List<SysType>();
            var columns = new List<SysColumn>();
            var indexes = new List<SysIndex>();
            var indexColumns = new List<SysIndexColumn>();

            using (SqlConnection dbConnection = new SqlConnection(ConnectionString))
            {
                dbConnection.Open();

                // Tables
                const string sqlTables = @"SELECT name, object_id FROM sys.tables";
                using (var dbCommand = new SqlCommand(sqlTables, dbConnection))
                {
                    using (var dbReader = dbCommand.ExecuteReader())
                    {
                        while (dbReader.Read())
                        {
                            tables.Add(new SysTable()
                            {
                                name = dbReader.GetString(0),
                                object_id = dbReader.GetInt32(1)
                            });
                        }
                    }
                }

                // Types
                const string sqlTypes = @"SELECT name, user_type_id FROM sys.types";
                using (var dbCommand = new SqlCommand(sqlTypes, dbConnection))
                {
                    using (var dbReader = dbCommand.ExecuteReader())
                    {
                        while (dbReader.Read())
                        {
                            types.Add(new SysType()
                            {
                                name = dbReader.GetString(0),
                                user_type_id = dbReader.GetInt32(1)
                            });
                        }
                    }
                }

                // Columns
                const string sqlColumns = @"SELECT object_id, name, column_id, user_type_id, max_length, is_nullable, is_identity FROM sys.columns";
                using (var dbCommand = new SqlCommand(sqlColumns, dbConnection))
                {
                    using (var dbReader = dbCommand.ExecuteReader())
                    {
                        while (dbReader.Read())
                        {
                            columns.Add(new SysColumn()
                            {
                                object_id = dbReader.GetInt32(0),
                                name = dbReader.GetString(1),
                                column_id = dbReader.GetInt32(2),
                                user_type_id = dbReader.GetInt32(3),
                                max_length = dbReader.GetInt16(4),
                                is_nullable = dbReader.GetBoolean(5),
                                is_identity = dbReader.GetBoolean(6)
                            });
                        }
                    }
                }

                // Indexes
                const string sqlIndexes = @"SELECT object_id, name, index_id, type, is_unique, is_primary_key FROM sys.indexes";
                using (var dbCommand = new SqlCommand(sqlIndexes, dbConnection))
                {
                    using (var dbReader = dbCommand.ExecuteReader())
                    {
                        while (dbReader.Read())
                        {
                            indexes.Add(new SysIndex()
                            {
                                object_id = dbReader.GetInt32(0),
                                name = dbReader.IsDBNull(1) ? null : dbReader.GetString(1),
                                index_id = dbReader.GetInt32(2),
                                type = dbReader.GetByte(3),
                                is_unique = dbReader.GetBoolean(4),
                                is_primary_key = dbReader.GetBoolean(5)
                            });
                        }
                    }
                }

                // Index Columns
                const string sqlIndexColumns = @"SELECT object_id, index_id, column_id, key_ordinal FROM sys.index_columns";
                using (var dbCommand = new SqlCommand(sqlIndexColumns, dbConnection))
                {
                    using (var dbReader = dbCommand.ExecuteReader())
                    {
                        while (dbReader.Read())
                        {
                            indexColumns.Add(new SysIndexColumn()
                            {
                                object_id = dbReader.GetInt32(0),
                                index_id = dbReader.GetInt32(1),
                                column_id = dbReader.GetInt32(2),
                                key_ordinal = dbReader.GetByte(3)
                            });
                        }
                    }
                }

                dbConnection.Close();
            }

            AugmentSchema(Schema, tables, types, columns, indexes, indexColumns);
        }
Exemple #8
0
        private static void AugmentSchema(EduHubSchema Schema, List<SysTable> Tables, List<SysType> Types, List<SysColumn> Columns, List<SysIndex> Indexes, List<SysIndexColumn> IndexColumns)
        {
            var tableLookup = Tables.ToDictionary(t => t.name, t => t.object_id, StringComparer.OrdinalIgnoreCase);
            var typeLookup = Types.ToDictionary(t => t.user_type_id, t => t.name);
            var columnsLookup = Columns
                .GroupBy(c => c.object_id)
                .ToDictionary(g => g.Key, g => g.OrderBy(c => c.column_id).ToList());
            var indexLookup = Indexes
                .GroupBy(i => i.object_id)
                .ToDictionary(g => g.Key, g => g.OrderBy(i => i.index_id).ToList());
            var indexColumnLookup = IndexColumns
                .GroupBy(ic => ic.object_id)
                .ToDictionary(g => g.Key, g => g.GroupBy(ic => ic.index_id).ToDictionary(g2 => g2.Key, g2 => g2.OrderBy(ic => ic.key_ordinal).ToList()));

            foreach (var entity in Schema.Entities)
            {
                // Table ID
                int tableObjectId;

                if (!tableLookup.TryGetValue(entity.Name, out tableObjectId))
                {
                    throw new InvalidOperationException($"Unknown Entity: {entity.Name}");
                }
                else
                {
                    // COLUMNS
                    // Validate schema conformance; determine nullable and identity columns
                    List<SysColumn> entityColumns;
                    if (columnsLookup.TryGetValue(tableObjectId, out entityColumns))
                    {
                        foreach (var column in entityColumns)
                        {
                            var field = entity.Fields.First(f => f.Name.Equals(column.name, StringComparison.OrdinalIgnoreCase));

                            field.SqlType = typeLookup[column.user_type_id];

                            // Check Framework Type
                            if (field.Type != GetFrameworkType(field.SqlType))
                            {
                                // Overrides
                                if (field.Entity.Name == "SPFSTORE" && field.Name == "ASSOCIATION_TYPE" && field.Type == "short" && field.SqlType == "int")
                                {
                                    field.Type = "int";
                                }
                                else
                                {
                                    throw new InvalidOperationException("Entity field type didn't match database schema");
                                }
                            }
                            if ((field.TypeMaxLength != 0 || field.Type == "string") && field.TypeMaxLength != column.max_length && field.SqlType != "text")
                            {
                                // Overrides
                                if (field.Entity.Name == "SPFSTORE" && field.Name == "PHYSICAL_LOCATION" && field.TypeMaxLength == 255 && column.max_length == 500)
                                {
                                    field.TypeMaxLength = column.max_length;
                                }
                                else
                                {
                                    throw new InvalidOperationException("Entity field max length didn't match database schema");
                                }
                            }
                            if (!field.IsNullable && column.is_nullable)
                            {
                                throw new InvalidOperationException("Entity field nullable didn't match database schema");
                            }

                            if (field.IsNullable && !column.is_nullable)
                            {
                                field.IsNullable = false;
                            }

                            if (field.IsIdentity && !column.is_identity)
                            {
                                throw new InvalidOperationException("Entity field identity didn't match database schema");
                            }

                            if (!field.IsIdentity && column.is_identity)
                            {
                                field.IsIdentity = true;
                            }
                        }
                    }

                    // INDEXES
                    // Determine Indexes
                    List<SysIndex> entityIndexes;
                    if (indexLookup.TryGetValue(tableObjectId, out entityIndexes))
                    {
                        foreach (var entityIndex in entityIndexes)
                        {
                            List<SysIndexColumn> indexColumns = indexColumnLookup[entityIndex.object_id][entityIndex.index_id];

                            List<EduHubField> fields = indexColumns
                                .Select(ic => entity.Fields.First(f => f.Name.Equals(entityColumns.First(c => c.column_id == ic.column_id).name, StringComparison.OrdinalIgnoreCase)))
                                .ToList();

                            if (fields.Count == 0)
                                throw new InvalidOperationException("Unexpected index with no columns");

                            var indexName = $"Index_{string.Join("_", fields.Select(f => f.Name))}";

                            var index = new EduHubIndex(
                                Entity: entity,
                                Name: indexName,
                                Fields: fields.AsReadOnly(),
                                IsPrimary: entityIndex.is_primary_key,
                                IsUnique: entityIndex.is_unique,
                                IsClustered: entityIndex.type == 1);  // 1 = Clustered, 2 = Non Clustered

                            // Check for existing Index with matching Fields; if matched, add unique or shortest name;
                            var matchingIndex = entity.Indexes.FirstOrDefault(ei => ei.Fields.Count == index.Fields.Count && ei.Fields.All(f => index.Fields.Contains(f)));
                            if (matchingIndex != null)
                            {
                                if ((!matchingIndex.IsUnique && index.IsUnique) || // New Index is unique
                                    (!matchingIndex.IsPrimary && index.IsPrimary) || // New Index is primary
                                    (!matchingIndex.IsClustered && index.IsClustered) || // New Index is clustered
                                    (index.Name.Length < matchingIndex.Name.Length)) // New Index has shorter name
                                {
                                    // Remove existing, add new
                                    entity.RemoveIndex(matchingIndex);
                                    entity.AddIndex(index);
                                }
                            }
                            else
                            {
                                entity.AddIndex(index);
                            }
                        }
                    }

                    // Ensure identity columns have index
                    foreach (var identityField in entity.Fields.Where(f => f.IsIdentity))
                    {
                        var name = $"Index_{identityField.Name}";

                        var index = new EduHubIndex(
                            Entity: entity,
                            Name: name,
                            Fields: new List<EduHubField>() { identityField }.AsReadOnly(),
                            IsPrimary: false,
                            IsUnique: true,
                            IsClustered: false);

                        // Check for existing Index with matching Fields; if matched, add unique or shortest name;
                        var matchingIndex = entity.Indexes.FirstOrDefault(ei => ei.Fields.Count == index.Fields.Count && ei.Fields.All(f => index.Fields.Contains(f)));
                        if (matchingIndex != null)
                        {
                            if ((!matchingIndex.IsUnique && index.IsUnique) || // New Index is unique
                                (index.Name.Length < matchingIndex.Name.Length)) // New Index has shorter name
                            {
                                if (matchingIndex.IsClustered)
                                {
                                    throw new InvalidOperationException("Shouldn't replace clustered indexes");
                                }
                                if (matchingIndex.IsPrimary)
                                {
                                    throw new InvalidOperationException("Shouldn't replace primary indexes");
                                }

                                // Remove existing, add new
                                entity.RemoveIndex(matchingIndex);
                                entity.AddIndex(index);
                            }
                        }
                        else
                        {
                            entity.AddIndex(index);
                        }
                    }

                }


            }
        }
Exemple #9
0
        public static void AugmentSchemaFromCsv(string CsvDirectory, EduHubSchema Schema)
        {
            var tables = new List<SysTable>();
            using (var stream = new FileStream(Path.Combine(CsvDirectory, "SQL_Schema_Tables.csv"), FileMode.Open))
            {
                using (var reader = new CsvReader(stream))
                {
                    tables = SysBuilder.ImportCsv(reader,
                        Tuple.Create("SYSSCHOBJS.NAME", (Action<SysTable, string>)((t, v) => t.name = v)),
                        Tuple.Create("OBJECT_ID", (Action<SysTable, string>)((t, v) => t.object_id = int.Parse(v)))
                        );
                }
            }

            var types = new List<SysType>();
            using (var stream = new FileStream(Path.Combine(CsvDirectory, "SQL_Schema_Types.csv"), FileMode.Open))
            {
                using (var reader = new CsvReader(stream))
                {
                    types = SysBuilder.ImportCsv(reader,
                        Tuple.Create("SYSSCALARTYPES.NAME", (Action<SysType, string>)((t, v) => t.name = v)),
                        Tuple.Create("USER_TYPE_ID", (Action<SysType, string>)((t, v) => t.user_type_id = int.Parse(v)))
                        );
                }
            }

            var columns = new List<SysColumn>();
            using (var stream = new FileStream(Path.Combine(CsvDirectory, "SQL_Schema_Columns.csv"), FileMode.Open))
            {
                using (var reader = new CsvReader(stream))
                {
                    columns = SysBuilder.ImportCsv(reader,
                        Tuple.Create("OBJECT_ID", (Action<SysColumn, string>)((t, v) => t.object_id = int.Parse(v))),
                        Tuple.Create("SYSCOLPARS.NAME", (Action<SysColumn, string>)((t, v) => t.name = v)),
                        Tuple.Create("COLUMN_ID", (Action<SysColumn, string>)((t, v) => t.column_id = int.Parse(v))),
                        Tuple.Create("USER_TYPE_ID", (Action<SysColumn, string>)((t, v) => t.user_type_id = int.Parse(v))),
                        Tuple.Create("MAX_LENGTH", (Action<SysColumn, string>)((t, v) => t.max_length = int.Parse(v))),
                        Tuple.Create("IS_NULLABLE", (Action<SysColumn, string>)((t, v) => t.is_nullable = bool.Parse(v))),
                        Tuple.Create("IS_IDENTITY", (Action<SysColumn, string>)((t, v) => t.is_identity = bool.Parse(v)))
                        );
                }
            }

            var indexes = new List<SysIndex>();
            using (var stream = new FileStream(Path.Combine(CsvDirectory, "SQL_Schema_Indexes.csv"), FileMode.Open))
            {
                using (var reader = new CsvReader(stream))
                {
                    indexes = SysBuilder.ImportCsv(reader,
                        Tuple.Create("OBJECT_ID", (Action<SysIndex, string>)((t, v) => t.object_id = int.Parse(v))),
                        Tuple.Create("SYSIDXSTATS.NAME", (Action<SysIndex, string>)((t, v) => t.name = v)),
                        Tuple.Create("INDEX_ID", (Action<SysIndex, string>)((t, v) => t.index_id = int.Parse(v))),
                        Tuple.Create("SYSIDXSTATS.TYPE", (Action<SysIndex, string>)((t, v) => t.type = int.Parse(v))),
                        Tuple.Create("IS_UNIQUE", (Action<SysIndex, string>)((t, v) => t.is_unique = bool.Parse(v))),
                        Tuple.Create("IS_PRIMARY_KEY", (Action<SysIndex, string>)((t, v) => t.is_primary_key = bool.Parse(v)))
                        );
                }
            }

            var indexColumns = new List<SysIndexColumn>();
            using (var stream = new FileStream(Path.Combine(CsvDirectory, "SQL_Schema_IndexColumns.csv"), FileMode.Open))
            {
                using (var reader = new CsvReader(stream))
                {
                    indexColumns = SysBuilder.ImportCsv(reader,
                        Tuple.Create("OBJECT_ID", (Action<SysIndexColumn, string>)((t, v) => t.object_id = int.Parse(v))),
                        Tuple.Create("INDEX_ID", (Action<SysIndexColumn, string>)((t, v) => t.index_id = int.Parse(v))),
                        Tuple.Create("COLUMN_ID", (Action<SysIndexColumn, string>)((t, v) => t.column_id = int.Parse(v))),
                        Tuple.Create("KEY_ORDINAL", (Action<SysIndexColumn, string>)((t, v) => t.key_ordinal = byte.Parse(v)))
                        );
                }
            }

            AugmentSchema(Schema, tables, types, columns, indexes, indexColumns);
        }
Exemple #10
0
        public static void AugmentSchema(string ConnectionString, EduHubSchema Schema)
        {
            const string sqlColumns = @"SELECT c.name, c.max_length, c.is_nullable, c.is_identity, st.name
FROM sys.tables t
JOIN sys.columns c on c.object_id=t.object_id
JOIN sys.systypes st ON c.system_type_id=st.xtype
WHERE t.name=@TableName
ORDER BY c.column_id";
            const string sqlIndexes = @"SELECT t.object_id, i.index_id, i.name, i.is_primary_key, i.is_unique, i.type
FROM sys.tables t
JOIN sys.indexes i ON i.object_id=t.object_id
WHERE t.name=@TableName";
            const string sqlIndex = @"SELECT c.name
FROM sys.indexes i
JOIN sys.index_columns ic ON ic.object_id=i.object_id AND ic.index_id=i.index_id
JOIN sys.columns c ON c.object_id=ic.object_id AND c.column_id=ic.column_id
WHERE i.object_id=@ObjectId AND i.index_id=@IndexId
ORDER BY ic.key_ordinal;";

            using (SqlConnection dbConnection = new SqlConnection(ConnectionString))
            {
                dbConnection.Open();

                foreach (var entity in Schema.Entities)
                {
                    // Validate schema conformance; determine nullable and identity columns
                    using (SqlCommand dbColumnsCommand = new SqlCommand(sqlColumns, dbConnection))
                    {
                        var dbColumnsTableParam = dbColumnsCommand.Parameters.Add("@TableName", SqlDbType.NVarChar, 128);
                        dbColumnsTableParam.Value = entity.Name;

                        using (var dbColumnsReader = dbColumnsCommand.ExecuteReader())
                        {
                            while (dbColumnsReader.Read())
                            {
                                var fieldName = dbColumnsReader.GetString(0);
                                var maxLength = dbColumnsReader.GetInt16(1);
                                var isNullable = dbColumnsReader.GetBoolean(2);
                                var isIdentity = dbColumnsReader.GetBoolean(3);
                                var fieldType = dbColumnsReader.GetString(4);
                                var frameworkType = DetermineFrameworkType(fieldType);

                                var field = entity.Fields.First(f => f.Name.Equals(fieldName, StringComparison.OrdinalIgnoreCase));

                                field.SqlType = fieldType;

                                // Check Framework Type
                                if (field.Type != frameworkType)
                                {
                                    throw new InvalidOperationException("Entity field type didn't match database schema");
                                }
                                if ((field.TypeMaxLength != 0 || field.Type == "string") && field.TypeMaxLength != maxLength && fieldType != "text")
                                {
                                    // Overrides
                                    if (field.Entity.Name == "KERROR" && field.Name == "SPOUTKEY" && field.TypeMaxLength == 100 && maxLength == 40)
                                    {
                                        field.TypeMaxLength = maxLength;
                                    }
                                    else if (field.Entity.Name == "SPOUT" && field.Name == "SPOUTKEY" && field.TypeMaxLength == 100 && maxLength == 40)
                                    {
                                        field.TypeMaxLength = maxLength;
                                    }
                                    else
                                    {
                                        throw new InvalidOperationException("Entity field max length didn't match database schema");
                                    }
                                }
                                if (!field.IsNullable && isNullable)
                                {
                                    throw new InvalidOperationException("Entity field nullable didn't match database schema");
                                }

                                if (field.IsNullable && !isNullable)
                                {
                                    field.IsNullable = false;
                                }

                                if (field.IsIdentity && !isIdentity)
                                {
                                    throw new InvalidOperationException("Entity field identity didn't match database schema");
                                }

                                if (!field.IsIdentity && isIdentity)
                                {
                                    field.IsIdentity = true;
                                }
                            }
                        }

                    }

                    // Determine Indexes
                    using (SqlCommand dbIndexesCommand = new SqlCommand(sqlIndexes, dbConnection))
                    {
                        var dbIndexesTableParam = dbIndexesCommand.Parameters.Add("@TableName", SqlDbType.NVarChar, 128);
                        dbIndexesTableParam.Value = entity.Name;

                        using (var dbIndexesReader = dbIndexesCommand.ExecuteReader())
                        {
                            while (dbIndexesReader.Read())
                            {
                                var objectId = dbIndexesReader.GetInt32(0);
                                var indexId = dbIndexesReader.GetInt32(1);
                                var name = dbIndexesReader.GetString(2);
                                var isPrimary = dbIndexesReader.GetBoolean(3);
                                var isUnique = dbIndexesReader.GetBoolean(4);
                                var isClustered = dbIndexesReader.GetByte(5) == 1; // 1 = Clustered, 2 = Non Clustered
                                List<EduHubField> fields = new List<EduHubField>();

                                using (SqlCommand dbIndexCommand = new SqlCommand(sqlIndex, dbConnection))
                                {
                                    var dbIndexObjectIdParam = dbIndexCommand.Parameters.Add("@ObjectId", SqlDbType.Int);
                                    var dbIndexIdParam = dbIndexCommand.Parameters.Add("@IndexId", SqlDbType.Int);
                                    dbIndexObjectIdParam.Value = objectId;
                                    dbIndexIdParam.Value = indexId;

                                    using (var dbIndexReader = dbIndexCommand.ExecuteReader())
                                    {
                                        while (dbIndexReader.Read())
                                        {
                                            var fieldName = dbIndexReader.GetString(0);
                                            fields.Add(entity.Fields.First(f => f.Name.Equals(fieldName, StringComparison.OrdinalIgnoreCase)));
                                        }
                                    }
                                }

                                if (fields.Count == 0)
                                    throw new InvalidOperationException("Unexpected index with no columns");

                                name = $"Index_{string.Join("_", fields.Select(f => f.Name))}";

                                var index = new EduHubIndex(
                                    Entity: entity,
                                    Name: name,
                                    Fields: fields.AsReadOnly(),
                                    IsPrimary: isPrimary,
                                    IsUnique: isUnique,
                                    IsClustered: isClustered);

                                // Check for existing Index with matching Fields; if matched, add unique or shortest name;
                                var matchingIndex = entity.Indexes.FirstOrDefault(ei => ei.Fields.Count == index.Fields.Count && ei.Fields.All(f => index.Fields.Contains(f)));
                                if (matchingIndex != null)
                                {
                                    if ((!matchingIndex.IsUnique && index.IsUnique) || // New Index is unique
                                        (!matchingIndex.IsPrimary && index.IsPrimary) || // New Index is primary
                                        (!matchingIndex.IsClustered && index.IsClustered) || // New Index is clustered
                                        (index.Name.Length < matchingIndex.Name.Length)) // New Index has shorter name
                                    {
                                        // Remove existing, add new
                                        entity.RemoveIndex(matchingIndex);
                                        entity.AddIndex(index);
                                    }
                                }
                                else
                                {
                                    entity.AddIndex(index);
                                }
                            }
                        }
                    }

                    // Ensure identity columns have index
                    foreach (var identityField in entity.Fields.Where(f => f.IsIdentity))
                    {
                        var name = $"Index_{identityField.Name}";

                        var index = new EduHubIndex(
                            Entity: entity,
                            Name: name,
                            Fields: new List<EduHubField>() { identityField }.AsReadOnly(),
                            IsPrimary: false,
                            IsUnique: true,
                            IsClustered: false);

                        // Check for existing Index with matching Fields; if matched, add unique or shortest name;
                        var matchingIndex = entity.Indexes.FirstOrDefault(ei => ei.Fields.Count == index.Fields.Count && ei.Fields.All(f => index.Fields.Contains(f)));
                        if (matchingIndex != null)
                        {
                            if ((!matchingIndex.IsUnique && index.IsUnique) || // New Index is unique
                                (index.Name.Length < matchingIndex.Name.Length)) // New Index has shorter name
                            {
                                if (matchingIndex.IsClustered)
                                {
                                    throw new InvalidOperationException("Shouldn't replace clustered indexes");
                                }
                                if (matchingIndex.IsPrimary)
                                {
                                    throw new InvalidOperationException("Shouldn't replace primary indexes");
                                }

                                // Remove existing, add new
                                entity.RemoveIndex(matchingIndex);
                                entity.AddIndex(index);
                            }
                        }
                        else
                        {
                            entity.AddIndex(index);
                        }
                    }
                }
                dbConnection.Close();
            }
        }