private CodeTypeDeclaration CreateTrackingClass(
IMap map,
CodeGeneratorConfig codeGeneratorConfig) {
var trackingClass =
new CodeTypeDeclaration(map.Type.Name + codeGeneratorConfig.TrackedClassSuffix);
trackingClass.IsClass = true;
trackingClass.TypeAttributes = TypeAttributes.Public;
trackingClass.BaseTypes.Add(
map.Type.Name + codeGeneratorConfig.ForeignKeyAccessClassSuffix);
trackingClass.BaseTypes.Add(typeof(ITrackedEntity));
// add in change tracking properties
this.GenerateGetSetProperty(trackingClass, "IsTracking", typeof(bool), FinalPublic);
this.GenerateGetSetProperty(
trackingClass,
"DirtyProperties",
typeof(ISet<>).MakeGenericType(typeof(string)),
FinalPublic);
this.GenerateGetSetProperty(
trackingClass,
"OldValues",
typeof(IDictionary<,>).MakeGenericType(typeof(string), typeof(object)),
FinalPublic);
this.GenerateGetSetProperty(
trackingClass,
"NewValues",
typeof(IDictionary<,>).MakeGenericType(typeof(string), typeof(object)),
FinalPublic);
this.GenerateGetSetProperty(
trackingClass,
"AddedEntities",
typeof(IDictionary<,>).MakeGenericType(
typeof(string),
typeof(IList<>).MakeGenericType(typeof(object))),
FinalPublic);
this.GenerateGetSetProperty(
trackingClass,
"DeletedEntities",
typeof(IDictionary<,>).MakeGenericType(
typeof(string),
typeof(IList<>).MakeGenericType(typeof(object))),
FinalPublic);
// add in a constructor to initialise collections
var constructor = new CodeConstructor();
constructor.Attributes = MemberAttributes.Public;
constructor.Statements.Add(
new CodeAssignStatement(
CodeHelpers.ThisField("DirtyProperties"),
new CodeObjectCreateExpression(
typeof(HashSet<>).MakeGenericType(typeof(string)))));
constructor.Statements.Add(
new CodeAssignStatement(
CodeHelpers.ThisField("OldValues"),
new CodeObjectCreateExpression(
typeof(Dictionary<,>).MakeGenericType(typeof(string), typeof(object)))));
constructor.Statements.Add(
new CodeAssignStatement(
CodeHelpers.ThisField("NewValues"),
new CodeObjectCreateExpression(
typeof(Dictionary<,>).MakeGenericType(typeof(string), typeof(object)))));
constructor.Statements.Add(
new CodeAssignStatement(
CodeHelpers.ThisField("AddedEntities"),
new CodeObjectCreateExpression(
typeof(Dictionary<,>).MakeGenericType(
typeof(string),
typeof(IList<>).MakeGenericType(typeof(object))))));
constructor.Statements.Add(
new CodeAssignStatement(
CodeHelpers.ThisField("DeletedEntities"),
new CodeObjectCreateExpression(
typeof(Dictionary<,>).MakeGenericType(
typeof(string),
typeof(IList<>).MakeGenericType(typeof(object))))));
// these constructor statements override the collection properties to use observable collections
foreach (var collectionColumn in map.Columns.Where(c => c.Value.Type.IsCollection())) {
if (
!collectionColumn.Value.Map.Type.GetProperty(collectionColumn.Value.Name)
.GetGetMethod()
.IsVirtual) {
// TODO: send a warning back to the programmer, did they mean to do this?
continue;
}
constructor.Statements.Add(
new CodeConditionStatement(
new CodeBinaryOperatorExpression(
new CodeFieldReferenceExpression(
new CodeThisReferenceExpression(),
collectionColumn.Key),
CodeBinaryOperatorType.IdentityEquality,
new CodePrimitiveExpression(null)),
new CodeStatement[] {
new CodeAssignStatement(
new CodePropertyReferenceExpression(
new CodeThisReferenceExpression(),
collectionColumn.Key),
new CodeObjectCreateExpression(
"Dashing.CodeGeneration.TrackingCollection<" + trackingClass.Name
+ "," + collectionColumn.Value.Type.GenericTypeArguments.First()
+ ">",
new CodeThisReferenceExpression(),
new CodePrimitiveExpression(collectionColumn.Key)))
},
new CodeStatement[] {
new CodeAssignStatement(
new CodePropertyReferenceExpression(
new CodeThisReferenceExpression(),
collectionColumn.Key),
new CodeObjectCreateExpression(
"Dashing.CodeGeneration.TrackingCollection<" + trackingClass.Name
+ "," + collectionColumn.Value.Type.GenericTypeArguments.First()
+ ">",
new CodeThisReferenceExpression(),
new CodePrimitiveExpression(collectionColumn.Key),
new CodePropertyReferenceExpression(
new CodeThisReferenceExpression(),
collectionColumn.Key)))
}));
}
// override value type properties to perform dirty checking
foreach (
var valueTypeColumn in
map.Columns.Where(c => !c.Value.Type.IsCollection() && !c.Value.IsIgnored)) {
if (
!valueTypeColumn.Value.Map.Type.GetProperty(valueTypeColumn.Value.Name)
.GetGetMethod()
.IsVirtual) {
// TODO: send a warning back to the programmer, did they mean to do this?
continue;
}
var prop = this.GenerateGetSetProperty(
trackingClass,
valueTypeColumn.Key,
valueTypeColumn.Value.Type,
MemberAttributes.Public | MemberAttributes.Override,
true);
// override the setter
// if isTracking && !this.DirtyProperties.ContainsKey(prop) add to dirty props and add oldvalue
bool propertyCanBeNull = valueTypeColumn.Value.Type.IsNullable()
|| !valueTypeColumn.Value.Type.IsValueType;
var changeCheck = new CodeBinaryOperatorExpression();
if (!propertyCanBeNull) {
// can't be null so just check values
changeCheck.Left =
new CodeMethodInvokeExpression(
CodeHelpers.BaseProperty(valueTypeColumn.Key),
"Equals",
new CodePropertySetValueReferenceExpression());
changeCheck.Operator = CodeBinaryOperatorType.IdentityEquality;
changeCheck.Right = new CodePrimitiveExpression(false);
}
else {
// can be null, need to be careful of null reference exceptions
changeCheck.Left =
new CodeBinaryOperatorExpression(
CodeHelpers.BasePropertyIsNull(valueTypeColumn.Key),
CodeBinaryOperatorType.BooleanAnd,
new CodeBinaryOperatorExpression(
new CodePropertySetValueReferenceExpression(),
CodeBinaryOperatorType.IdentityInequality,
new CodePrimitiveExpression(null)));
changeCheck.Operator = CodeBinaryOperatorType.BooleanOr;
changeCheck.Right =
new CodeBinaryOperatorExpression(
CodeHelpers.BasePropertyIsNotNull(valueTypeColumn.Key),
CodeBinaryOperatorType.BooleanAnd,
new CodeBinaryOperatorExpression(
new CodeMethodInvokeExpression(
CodeHelpers.BaseProperty(valueTypeColumn.Key),
"Equals",
new CodePropertySetValueReferenceExpression()),
CodeBinaryOperatorType.IdentityEquality,
new CodePrimitiveExpression(false)));
}
prop.SetStatements.Insert(
0,
new CodeConditionStatement(
CodeHelpers.ThisPropertyIsTrue("IsTracking"),
new CodeStatement[] {
new CodeConditionStatement(
new CodeBinaryOperatorExpression(
new CodeBinaryOperatorExpression(
new CodeMethodInvokeExpression(
CodeHelpers.ThisProperty("DirtyProperties"),
"Contains",
new CodePrimitiveExpression(prop.Name)),
CodeBinaryOperatorType.IdentityEquality,
new CodePrimitiveExpression(false)),
CodeBinaryOperatorType.BooleanAnd,
changeCheck),
new CodeStatement[] {
new CodeExpressionStatement(
new CodeMethodInvokeExpression(
CodeHelpers.ThisProperty("DirtyProperties"),
"Add",
new CodePrimitiveExpression(prop.Name))),
new CodeAssignStatement(
new CodeIndexerExpression(
CodeHelpers.ThisProperty("OldValues"),
new CodePrimitiveExpression(prop.Name)),
CodeHelpers.BaseField(prop.Name))
}),
new CodeAssignStatement(
new CodeIndexerExpression(
CodeHelpers.ThisProperty("NewValues"),
new CodePrimitiveExpression(prop.Name)),
new CodePropertySetValueReferenceExpression())
}));
}
trackingClass.Members.Add(constructor);
return trackingClass;
}