private CodeMemberProperty GenerateGetSetProperty( CodeTypeDeclaration owningClass, string name, Type type, MemberAttributes attributes, bool useBaseProperty = false) { // generate the property var prop = new CodeMemberProperty(); prop.Name = name; prop.Type = new CodeTypeReference(type); prop.Attributes = attributes; if (useBaseProperty) { prop.GetStatements.Add( new CodeMethodReturnStatement(CodeHelpers.BaseProperty(name))); prop.SetStatements.Add( new CodeAssignStatement( CodeHelpers.BaseProperty(name), new CodePropertySetValueReferenceExpression())); } else { // generate the backing field for this property var backingField = new CodeMemberField(); backingField.Name = "backing" + name; backingField.Type = new CodeTypeReference(type); owningClass.Members.Add(backingField); prop.GetStatements.Add( new CodeMethodReturnStatement(CodeHelpers.ThisField(backingField.Name))); prop.SetStatements.Add( new CodeAssignStatement( CodeHelpers.ThisField(backingField.Name), new CodePropertySetValueReferenceExpression())); } owningClass.Members.Add(prop); return(prop); }
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); }