private static (int ReaderStartIndex, int ReaderEndIndex) CalculateJoinIndexes(ORMEntity parentEntity, ORMEntity childEntity, SQLBuilder sqlBuilder) { var startIndex = 0; foreach (var(name, type) in sqlBuilder.TableOrder) { // The TableOrder (as the name suggests) provides the order from the SqlReader // with all the involved tables. Therefore the parent will always be set first // and we can break once the current join has been found. if (type == parentEntity.GetType()) { startIndex = sqlBuilder.TableNameColumnCount[name]; } // If there are multiple joins, we want to continue adding up the total tally // untill our current join type is found so we know the current index. else if (type != parentEntity.GetType() && type != childEntity.GetType()) { startIndex += sqlBuilder.TableNameColumnCount[name]; } else if (type == childEntity.GetType()) { // We found the indexes based on the parent entity fields and the previous // joins within the sqlBuilder. var endIndex = startIndex + sqlBuilder.TableNameColumnCount[name]; return(startIndex, endIndex); } } // This shouldn't happen, but leaving an exception for now to make sure all cases work as expected. // -Rick, 11 December 2020 throw new NotImplementedException(); }
internal static void FinaliaseEntity(ORMEntity entity) { entity.IsNew = false; if (!entity.DisableChangeTracking) { entity.GetType() .GetProperty(nameof(ORMEntity.OriginalFetchedValue), entity.NonPublicFlags) .SetValue(entity, entity.ShallowCopy()); foreach (var relation in entity.Relations.Where(x => x != null && !x.IsNew)) { entity.OriginalFetchedValue[relation.GetType().Name] = (entity[relation.GetType().Name] as ORMEntity).OriginalFetchedValue; } } if (UnitTestUtilities.IsUnitTesting) { // With unit tests, the ORMPrimaryKey isn't always set - e.g: // A (child) object is spawned from within the framework, therefore the private/internal // constructor is called. In this case, the ORMPrimaryKey value will never be set. // ~ Rick, 12/01/2021 foreach (var primaryKey in entity.PrimaryKey.Keys) { primaryKey.Value = entity[primaryKey.PropertyName]; } } }
private static IDataReader ApplyEntityJoinsToReader(ORMEntity entity, IDataReader reader, SQLBuilder sqlBuilder) { foreach (var join in sqlBuilder.Joins) { if (join.IsManyToMany) { if (join.LeftPropertyInfo.ReflectedType == entity.GetType()) { foreach (var property in entity.GetType().GetProperties()) { if (property.PropertyType == join.RightTableAttribute.CollectionTypeRight && property.CustomAttributes.Any(x => x.AttributeType == typeof(ORMManyToMany))) { var parentDataTable = new DataTable(); parentDataTable.Load(reader); var childTableName = join.RightTableAttribute.EntityType.Name; var childEntity = Activator.CreateInstance(join.RightTableAttribute.EntityType, true) as ORMEntity; var childCollection = Activator.CreateInstance(join.RightTableAttribute.CollectionType) as IEnumerable <ORMEntity>; foreach (var childProperty in childEntity.GetType().GetProperties(childEntity.PublicFlags)) { var executedQuery = (string)childCollection.GetType().GetProperty(nameof(ORMCollection <ORMEntity> .ExecutedQuery)).GetValue(childCollection); if (!string.IsNullOrEmpty(executedQuery)) { break; } #pragma warning disable IDE0019 // Use pattern matching var fkAttribute = childProperty.GetCustomAttributes(typeof(ORMForeignKeyAttribute), false).FirstOrDefault() as ORMForeignKeyAttribute; #pragma warning restore IDE0019 // Use pattern matching if (fkAttribute != null && fkAttribute.Relation == entity.GetType()) { BinaryExpression whereExpression = null; for (int i = 0; i < childEntity.PrimaryKey.Count; i++) { if (!childEntity.IsForeignKeyOfType(childEntity.PrimaryKey.Keys[i].PropertyName, entity.GetType())) { continue; } // Contains the id represented as a MemberExpression: {x.InternalPropertyName}. var memberExpression = Expression.Property(Expression.Parameter(childEntity.GetType(), $"x"), childEntity.GetPrimaryKeyPropertyInfo()[i]); // Contains the actual id represented as a ConstantExpression: {id_value}. var constantExpression = Expression.Constant(entity.PrimaryKey.Keys[i].Value, entity.PrimaryKey.Keys[i].Value.GetType()); // Combines the expressions represtend as a Expression: {(x.InternalPropertyName == id_value)} if (whereExpression == null) { whereExpression = Expression.Equal(memberExpression, constantExpression); } } // Sets the InternalWhere with the WhereExpression. childCollection.GetType().GetMethod(nameof(ORMCollection <ORMEntity> .InternalWhere), entity.NonPublicFlags, null, new Type[] { typeof(BinaryExpression) }, null).Invoke(childCollection, new object[] { whereExpression }); // Fetches the data. childCollection.GetType().GetMethod(nameof(ORMCollection <ORMEntity> .Fetch), childEntity.NonPublicFlags, null, new Type[] { typeof(ORMEntity), typeof(long), typeof(Expression) }, null).Invoke(childCollection, new object[] { null, -1, null }); } } var childCollectionRight = Activator.CreateInstance(join.RightTableAttribute.CollectionTypeRight) as IEnumerable <ORMEntity>; var childEntityRight = Activator.CreateInstance(ORMUtilities.CollectionEntityRelations[childCollectionRight.GetType()]) as ORMEntity; BinaryExpression whereExpressionRight = null; foreach (var childEntityLeft in childCollection) { for (int i = 0; i < childEntityLeft.PrimaryKey.Count; i++) { if (!childEntity.IsForeignKeyOfType(childEntityLeft.PrimaryKey.Keys[i].PropertyName, childEntityRight.GetType())) { continue; } // Contains the id represented as a MemberExpression: {x.InternalPropertyName}. var memberExpressionRight = Expression.Property(Expression.Parameter(childEntityRight.GetType(), $"x"), childEntityRight.GetPrimaryKeyPropertyInfo()[0]); // Contains the actual id represented as a ConstantExpression: {id_value}. var constantExpressionRight = Expression.Constant(childEntityLeft.PrimaryKey.Keys[i].Value, childEntityLeft.PrimaryKey.Keys[i].Value.GetType()); // Combines the expressions represtend as a Expression: {(x.InternalPropertyName == id_value)} if (whereExpressionRight == null) { whereExpressionRight = Expression.Equal(memberExpressionRight, constantExpressionRight); } else { whereExpressionRight = Expression.Or(whereExpressionRight, Expression.Equal(memberExpressionRight, constantExpressionRight)); } } } // We no longer need the old collection; childCollection = Activator.CreateInstance(join.RightTableAttribute.CollectionTypeRight) as IEnumerable <ORMEntity>; // Sets the InternalWhere with the WhereExpression. childCollection.GetType().GetMethod(nameof(ORMCollection <ORMEntity> .InternalWhere), entity.NonPublicFlags, null, new Type[] { typeof(BinaryExpression) }, null).Invoke(childCollection, new object[] { whereExpressionRight }); // Fetches the data. childCollection.GetType().GetMethod(nameof(ORMCollection <ORMEntity> .Fetch), entity.NonPublicFlags, null, new Type[] { typeof(ORMEntity), typeof(long), typeof(Expression) }, null).Invoke(childCollection, new object[] { null, -1, null }); // Sets the ManyToMany collection. property.SetValue(entity, childCollection); reader = parentDataTable.CreateDataReader(); break; } } } } else { foreach (var field in entity.TableScheme) { if (join.LeftPropertyInfo.PropertyType == entity.GetPropertyInfo(field).PropertyType) { var parentDataTable = new DataTable(); parentDataTable.Load(reader); var childTableName = ORMUtilities.CollectionEntityRelations[join.LeftPropertyInfo.PropertyType].Name; var childEntity = Activator.CreateInstance(join.LeftPropertyInfo.PropertyType); var childId = parentDataTable.Rows[0][entity.TableScheme.IndexOf(field)]; var childReader = ORMUtilities.MemoryEntityDatabase.FetchEntityById(childTableName, entity.PrimaryKey.Keys[0], childId); var childDataTable = new DataTable(); childDataTable.Load(childReader); foreach (DataColumn column in childDataTable.Columns) { if (parentDataTable.Columns.Contains(column.ColumnName)) { childDataTable.Columns[column.ColumnName].ColumnName = $"{ childDataTable.TableName }_{ column.ColumnName }"; } } parentDataTable.Merge(childDataTable); var left = parentDataTable.Rows[0]; var right = parentDataTable.Rows[1]; foreach (DataColumn column in left.Table.Columns) { if (left[column] == DBNull.Value) { left[column] = right[column]; } } parentDataTable.Rows.Remove(right); reader = parentDataTable.CreateDataReader(); break; } } } } return(reader); }
internal static void SetEntityProperty(ORMEntity entity, IDataReader reader, SQLBuilder sqlBuilder, int iteration, bool isEntityManyTomany = false) { // All joins (child-entities) are filled through PopulateChildEntity, therefore we can // skip anything past the current entity (parent) within the reader. if (iteration >= sqlBuilder.TableNameColumnCount.First().Value&& !isEntityManyTomany) { // Skipping. return; } var propertyName = reader.GetName(iteration); if (UnitTestUtilities.IsUnitTesting) { propertyName = propertyName.Split('_').Last(); } var entityPropertyInfo = entity.GetPropertyInfo(propertyName); if (null == entityPropertyInfo) { if (propertyName == entity.GetType().Name) { throw new ORMIllegalColumnNameException($"The column [{propertyName}] has not been implemented in entity [{entity.GetType().Name}], but can't have the same name as its enclosing type."); } throw new NotImplementedException($"The column [{propertyName}] has not been implemented in entity [{entity.GetType().Name}]."); } else if (!entityPropertyInfo.CanWrite) { throw new ReadOnlyException($"Property [{propertyName}] is read-only in [{entity.GetType().Name}]."); } object value = null; switch (entityPropertyInfo.PropertyType) { case Type type when type == typeof(DateTime?): value = reader.GetValue(iteration); break; case Type type when type == typeof(DateTime): if (reader.GetValue(iteration) == DBNull.Value) { throw new ORMPropertyNotNullableException($"Property [{propertyName}] is not nullable, but the database column equivelant is."); } value = reader.GetValue(iteration); break; case Type type when type.IsSubclassOf(typeof(ORMEntity)): if (reader.GetValue(iteration) == DBNull.Value) { break; } // If there are no joins provided or none matched the current type we don't want // to fetch the child-object. if (sqlBuilder.Joins.Count == 0 || !sqlBuilder.Joins.Any(x => x.LeftPropertyInfo.PropertyType == type)) { value = null; break; } foreach (var join in sqlBuilder.Joins) { if (join.LeftPropertyInfo.PropertyType == type) { var subEntity = Activator.CreateInstance(type.UnderlyingSystemType) as ORMEntity; PopulateChildEntity(entity, subEntity, reader, sqlBuilder); value = subEntity; entity.Relations.Add(value as ORMEntity); if (entityPropertyInfo == null) { entityPropertyInfo = entity.GetType().GetProperty("Organisation", entity.PublicIgnoreCaseFlags); } break; } } break; default: value = reader.GetValue(iteration); break; } if (UnitTestUtilities.IsUnitTesting) { // Unit tests columns are all of type string, therefore they require to be converted to their respective type. if (Nullable.GetUnderlyingType(entityPropertyInfo.PropertyType) != null && value != DBNull.Value) { value = Convert.ChangeType(value, Nullable.GetUnderlyingType(entityPropertyInfo.PropertyType)); } else if (!entityPropertyInfo.PropertyType.IsSubclassOf(typeof(ORMEntity)) && value != DBNull.Value) { value = Convert.ChangeType(value, entityPropertyInfo.PropertyType); } } if (reader.GetValue(iteration) == DBNull.Value) { entityPropertyInfo.SetValue(entity, null); } else { entityPropertyInfo.SetValue(entity, value); } }
private static void PopulateManyToManyEntity(ORMEntity entity, IDataReader reader, SQLBuilder sqlBuilder) { Dictionary <ORMPrimaryKey, Dictionary <string, List <ORMEntity> > > manyToManyData = new Dictionary <ORMPrimaryKey, Dictionary <string, List <ORMEntity> > >(new ORMPrimaryKey()); var manyToManyJoinIndexes = new List <(string, int[])>(); var manyToManyJoinTypes = new Dictionary <string, Type>(); var tableIndex = 0; foreach (var(name, _) in sqlBuilder.TableOrder) { var objectPath = sqlBuilder.TableNameResolvePaths.ContainsKey(name) ? sqlBuilder.TableNameResolvePaths[name] : string.Empty; var tableColumnCount = sqlBuilder.TableNameColumnCount[name]; if (objectPath.StartsWith(SQLBuilder.MANY_TO_MANY_JOIN_DATA, StringComparison.Ordinal)) { var indexes = new List <int>(); for (int i = 0; i < tableColumnCount; i++) { indexes.Add(tableIndex + i); } manyToManyJoinIndexes.Add((objectPath.Split('.')[1], indexes.ToArray())); } tableIndex += tableColumnCount; } void AddManyToManyObject(ORMPrimaryKey key, IDataReader reader) { Dictionary <string, List <ORMEntity> > relations; if (manyToManyData.ContainsKey(key)) { relations = manyToManyData[key]; } else { relations = new Dictionary <string, List <ORMEntity> >(); manyToManyData[key] = relations; } foreach (var(fieldName, indexes) in manyToManyJoinIndexes) { bool IsRowEmpty(List <(string, int[])> manyToManyJoinIndexes, IDataReader reader) { foreach (var(fieldName, indexes) in manyToManyJoinIndexes) { for (int i = 0; i < indexes.Length; i++) { if (reader.GetValue(indexes[i]) == DBNull.Value) { if ((i + 1) == indexes.Length) { return(true); } } else { return(false); } } } return(true); } if (!IsRowEmpty(manyToManyJoinIndexes, reader)) { var instance = (ORMEntity)Activator.CreateInstance(manyToManyJoinTypes[fieldName]); foreach (var index in indexes) { SetEntityProperty(instance, reader, sqlBuilder, index, true); } if (relations.ContainsKey(fieldName)) { relations[fieldName].Add(instance); } else { relations[fieldName] = new List <ORMEntity>() { instance }; } } } } int[] primaryKeyIndexes = ORMPrimaryKey.DeterminePrimaryKeyIndexes(reader, entity); foreach (var(fieldName, _) in manyToManyJoinIndexes) { var type = entity.GetPropertyInfo(fieldName).PropertyType; if (!typeof(ORMEntity).IsAssignableFrom(type.GetType())) { type = ORMUtilities.CollectionEntityRelations[type]; } manyToManyJoinTypes.Add(fieldName, type); } ORMPrimaryKey pk = new ORMPrimaryKey(reader, primaryKeyIndexes); //PopulateEntity(entity, reader, sqlBuilder); AddManyToManyObject(pk, reader); foreach (var kvPair in manyToManyData) { foreach (var data in kvPair.Value) { var property = entity.GetType().GetProperty(data.Key, entity.PublicFlags); if (typeof(IORMCollection <ORMEntity>).IsAssignableFrom(property.PropertyType)) { var propertyValue = entity.GetType().GetProperty(data.Key, entity.PublicFlags).GetValue(entity); if (propertyValue == null) { var subcollection = Activator.CreateInstance(property.PropertyType); var collectionProperty = property.PropertyType.GetProperty(nameof(ORMCollection <ORMEntity> .MutableEntityCollection), entity.NonPublicFlags); var list = collectionProperty.GetValue(subcollection) as IList; foreach (var item in data.Value) { list.Add(item); } property.SetValue(entity, subcollection); } else { foreach (var item in data.Value) { propertyValue.GetType().GetMethod("Add", entity.PublicFlags).Invoke(propertyValue, new object[] { item }); } } } else { throw new Exception("Something went wrong trying to cast to a subcollection"); } } } }