public TEntity[] CreateEntities <TEntity>(DbDataReader dataReader, QueryBuilder <TEntity> builder) where TEntity : Entity, new() { var fieldCount = builder.PropertySetter.Length; var arrayFieldCount = 0; var classFieldCount = 0; var structFieldCount = 0; // Return an empty array if the used DbDataReader is null or doesn't contain any rows. if (dataReader?.Read() == false) { return(new TEntity[0]); } var pluralizedEntityName = Pluralize <TEntity>(); for (var i = 0; i < builder.Properties.Length; i++) { if (builder.Properties[i].PropertyType.IsArray) { var arr = builder.Properties[i].GetValue(new TEntity()) as Array; arrayFieldCount += arr.Length - 1; } else if (builder.Properties[i].PropertyType.IsCustomClass()) { classFieldCount += builder.Properties[i].PropertyType.GetReadWriteProperties().Length - 1; } else if (builder.Properties[i].PropertyType.IsCustomStruct()) { structFieldCount += builder.Properties[i].PropertyType.GetTypeInfo().GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly).Length - 1; } } // Workaround for types like Datetime. if (structFieldCount < 0) { structFieldCount = 0; } var totalFieldCount = fieldCount + arrayFieldCount + classFieldCount + structFieldCount; if (dataReader.FieldCount != totalFieldCount) { database.Log.Message(LogTypes.Error, $"Table '{pluralizedEntityName}' (Column/Property count mismatch)\nColumns '{dataReader.FieldCount}'\nProperties '{totalFieldCount}'"); return(new TEntity[0]); } // TODO: // - Move this check to a new Manager class, so it's done one time on initialization. // - Fix array field type checks. // - Optimize?! // Strict types (signed/unsigned) only used in MySql databases. /* * if (database.Type == DatabaseType.MySql) * { * for (var i = 0; i < fieldCount; i++) * { * if (builder.Properties[i].PropertyType == typeof(bool) || builder.Properties[i].PropertyType.IsArray || * builder.Properties[i].PropertyType.IsCustomClass() || builder.Properties[i].PropertyType.IsCustomStruct()) * continue; * * // Return an empty list if any column/property type mismatches * if (!dataReader.GetFieldType(i).GetTypeInfo().IsEquivalentTo(builder.Properties[i].PropertyType.GetTypeInfo().IsEnum ? * builder.Properties[i].PropertyType.GetTypeInfo().GetEnumUnderlyingType() : builder.Properties[i].PropertyType)) * { * var propertyType = builder.Properties[i].PropertyType.GetTypeInfo().IsEnum ? builder.Properties[i].PropertyType.GetTypeInfo().GetEnumUnderlyingType() : builder.Properties[i].PropertyType; * * database.Log.Message(LogTypes.Error, $"Table '{pluralizedEntityName}' (Column/Property type mismatch)"); * database.Log.Message(LogTypes.Error, $"{dataReader.GetName(i)}: {dataReader.GetFieldType(i)}/{propertyType}"); * * return new TEntity[0]; * } * } * }*/ var entities = new ConcurrentBag <TEntity>(); // Create one test object for foreign key assignment check var foreignKeys = typeof(TEntity).GetTypeInfo().DeclaredProperties.Where(p => p.GetMethod.IsVirtual).ToArray(); // Key: GroupStartIndex, Value: GroupCount var groups = new ConcurrentDictionary <int, int>(); var lastGroupName = ""; var lastGroupStartIndex = 0; // Get Groups for (var i = 0; i < fieldCount; i++) { var group = builder.Properties[i].GetCustomAttribute <GroupAttribute>(); if (group != null) { if (group.Name == lastGroupName) { ++groups[lastGroupStartIndex]; } else { lastGroupName = group.Name; lastGroupStartIndex = i; groups.TryAdd(lastGroupStartIndex, 1); } } } var assignForeignKeys = new TEntity().LoadForeignKeys&& foreignKeys.Length > 0 && groups.Count == 0; do { var entity = new TEntity(); var row = new object[totalFieldCount]; // TODO: Should be safe without any additional checks? dataReader.GetValues(row); for (int j = 0, a = 0; j < fieldCount; j++) { if (!builder.Properties[j].PropertyType.IsArray) { if (builder.Properties[j].PropertyType.IsCustomClass()) { var instanceFields = builder.Properties[j].PropertyType.GetReadWriteProperties(); var instance = Activator.CreateInstance(builder.Properties[j].PropertyType); for (var f = 0; f < instanceFields.Length; f++) { instanceFields[f].SetValue(instance, dataReader.IsDBNull(j + f) ? "" : row[j + f]); } builder.PropertySetter[j](entity, instance); } else if (builder.Properties[j].PropertyType.IsCustomStruct()) { var instanceFields = builder.Properties[j].PropertyType.GetTypeInfo().GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly).ToArray(); // Workaround for types like Datetime. if (instanceFields.Length > 0) { var instance = Activator.CreateInstance(builder.Properties[j].PropertyType); for (var f = 0; f < instanceFields.Length; f++) { instanceFields[f].SetValue(instance, dataReader.IsDBNull(j + f) ? "" : row[j + f].ChangeTypeGet(builder.Properties[j + f].PropertyType)); } builder.PropertySetter[j](entity, instance); } else { builder.PropertySetter[j](entity, dataReader.IsDBNull(j) ? "" : row[j].ChangeTypeGet(builder.Properties[j].PropertyType)); } } else { builder.PropertySetter[j](entity, dataReader.IsDBNull(j) ? "" : row[j].ChangeTypeGet(builder.Properties[j].PropertyType)); } } else { if (groups.TryGetValue(j, out int groupCount)) { for (var c = 0; c < groupCount; c++, j++) { var arr = builder.Properties[j].GetValue(entity) as Array; for (var k = 0; k < arr.Length; k++, a++) { arr.SetValue(row[j + (k * groupCount)], k); } builder.PropertySetter[j](entity, arr); // TODO: Test field groups. // -1 for each new array field. --a; } } else { var arr = builder.Properties[j].GetValue(entity) as Array; for (var k = 0; k < arr.Length; k++, a++) { arr.SetValue(row[j + a], k); } builder.PropertySetter[j](entity, arr); // -1 for each new array field. --a; } } } // TODO Fix group assignment in foreign keys. if (assignForeignKeys) { database.AssignForeignKeyData(entity, foreignKeys, groups); } entity.InitializeNonTableProperties(); entities.Add(entity); } while (dataReader.Read()); return(entities.ToArray()); }
// MySql only. // TODO: Fix for MSSql & SQLite public async ValueTask <bool> CreateAsync <TEntity>(MySqlEngine dbEngine = MySqlEngine.MyISAM, bool replaceTable = false) where TEntity : Entity, new() { if (Connector.Settings.DatabaseType != DatabaseType.MySql) { return(false); } // Check if table exists or is allowed to be replaced. if (!await ExistsAsync <TEntity>() || replaceTable) { // Exclude foreign key and non db related properties. var properties = typeof(TEntity).GetReadWriteProperties(); var fields = new Dictionary <string, PropertyInfo>(); var query = new QueryBuilder <TEntity>(Connector.Query, properties); var entity = new TEntity(); // Key: GroupStartIndex, Value: GroupCount var groups = new ConcurrentDictionary <int, int>(); var lastGroupName = ""; var lastGroupStartIndex = 0; // Get Groups for (var i = 0; i < properties.Length; i++) { var group = properties[i].GetCustomAttribute <GroupAttribute>(); if (group != null) { if (group.Name == lastGroupName) { groups[lastGroupStartIndex] += 1; } else { lastGroupName = group.Name; lastGroupStartIndex = i; groups.TryAdd(lastGroupStartIndex, 1); } } } for (var i = 0; i < properties.Length; i++) { var groupCount = 0; if (!properties[i].PropertyType.IsArray) { fields.Add(properties[i].GetName(), properties[i]); } else { if (groups.TryGetValue(i, out groupCount)) { var arr = properties[i].GetValue(Activator.CreateInstance(typeof(TEntity))) as Array; for (var k = 1; k <= arr.Length; k++) { for (var j = 0; j < groupCount; j++) { fields.Add(properties[i + j].GetName() + k, properties[i + j]); } } i += groupCount - 1; } } } return(await ExecuteAsync(null));// query.BuildTableCreate(fields, dbEngine)); } return(false); }