/// <summary> /// Find problems in the changes between two schemas by comparing this /// recorded schema to a given schema (usually the current schema) /// </summary> /// <param name="codeSchema">The current schema</param> /// <returns>List of potential problems arising from the differences between the schemas</returns> public List <ChangeProblem> FindProblems(ContentModelSchema codeSchema) { var changeProblems = new List <ChangeProblem>(); foreach (string typeName in this.ContentTypes.Except(codeSchema.ContentTypes)) { changeProblems.Add(new ChangeProblem(typeName, null, ChangeProblemType.DeletionNeeded)); } var otherTypes = new Dictionary <string, Type>(); foreach (string typeName in codeSchema.TypeProperties.Keys) { // Check its binary serializable Type type = ContentTypeHierarchy.GetAnyType(typeName); if (type == null) { type = ReflectionX.GetLoadedType(typeName); otherTypes.Add(typeName, type); } if (type != null && type.GetCustomAttribute <SerializableAttribute>() == null) { changeProblems.Add(new ChangeProblem(typeName, null, ChangeProblemType.NotBinarySerializable)); } } foreach (string typeName in codeSchema.ContentTypes) { // Check it doesn't initialise with a null value for an object or list property Type type = ContentTypeHierarchy.GetAnyType(typeName); if (type == null) { type = otherTypes[typeName]; } PropertyInfo nullProperty = new NoNullObjectCheck().Run(type); // Exception for UniqueId of Summary if (nullProperty != null && !(nullProperty.DeclaringType == typeof(Summary) && nullProperty.Name == "UniqueId")) { changeProblems.Add(new ChangeProblem(nullProperty.ReflectedType.FullName, nullProperty.Name, ChangeProblemType.NullObjectValue)); } } // Types existing in both code and data schemas foreach (string typeName in codeSchema.TypeProperties.Keys.Intersect(TypeProperties.Keys)) { bool isSummary = ContentTypeHierarchy.GetSummaryType(typeName) != null; if (isSummary && !this.SummaryMap.ContainsKey(typeName)) { log.Warn("Unused summary type: " + typeName); continue; } foreach (string deletedProp in TypeProperties[typeName].Keys.Except(codeSchema.TypeProperties[typeName].Keys)) { if (isSummary) { // Test for when a property is dropped from the summary which was mapped to the content type // In this case the property data needs copied from the Summary db field to the Content one if (this.SummaryMap[typeName].ContainsValue(deletedProp) && this.SummaryTypes.ContainsValue(typeName)) { var oldContentProp = this.SummaryMap[typeName].Single(kvp => kvp.Value == deletedProp).Key; bool anyContentTypeHadProperty = this.SummaryTypes .Where(kvp => kvp.Value == typeName) .Any(kvp => TypeProperties[kvp.Key].ContainsKey(oldContentProp)); if (anyContentTypeHadProperty) { changeProblems.Add(new ChangeProblem(typeName, deletedProp, ChangeProblemType.PropertyDroppedFromSummary)); } } } else { changeProblems.Add(new ChangeProblem(typeName, deletedProp, ChangeProblemType.PropertyDropped)); } } foreach (string addedProp in codeSchema.TypeProperties[typeName].Keys.Except(TypeProperties[typeName].Keys)) { if (isSummary) { // Test for when a property is added to the summary which is mapped from the content type // In this case the data needs copied from the Content db field to the Summary if (codeSchema.SummaryMap[typeName].ContainsValue(addedProp) && codeSchema.SummaryTypes.ContainsValue(typeName)) { var newContentProp = codeSchema.SummaryMap[typeName].Single(kvp => kvp.Value == addedProp).Key; var anyContentTypeContainedProp = codeSchema.SummaryTypes .Where(kvp => kvp.Value == typeName) .Any(kvp => TypeProperties[kvp.Key].ContainsKey(newContentProp)); if (anyContentTypeContainedProp) { changeProblems.Add(new ChangeProblem(typeName, addedProp, ChangeProblemType.PropertyAddedToSummary)); } } } } } return(changeProblems); }