private static void TraverseBreadthFirst(ChangeNode rootNode, List <ChangeNode> accumulator, Predicate <ChangeNode> predicate) { if (accumulator == null) { accumulator = new List <ChangeNode>(); } foreach (var childNode in rootNode.Nodes) { TraverseBreadthFirst(childNode, accumulator, predicate); } if ((rootNode.IsLeaf || rootNode.IsObject) && predicate != null && predicate(rootNode)) { accumulator.Add(rootNode); } }
private static List <ChangeNode> GetChanges(ChangeNode rootNode, ObjectState objectState) { var result = new List <ChangeNode>(); switch (objectState) { case ObjectState.New: TraverseDepthFirst(rootNode, result, n => n.ObjectState == ObjectState.New); break; case ObjectState.Deleted: TraverseBreadthFirst(rootNode, result, n => n.ObjectState == ObjectState.Deleted); break; case ObjectState.Dirty: TraverseDepthFirst(rootNode, result, n => n.ObjectState == ObjectState.Dirty); break; } return(result); }
private static Tuple <string, Param[], List <object> > GetCommitStatement(ChangeNode rootNode, DbConnection connection) { var dialect = DialectFactory.GetProvider(connection); var dataEntities = new List <object>(); var sql = new StringBuilder(); var statementId = 0; // Inserts var newNodes = GetChanges(rootNode, ObjectState.New); var tableCreated = false; var tempTableName = dialect.GetTemporaryTableName("ID"); var allParameters = new List <Param>(); foreach (var newNode in newNodes.Where(n => n.IsObject)) { var item = newNode.Value; if (item != null) { dataEntities.Add(item); } else { continue; } var objectType = newNode.Value.GetType(); var propertyMap = Reflector.GetPropertyMap(objectType); var autoGenProperty = propertyMap.Where(p => p.Key.CanWrite && p.Value != null && p.Value.IsAutoGenerated).Select(p => p.Key).FirstOrDefault(); var autoGenType = DbType.String; if (!tableCreated) { if (autoGenProperty != null) { autoGenType = Reflector.ClrToDbType(autoGenProperty.PropertyType); } sql.AppendFormat(dialect.CreateTemporaryTable("ID", new Dictionary <string, DbType> { { "StatementId", DbType.Int32 }, { "GeneratedId", autoGenType }, { "ParameterName", DbType.AnsiString }, { "PropertyName", DbType.AnsiString } })).AppendLine(); tableCreated = true; } var parameters = ObjectExtensions.GetInsertParameters(newNode.Value, propertyMap, statementId); allParameters.AddRange(parameters); if (parameters.Length > 0) { string commandName = SqlBuilder.GetInsertStatement(objectType, parameters, dialect); var autoGenParameter = parameters.FirstOrDefault(p => p.IsAutoGenerated); if (autoGenParameter != null) { sql.AppendFormat(dialect.DeclareVariable(autoGenParameter.Name, autoGenType)).AppendLine(); sql.AppendFormat(dialect.AssignVariable(autoGenParameter.Name, autoGenParameter.Type.GetDefault())).AppendLine(); } sql.AppendLine(commandName); if (autoGenParameter != null) { sql.AppendFormat(dialect.ComputeAutoIncrement(autoGenParameter.Name, () => SqlBuilder.GetTableNameForSql(objectType, dialect))).AppendLine(); sql.AppendFormat("INSERT INTO " + tempTableName + " ({4}StatementId{5}, {4}GeneratedId{5}, {4}ParameterName{5}, {4}PropertyName{5}) VALUES ({0}, {1}, '{2}', '{3}')", statementId, dialect.EvaluateVariable(dialect.ParameterPrefix + autoGenParameter.Name), autoGenParameter.Name, autoGenProperty.Name, dialect.IdentifierEscapeStartCharacter, dialect.IdentifierEscapeEndCharacter).AppendLine(); } statementId++; } } if (newNodes.Count > 0) { sql.AppendLine("SELECT * FROM " + tempTableName); } if (tableCreated && !dialect.SupportsTemporaryTables) { sql.AppendLine("DROP TABLE " + tempTableName); } // Updates var dirtyNodes = GetChanges(rootNode, ObjectState.Dirty); var dirtyNodeParents = dirtyNodes.Where(n => n.IsSimpleLeaf).Select(n => n.Parent).Distinct(); foreach (var dirtyNode in dirtyNodeParents) { var objectType = dirtyNode.Value.GetType(); var propertyMap = Reflector.GetPropertyMap(objectType).ToDictionary(p => p.Key.Name, p => p); var parameters = new List <Param>(); foreach (var change in dirtyNode.Nodes.Where(n => n.IsSimpleLeaf)) { KeyValuePair <PropertyInfo, ReflectedProperty> map; if (propertyMap.TryGetValue(change.PropertyName, out map)) { var property = map.Key; var parameterName = change.PropertyName; if (map.Value != null && !string.IsNullOrEmpty(map.Value.ParameterName)) { parameterName = map.Value.ParameterName; } parameters.Add(new Param { Name = parameterName + "_" + statementId, Value = change.Value, Source = MapColumnAttribute.GetMappedColumnName(property) }); } } var primaryKey = new List <Param>(); foreach (var primaryKeyMap in propertyMap.Values.Where(p => p.Value.IsPrimaryKey)) { var value = dirtyNode.Value.Property(primaryKeyMap.Key.Name); var parameterName = primaryKeyMap.Key.Name; if (primaryKeyMap.Value != null && !string.IsNullOrEmpty(primaryKeyMap.Value.ParameterName)) { parameterName = primaryKeyMap.Value.ParameterName; } primaryKey.Add(new Param { Name = parameterName + "_" + statementId, Value = value, Source = MapColumnAttribute.GetMappedColumnName(primaryKeyMap.Key) }); } var commandName = SqlBuilder.GetUpdateStatement(objectType, parameters, primaryKey, dialect); allParameters.AddRange(parameters); allParameters.AddRange(primaryKey); sql.Append(commandName).AppendLine(); statementId++; } // Deletes var deletedNodes = GetChanges(rootNode, ObjectState.Deleted); foreach (var deletedNode in deletedNodes) { var objectType = deletedNode.Value.GetType(); var propertyMap = Reflector.GetPropertyMap(objectType); var parameters = ObjectExtensions.GetDeleteParameters(deletedNode.Value, propertyMap, statementId); var commandName = SqlBuilder.GetDeleteStatement(objectType, parameters, dialect); allParameters.AddRange(parameters); sql.Append(commandName).AppendLine(); statementId++; } return(Tuple.Create(sql.ToString(), allParameters.ToArray(), dataEntities)); }
private static List <ChangeNode> CompareLists(IList currentList, IList oldList, ChangeNode parentNode, string propertyName) { var changeList = new List <ChangeNode>(); if (currentList == null && oldList == null) { return(changeList); } var objectMapCurrent = new Dictionary <string, object>(); var objectMapOld = new Dictionary <string, object>(); if (currentList != null) { for (var i = 0; i < currentList.Count; i++) { objectMapCurrent.Add(currentList[i].ComputeHash(), currentList[i]); } } if (oldList != null) { for (var i = 0; i < oldList.Count; i++) { objectMapOld.Add(oldList[i].ComputeHash(), oldList[i]); } } var modifications = objectMapCurrent.Where(k => objectMapOld.ContainsKey(k.Key)); var additions = objectMapCurrent.Where(k => !objectMapOld.ContainsKey(k.Key)); var deletions = objectMapOld.Where(k => !objectMapCurrent.ContainsKey(k.Key)); foreach (var pair in modifications) { var changes = CompareObjects(pair.Value, objectMapOld[pair.Key]); var changeNode = new ChangeNode { PropertyName = propertyName, Value = pair.Value, Parent = parentNode, Index = changeList.Count }; if (changes.Count > 0) { changeNode.ObjectState = ObjectState.Dirty; changes.Parent = changeNode; changeNode.Nodes.AddRange(changes.Nodes); } changeList.Add(changeNode); } foreach (var pair in additions) { var changes = CompareObjects(pair.Value, null); var changeNode = new ChangeNode { ObjectState = ObjectState.New, PropertyName = propertyName, Value = pair.Value, Parent = parentNode, Index = changeList.Count }; if (changes.Count > 0) { changes.Parent = changeNode; changeNode.Nodes.AddRange(changes.Nodes); } changeList.Add(changeNode); } foreach (var pair in deletions) { var changes = CompareObjects(null, pair.Value); var changeNode = new ChangeNode { ObjectState = ObjectState.Deleted, PropertyName = propertyName, Value = pair.Value, Parent = parentNode, Index = changeList.Count }; if (changes.Count > 0) { changes.Parent = changeNode; changeNode.Nodes.AddRange(changes.Nodes); } changeList.Add(changeNode); } if (changeList.Count > 0) { parentNode.Nodes.AddRange(changeList); } return(changeList); }
private static ChangeNode CompareObjects(object currentObject, object oldObject, ChangeNode parentNode = null) { if (currentObject == null && oldObject == null) { throw new ArgumentException("currentObject and oldObject cannot be null at the same time"); } var rootNode = new ChangeNode { Value = currentObject }; if (parentNode != null) { rootNode.Parent = parentNode; } var context = ObjectScope.Current; if (context == null) { return(null); } // Get properties and build a property map var type = (currentObject ?? oldObject).GetType(); var primaryKey = (currentObject ?? oldObject).GetPrimaryKey(); var propertyMap = Reflector.GetPropertyMap(type); foreach (var property in propertyMap.Values) { object currentValue = null; if (currentObject != null && currentObject.PropertyExists(property.PropertyName)) { currentValue = currentObject.Property(property.PropertyName); } object oldValue = null; if (oldObject != null && oldObject.PropertyExists(property.PropertyName)) { oldValue = oldObject.Property(property.PropertyName); } if (currentValue == null && oldValue == null) { continue; } var changeNode = new ChangeNode { ObjectState = ObjectState.Clean }; var objectType = property.PropertyType; changeNode.Type = objectType; changeNode.Property = property; if (!context.IsNew && rootNode.ObjectState != ObjectState.DirtyPrimaryKey && (property.IsSimpleType || property.IsSimpleList || property.IsBinary)) { if (property.IsSimpleList) { currentValue = ((IEnumerable)currentValue)?.Cast <object>().ToArray(); oldValue = ((IEnumerable)oldValue)?.Cast <object>().ToArray(); } if (currentObject != null && oldObject != null) { var same = property.IsSimpleList ? ((IEnumerable <object>)currentValue).SequenceEqual((IEnumerable <object>)oldValue) : property.IsBinary ? ((byte[])currentValue).SequenceEqual((byte[])oldValue) : Equals(currentValue, oldValue); if (!same) { changeNode.Value = currentValue; changeNode.ObjectState = ObjectState.Dirty; } } else if (currentObject != null && oldObject == null) { changeNode.Value = currentValue; changeNode.ObjectState = ObjectState.New; } } /*else if (!context.IsNew && Reflector.IsXmlDocument(objectType)) * { * if (currentObject != null && oldObject != null && * ((currentValue != null && oldValue != null && ((XmlDocument)currentValue).OuterXml.GetHashCode() != ((XmlDocument)oldValue).OuterXml.GetHashCode()) || (currentValue != null && oldValue == null) || (currentValue == null && oldValue != null))) || { || changeNode.Value = currentValue; || changeNode.ObjectState = ObjectState.Dirty; || } ||}*/ else if (property.IsDataEntityList) { var changes = CompareLists((IList)currentValue, (IList)oldValue, changeNode, property.PropertyName); if (changes.Count > 0) { changeNode.Value = currentValue ?? oldValue; } rootNode.ListProperties.Add(property.PropertyName); } else if (property.IsDataEntity) { var changes = CompareObjects(currentValue, oldValue, parentNode); if (changes.Count > 0) { changeNode.Value = currentValue ?? oldValue; changes.Nodes.ForEach(n => changeNode.Nodes.Add(n)); } rootNode.ObjectProperties.Add(property.PropertyName); } if (!changeNode.IsEmpty) { changeNode.PropertyName = property.PropertyName; if (changeNode.ObjectState == ObjectState.Clean) { if (context.IsNew) { changeNode.ObjectState = ObjectState.New; } else if (currentValue != null && oldValue != null) { changeNode.ObjectState = ObjectState.Dirty; } else if (currentValue != null) { changeNode.ObjectState = ObjectState.New; } else if (oldValue != null) { changeNode.ObjectState = ObjectState.Deleted; } } // Primary key modifications should translate into insert and delete if (changeNode.ObjectState == ObjectState.Dirty && primaryKey.ContainsKey(property.PropertyName)) { rootNode.Nodes.RemoveAll(n => n.ObjectState == ObjectState.Dirty); var insertNode = new ChangeNode { Parent = rootNode, Value = currentObject, ObjectState = ObjectState.New }; var deleteNode = new ChangeNode { Parent = rootNode, Value = oldObject, ObjectState = ObjectState.Deleted }; rootNode.ObjectState = ObjectState.DirtyPrimaryKey; rootNode.Nodes.Add(insertNode); rootNode.Nodes.Add(deleteNode); } else { changeNode.Parent = rootNode; rootNode.Nodes.Add(changeNode); } } } // Root node can never be deleted! if (context.IsNew) { rootNode.ObjectState = ObjectState.New; } else if (!rootNode.IsRoot) { if (rootNode.ObjectState != ObjectState.Dirty) { rootNode.ObjectState = ObjectState.Clean; } } else if (currentObject != null && oldObject == null) { rootNode.ObjectState = ObjectState.New; } return(rootNode); }