/// <summary> /// Adds the navigation property validations. /// </summary> /// <param name="clrType">Type of the color.</param> /// <param name="navigationProperty">The navigation property.</param> public static void PopulateNavigationPropertyValidations(Type clrType, NavigationProperty navigationProperty) { var clrProperty = clrType.GetMember(navigationProperty.Name).FirstOrDefault(); var displayName = navigationProperty.GetDisplayName(); var dataAnnotations = clrProperty.GetAttributes<ValidationAttribute>(true); foreach (var att in dataAnnotations) { string msg; var rmsg = att.ErrorMessageResourceName; try { msg = att.FormatErrorMessage(displayName); } catch { msg = null; } var mal = att as MaxLengthAttribute; if (mal != null && mal.Length > 0) { navigationProperty.Validators.Add(Meta.Validator.MaxLength(msg, rmsg, mal.Length)); continue; } var mil = att as MinLengthAttribute; if (mil != null && mil.Length > 0) navigationProperty.Validators.Add(Meta.Validator.MinLength(msg, rmsg, mil.Length)); } }
/// <summary> /// Gets the metadata. /// </summary> /// <param name="connection">The connection.</param> /// <param name="modelNamespace">The model namespace.</param> /// <param name="modelAssemblyName">Name of the model assembly.</param> /// <returns></returns> public static Metadata GetMetadata(IDbConnection connection, string modelNamespace = null, string modelAssemblyName = null) { var metadata = new Metadata(connection.Database); bool closeConnection; if (connection.State == ConnectionState.Closed) { connection.Open(); closeConnection = true; } else closeConnection = false; using (var tablesCommand = connection.CreateCommand()) { const string schemaSql = @" select distinct t.TABLE_NAME, c.COLUMN_NAME, c.COLUMN_DEFAULT, c.IS_NULLABLE, c.DATA_TYPE, c.CHARACTER_MAXIMUM_LENGTH, c.NUMERIC_PRECISION, c.NUMERIC_SCALE, case when COALESCE(ck.COLUMN_NAME, '') = '' then 0 else 1 end as IsKey, {0} as GenerationPattern from INFORMATION_SCHEMA.TABLES t inner join INFORMATION_SCHEMA.COLUMNS c on t.TABLE_NAME = c.TABLE_NAME left join INFORMATION_SCHEMA.TABLE_CONSTRAINTS tk on t.TABLE_NAME = tk.TABLE_NAME and tk.CONSTRAINT_TYPE = 'PRIMARY KEY' left join INFORMATION_SCHEMA.KEY_COLUMN_USAGE ck on t.TABLE_NAME = ck.TABLE_NAME and c.COLUMN_NAME = ck.COLUMN_NAME order by t.TABLE_NAME, c.COLUMN_NAME "; const string sqlServerGenerationPatternSql = "columnproperty(object_id(t.TABLE_NAME), c.COLUMN_NAME, 'IsIdentity')+(2*columnproperty(object_id(t.TABLE_NAME), c.COLUMN_NAME, 'IsComputed'))"; const string sqlCeGenerationPatternSql = "case when AUTOINC_NEXT > 0 then 1 else 0 end"; const string mySqlGenerationPatternSql = "INSTR(EXTRA, 'auto_increment')"; string generationPatternSql; // not the ideal solution but I couldn't find a better way var connectionType = connection.GetType().ToString(); if (connectionType.Contains("SqlConnection")) generationPatternSql = sqlServerGenerationPatternSql; else if (connectionType.Contains("SqlCe")) generationPatternSql = sqlCeGenerationPatternSql; else if (connectionType.Contains("MySql") || connectionType.Contains("Oracle")) generationPatternSql = mySqlGenerationPatternSql; else generationPatternSql = "0"; tablesCommand.CommandText = string.Format(schemaSql, generationPatternSql); using (var tablesReader = tablesCommand.ExecuteReader()) { EntityType entityType = null; Type clrType = null; while (tablesReader.Read()) { var tableName = tablesReader.GetString(0); var columnName = tablesReader.GetString(1); var defaultValue = tablesReader.GetValue(2); var isNullable = tablesReader.GetString(3) == "YES"; var dataType = tablesReader.GetString(4); var maxLength = tablesReader.GetValue(5); var precision = tablesReader.GetValue(6); var scale = tablesReader.GetValue(7); var isKey = tablesReader.GetInt32(8) > 0; var generationPattern = tablesReader.GetInt32(9); if (entityType == null || entityType.ShortName != tableName) { var fullName = tableName; if (!string.IsNullOrEmpty(modelNamespace)) fullName = modelNamespace + "." + fullName; if (!string.IsNullOrEmpty(modelAssemblyName)) fullName += ", " + modelAssemblyName; // we use fullName to create instance of model class entityType = new EntityType(fullName, tableName) { QueryName = Pluralize(tableName) }; clrType = Type.GetType(entityType.Name); entityType.ClrType = clrType; metadata.Entities.Add(entityType); } var dataTypeEnum = GetDataType(dataType); if (defaultValue != null) { var defaultStr = defaultValue.ToString(); // todo: check if MySql, Oracle etc returns default value in parenthesis if (defaultStr.StartsWith("((")) defaultStr = defaultStr.Substring(2, defaultStr.Length - 4); else if (defaultStr.StartsWith("(")) defaultStr = defaultStr.Substring(1, defaultStr.Length - 2); defaultValue = defaultStr; if (dataTypeEnum == DataType.Boolean) defaultValue = defaultStr == "1"; } Func<string> displayNameGetter = null; if (clrType != null) { var propertyInfo = clrType.GetMember(columnName).FirstOrDefault(); if (propertyInfo != null) displayNameGetter = GetDisplayNameGetter(propertyInfo); } var dataProperty = new DataProperty(columnName, displayNameGetter) { ResourceName = columnName, DataType = dataTypeEnum, DefaultValue = defaultValue == DBNull.Value ? null : defaultValue, EnumType = null, GenerationPattern = (GenerationPattern)generationPattern, // we cannot read this from INFORMATION_SCHEMA IsEnum = false, IsNullable = isNullable, Precision = precision == DBNull.Value ? (int?)null : Convert.ToInt32(precision), Scale = scale == DBNull.Value ? (int?)null : Convert.ToInt32(scale) }; var iMaxLength = maxLength == DBNull.Value ? (int?)null : (int)maxLength; if (!isNullable) dataProperty.Validators.Add(Meta.Validator.Required(string.Format(Resources.RequiredError, columnName), "requiredError", false)); if (iMaxLength.HasValue) dataProperty.Validators.Add(Meta.Validator.StringLength(string.Format(Resources.MaxLenError, columnName), "maxLenError", 0, iMaxLength.Value)); if (isKey) entityType.Keys.Add(columnName); if (clrType != null) PopulateDataPropertyValidations(clrType, dataProperty, iMaxLength); entityType.DataProperties.Add(dataProperty); } } } using (var navigationCommand = connection.CreateCommand()) { navigationCommand.CommandText = @" select distinct FK.TABLE_NAME, CU.COLUMN_NAME, case when coalesce(CU2.COLUMN_NAME, '') = '' then 0 else 1 end as IsOneToOne, PK.TABLE_NAME, C.CONSTRAINT_NAME, c.DELETE_RULE from INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS C inner join INFORMATION_SCHEMA.TABLE_CONSTRAINTS FK on C.CONSTRAINT_NAME = FK.CONSTRAINT_NAME inner join INFORMATION_SCHEMA.TABLE_CONSTRAINTS PK on C.UNIQUE_CONSTRAINT_NAME = PK.CONSTRAINT_NAME inner join INFORMATION_SCHEMA.KEY_COLUMN_USAGE CU on C.CONSTRAINT_NAME = CU.CONSTRAINT_NAME left join INFORMATION_SCHEMA.TABLE_CONSTRAINTS FK2 on FK2.TABLE_NAME = FK.TABLE_NAME and FK2.CONSTRAINT_TYPE = 'PRIMARY KEY' left join INFORMATION_SCHEMA.KEY_COLUMN_USAGE CU2 on FK2.CONSTRAINT_NAME = CU2.CONSTRAINT_NAME and CU.COLUMN_NAME = CU2.COLUMN_NAME "; using (var navigationReader = navigationCommand.ExecuteReader()) { while (navigationReader.Read()) { var fkTable = navigationReader.GetString(0); var fkColumn = navigationReader.GetString(1); var isOneToOne = navigationReader.GetInt32(2) > 0; var pkTable = navigationReader.GetString(3); var constraintName = navigationReader.GetString(4); var cascadeDelete = navigationReader.GetString(5) == "CASCADE"; var fkEntity = metadata.Entities.First(e => e.ShortName == fkTable); var fkNavigation = fkEntity.NavigationProperties.FirstOrDefault(np => np.AssociationName == constraintName); if (fkNavigation == null) { Func<string> displayNameGetter = null; if (fkEntity.ClrType != null) { var propertyInfo = fkEntity.ClrType.GetMember(fkNavigation.Name).FirstOrDefault(); if (propertyInfo != null) displayNameGetter = GetDisplayNameGetter(propertyInfo); } fkNavigation = new NavigationProperty(pkTable, displayNameGetter) { ResourceName = pkTable, AssociationName = constraintName, DoCascadeDelete = false, EntityTypeName = pkTable, IsScalar = true }; fkEntity.NavigationProperties.Add(fkNavigation); } fkNavigation.ForeignKeys.Add(fkColumn); if (fkEntity.ClrType != null) PopulateNavigationPropertyValidations(fkEntity.ClrType, fkNavigation); var pkEntity = metadata.Entities.First(e => e.ShortName == pkTable); var pkNavigation = pkEntity.NavigationProperties.FirstOrDefault(np => np.AssociationName == constraintName); if (pkNavigation == null) { Func<string> displayNameGetter = null; if (pkEntity.ClrType != null) { var propertyInfo = pkEntity.ClrType.GetMember(pkNavigation.Name).FirstOrDefault(); if (propertyInfo != null) displayNameGetter = GetDisplayNameGetter(propertyInfo); } var pkNavName = isOneToOne ? fkTable : Pluralize(fkTable); pkNavigation = new NavigationProperty(pkNavName, displayNameGetter) { ResourceName = pkNavName, AssociationName = constraintName, DoCascadeDelete = cascadeDelete, EntityTypeName = fkTable, IsScalar = isOneToOne }; pkEntity.NavigationProperties.Add(pkNavigation); } else pkNavigation.IsScalar &= isOneToOne; if (pkEntity.ClrType != null) PopulateNavigationPropertyValidations(pkEntity.ClrType, pkNavigation); } } } if (closeConnection) connection.Close(); metadata.FixReferences(); return metadata; }