/// <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; }
/// <summary> /// Generates mappings. /// </summary> /// <param name="entityResources">The entity resources.</param> /// <param name="enumResources">The enum resources.</param> /// <param name="itemCollection">The item collection.</param> /// <param name="container">The entity container.</param> /// <returns></returns> /// <exception cref="System.ArgumentNullException">itemCollection</exception> /// <exception cref="System.Exception">Unknown data type: + p.TypeUsage.EdmType.Name</exception> private static Metadata Mapping(IEnumerable<EntityResource> entityResources, IEnumerable<EnumResource> enumResources, IEnumerable<GlobalItem> itemCollection, EntityContainer container) { if (itemCollection == null) throw new ArgumentNullException("itemCollection"); var globalItems = itemCollection as IList<GlobalItem> ?? itemCollection.ToList(); var retVal = new Metadata(container.Name); // entity types foreach (var er in entityResources) { var fullName = string.Format("{0}, {1}", er.ClrType.FullName, er.ClrType.Assembly.GetName().Name); var et = new Meta.EntityType(fullName, er.Name) {TableName = er.TableName}; // entity informations if (er.Entity != null) { et.QueryName = er.EntitySet.Name; if (er.EntitySet.ElementType.Name != er.Name) et.QueryType = er.EntitySet.ElementType.Name; et.Keys.AddRange(er.Entity.KeyMembers.Select(k => k.Name)); // if entity has base type, set the base type's name if (er.Entity.BaseType != null) et.BaseTypeName = er.Entity.BaseType.Name; if (er.ClrType != null) et.ClrType = er.ClrType; // navigation properties if (er.NavigationProperties != null) { foreach (var p in er.NavigationProperties) { var ass = globalItems.OfType<AssociationType>().First(a => a.Name == p.RelationshipType.Name); Func<string> displayNameGetter = null; if (er.ClrType != null) { var propertyInfo = er.ClrType.GetMember(p.Name).FirstOrDefault(); if (propertyInfo != null) displayNameGetter = Helper.GetDisplayNameGetter(propertyInfo); } var np = new Meta.NavigationProperty(p.Name, displayNameGetter); np.EntityTypeName = (((RefType)p.ToEndMember.TypeUsage.EdmType).ElementType).Name; var isScalar = p.ToEndMember.RelationshipMultiplicity != RelationshipMultiplicity.Many; if (isScalar) { np.IsScalar = true; np.ForeignKeys.AddRange(ass.ReferentialConstraints.SelectMany(rc => rc.ToProperties.Select(tp => tp.Name))); } if (p.FromEndMember.DeleteBehavior == OperationAction.Cascade) np.DoCascadeDelete = true; if (er.ClrType != null) Helper.PopulateNavigationPropertyValidations(er.ClrType, np); np.AssociationName = p.RelationshipType.Name; et.NavigationProperties.Add(np); } } // complex properties if (er.ComplexProperties != null) { foreach (var p in er.ComplexProperties) { Func<string> displayNameGetter = null; if (er.ClrType != null) { var propertyInfo = er.ClrType.GetMember(p.Key.Name).FirstOrDefault(); if (propertyInfo != null) displayNameGetter = Helper.GetDisplayNameGetter(propertyInfo); } var cp = new ComplexProperty(p.Key.Name, displayNameGetter) { TypeName = p.Key.TypeUsage.EdmType.Name, Mappings = p.Value }; et.ComplexProperties.Add(cp); } } } else et.IsComplexType = true; // this is a complex type // data properties foreach (var sp in er.SimpleProperties) { var p = sp.Value; var clrType = UnderlyingClrType(p.TypeUsage.EdmType); Func<string> displayNameGetter = null; if (er.ClrType != null) { var propertyInfo = clrType.GetMember(p.Name).FirstOrDefault(); if (propertyInfo != null) displayNameGetter = Helper.GetDisplayNameGetter(propertyInfo); } var dp = new DataProperty(p.Name, displayNameGetter) { ColumnName = sp.Key }; var jsType = DataType.Binary; var enumType = p.TypeUsage.EdmType as EnumType; if (enumType != null) { dp.IsEnum = true; dp.EnumType = enumType.Name; } else { // convert CLR type to javascript type if (clrType == typeof(string)) jsType = DataType.String; else if (clrType == typeof(Guid)) jsType = DataType.Guid; else if (clrType == typeof(DateTime)) jsType = DataType.Date; else if (clrType == typeof(DateTimeOffset)) jsType = DataType.DateTimeOffset; else if (clrType == typeof(TimeSpan)) jsType = DataType.Time; else if (clrType == typeof(bool)) jsType = DataType.Boolean; else if (clrType == typeof(Int16) || clrType == typeof(Int64) || clrType == typeof(Int32)) jsType = DataType.Int; else if (clrType == typeof(Single) || clrType == typeof(double) || clrType == typeof(decimal)) jsType = DataType.Number; else if (clrType == typeof(byte)) jsType = DataType.Byte; else if (clrType == typeof(DbGeography)) jsType = DataType.Geography; else if (clrType == typeof(DbGeometry)) jsType = DataType.Geometry; else if (clrType == typeof(byte[])) jsType = DataType.Binary; else throw new BeetleException(Resources.UnknownDataType + p.TypeUsage.EdmType.Name); } dp.DataType = jsType; var generated = p.MetadataProperties.FirstOrDefault(m => m.Name == StoreGeneratedPatternAttributeName); if (generated == null) dp.GenerationPattern = GenerationPattern.None; else if (generated.Value.ToString().StartsWith("I")) dp.GenerationPattern = GenerationPattern.Identity; else dp.GenerationPattern = GenerationPattern.Computed; dp.UseForConcurrency = IsConcurrencyProperty(p); if (p.Nullable) dp.IsNullable = true; if (jsType == DataType.Number) { var precision = GetPrecision(p); var scale = GetScale(p); if (precision.HasValue) dp.Precision = precision; if (scale.HasValue) dp.Scale = scale; } if (er.ClrType != null) Helper.PopulateDataPropertyValidations(er.ClrType, dp, GetMaxStringLength(p)); if (p.DefaultValue != null) dp.DefaultValue = p.DefaultValue; et.DataProperties.Add(dp); } retVal.Entities.Add(et); } // enum types foreach (var enumResource in enumResources) { var enumType = enumResource.EnumType; //var enumClrType = enumResource.ClrType; var et = new Meta.EnumType(enumType.Name); foreach (var member in enumType.Members) { //todo: enum member için display var em = new Meta.EnumMember(member.Name, null) {Value = member.Value}; et.Members.Add(em); } retVal.Enums.Add(et); } retVal.FixReferences(); return retVal; }
/// <summary> /// Gets the generated values comparing Entity-ClientEntity for each EntityBag. /// </summary> /// <param name="entityBags">The entity bags.</param> /// <param name="metadata">The metadata.</param> /// <returns> /// Generated-modified values for EntityBags. /// </returns> public static IEnumerable<GeneratedValue> GetGeneratedValues(IEnumerable<EntityBag> entityBags, Metadata metadata) { var generatedValues = new List<Tuple<GeneratedValue, bool>>(); // populate auto-generated values foreach (var entityBagTmp in entityBags.Where(el => el.EntityState == EntityState.Added || el.EntityState == EntityState.Modified)) { var entityBag = entityBagTmp; var entityType = entityBag.EntityType; if (entityType == null) { if (metadata == null) throw new BeetleException(Resources.CannotGetGeneratedValues); var type = entityBag.Entity.GetType(); var entityTypeName = string.Format("{0}, {1}", type.FullName, type.Assembly.GetName().Name); entityType = metadata.Entities.FirstOrDefault(e => e.Name == entityTypeName); if (entityType == null) throw new BeetleException(string.Format(Resources.CannotFindMetadata, entityTypeName)); } var changedValues = GetGeneratedValues(entityBag.ClientEntity, entityBag.Entity, entityType); if (changedValues != null) { foreach (var changedValue in changedValues) { var isKeyPart = entityType.Keys.Contains(changedValue.Key); generatedValues.Add(new Tuple<GeneratedValue, bool>(new GeneratedValue(entityBag.Index, changedValue.Key, changedValue.Value), isKeyPart)); } } } return generatedValues.OrderByDescending(x => x.Item2).Select(x => x.Item1); }