void WriteConstructorComments(ModelClass modelClass) { foreach (ModelAttribute requiredAttribute in modelClass.AllRequiredAttributes.Where(x => (!x.IsIdentity || x.IdentityType == IdentityType.Manual) && !x.IsConcurrencyToken && x.SetterVisibility == SetterAccessModifier.Public)) Output($@"/// <param name=""{requiredAttribute.Name.ToLower()}"">{SecurityElement.Escape(requiredAttribute.Summary)}</param>"); // TODO: Add comment if available foreach (NavigationProperty requiredNavigationProperty in modelClass.AllRequiredNavigationProperties() .Where(np => np.AssociationObject.SourceMultiplicity != Sawczyn.EFDesigner.EFModel.Multiplicity.One || np.AssociationObject.TargetMultiplicity != Sawczyn.EFDesigner.EFModel.Multiplicity.One)) Output($@"/// <param name=""{requiredNavigationProperty.PropertyName.ToLower()}""></param>"); }
List<string> GetRequiredParameters(ModelClass modelClass, bool? haveDefaults, bool? publicOnly = null) { List<string> requiredParameters = new List<string>(); if (haveDefaults != true) { requiredParameters.AddRange(modelClass.AllRequiredAttributes .Where(x => (!x.IsIdentity || x.IdentityType == IdentityType.Manual) && !x.IsConcurrencyToken && (x.SetterVisibility == SetterAccessModifier.Public || publicOnly != false) && string.IsNullOrEmpty(x.InitialValue)) .Select(x => $"{x.FQPrimitiveType} {x.Name.ToLower()}")); // don't use 1..1 associations in constructor parameters. Becomes a Catch-22 scenario. requiredParameters.AddRange(modelClass.AllRequiredNavigationProperties() .Where(np => np.AssociationObject.SourceMultiplicity != Sawczyn.EFDesigner.EFModel.Multiplicity.One || np.AssociationObject.TargetMultiplicity != Sawczyn.EFDesigner.EFModel.Multiplicity.One) .Select(x => $"{x.ClassType.FullName} {x.PropertyName.ToLower()}")); } if (haveDefaults != false) { requiredParameters.AddRange(modelClass.AllRequiredAttributes .Where(x => (!x.IsIdentity || x.IdentityType == IdentityType.Manual) && !x.IsConcurrencyToken && (x.SetterVisibility == SetterAccessModifier.Public || publicOnly != false) && !string.IsNullOrEmpty(x.InitialValue)) .Select(x => { string quote = x.PrimitiveType == "string" ? "\"" : x.PrimitiveType == "char" ? "'" : string.Empty; string value = FullyQualified(modelClass.ModelRoot, quote.Length > 0 ? x.InitialValue.Trim(quote[0]) : x.InitialValue); return $"{x.FQPrimitiveType} {x.Name.ToLower()} = {quote}{value}{quote}"; })); } return requiredParameters; }
List<string> GetRequiredParameterNames(ModelClass modelClass, bool? publicOnly = null) { List<string> requiredParameterNames = modelClass.AllRequiredAttributes .Where(x => (!x.IsIdentity || x.IdentityType == IdentityType.Manual) && !x.IsConcurrencyToken && (x.SetterVisibility == SetterAccessModifier.Public || publicOnly != false) && string.IsNullOrEmpty(x.InitialValue)) .Select(x => x.Name.ToLower()) .ToList(); requiredParameterNames.AddRange(modelClass.AllRequiredNavigationProperties() .Where(np => np.AssociationObject.SourceMultiplicity != Sawczyn.EFDesigner.EFModel.Multiplicity.One || np.AssociationObject.TargetMultiplicity != Sawczyn.EFDesigner.EFModel.Multiplicity.One) .Select(x => x.PropertyName.ToLower())); requiredParameterNames.AddRange(modelClass.AllRequiredAttributes .Where(x => (!x.IsIdentity || x.IdentityType == IdentityType.Manual) && !x.IsConcurrencyToken && (x.SetterVisibility == SetterAccessModifier.Public || publicOnly != false) && !string.IsNullOrEmpty(x.InitialValue)) .Select(x => x.Name.ToLower())); return requiredParameterNames; }
void WriteConstructor(ModelClass modelClass) { Output("partial void Init();"); NL(); /***********************************************************************/ // Default constructor /***********************************************************************/ bool hasRequiredParameters = GetRequiredParameters(modelClass, false).Any(); bool hasOneToOneAssociations = modelClass.AllRequiredNavigationProperties() .Any(np => np.AssociationObject.SourceMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.One && np.AssociationObject.TargetMultiplicity != Sawczyn.EFDesigner.EFModel.Multiplicity.One); string visibility = (hasRequiredParameters || modelClass.IsAbstract) && !modelClass.IsDependentType ? "protected" : "public"; if (visibility == "public") { Output("/// <summary>"); Output("/// Default constructor"); Output("/// </summary>"); } else if (modelClass.IsAbstract) { Output("/// <summary>"); Output("/// Default constructor. Protected due to being abstract."); Output("/// </summary>"); } else if (hasRequiredParameters) { Output("/// <summary>"); Output("/// Default constructor. Protected due to required properties, but present because EF needs it."); Output("/// </summary>"); } List<string> remarks = new List<string>(); if (hasOneToOneAssociations) { List<Association> oneToOneAssociations = modelClass.AllRequiredNavigationProperties() .Where(np => np.AssociationObject.SourceMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.One && np.AssociationObject.TargetMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.One) .Select(np => np.AssociationObject) .ToList(); List<ModelClass> otherEndsOneToOne = oneToOneAssociations.Where(a => a.Source != modelClass).Select(a => a.Target) .Union(oneToOneAssociations.Where(a => a.Target != modelClass).Select(a => a.Source)) .ToList(); if (oneToOneAssociations.Any(a => a.Source.Name == modelClass.Name && a.Target.Name == modelClass.Name)) otherEndsOneToOne.Add(modelClass); if (otherEndsOneToOne.Any()) { string nameList = otherEndsOneToOne.Count == 1 ? otherEndsOneToOne.First().Name : string.Join(", ", otherEndsOneToOne.Take(otherEndsOneToOne.Count - 1).Select(c => c.Name)) + " and " + (otherEndsOneToOne.Last().Name != modelClass.Name ? otherEndsOneToOne.Last().Name : "itself"); remarks.Add($"// NOTE: This class has one-to-one associations with {nameList}."); remarks.Add("// One-to-one associations are not validated in constructors since this causes a scenario where each one must be constructed before the other."); } } Output(modelClass.Superclass != null ? $"{visibility} {modelClass.Name}(): base()" : $"{visibility} {modelClass.Name}()"); Output("{"); if (remarks.Count > 0) { foreach (string remark in remarks) Output(remark); NL(); } WriteDefaultConstructorBody(modelClass); Output("}"); NL(); if (visibility != "public" && !modelClass.IsAbstract) { Output("/// <summary>"); Output("/// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving."); Output("/// </summary>"); Output($"public static {modelClass.Name} Create{modelClass.Name}Unsafe()"); Output("{"); Output($"return new {modelClass.Name}();"); Output("}"); NL(); } /***********************************************************************/ // Constructor with required parameters (if necessary) /***********************************************************************/ if (hasRequiredParameters) { visibility = modelClass.IsAbstract ? "protected" : "public"; Output("/// <summary>"); Output("/// Public constructor with required data"); Output("/// </summary>"); WriteConstructorComments(modelClass); Output($"{visibility} {modelClass.Name}({string.Join(", ", GetRequiredParameters(modelClass, null))})"); Output("{"); if (remarks.Count > 0) { foreach (string remark in remarks) Output(remark); NL(); } foreach (ModelAttribute requiredAttribute in modelClass.AllRequiredAttributes .Where(x => (!x.IsIdentity || x.IdentityType == IdentityType.Manual) && !x.IsConcurrencyToken && x.SetterVisibility == SetterAccessModifier.Public)) { if (requiredAttribute.Type == "String") Output($"if (string.IsNullOrEmpty({requiredAttribute.Name.ToLower()})) throw new ArgumentNullException(nameof({requiredAttribute.Name.ToLower()}));"); else if (requiredAttribute.Type.StartsWith("Geo")) Output($"if ({requiredAttribute.Name.ToLower()} == null) throw new ArgumentNullException(nameof({requiredAttribute.Name.ToLower()}));"); Output($"this.{requiredAttribute.Name} = {requiredAttribute.Name.ToLower()};"); NL(); } foreach (ModelAttribute modelAttribute in modelClass.Attributes.Where(x => x.SetterVisibility == SetterAccessModifier.Public && !x.Required && !string.IsNullOrEmpty(x.InitialValue) && x.InitialValue != "null")) { string quote = modelAttribute.Type == "String" ? "\"" : modelAttribute.Type == "Char" ? "'" : string.Empty; Output(quote.Length > 0 ? $"this.{modelAttribute.Name} = {quote}{FullyQualified(modelClass.ModelRoot, modelAttribute.InitialValue.Trim(quote[0]))}{quote};" : $"this.{modelAttribute.Name} = {quote}{FullyQualified(modelClass.ModelRoot, modelAttribute.InitialValue)}{quote};"); } foreach (NavigationProperty requiredNavigationProperty in modelClass.AllRequiredNavigationProperties() .Where(np => np.AssociationObject.SourceMultiplicity != Sawczyn.EFDesigner.EFModel.Multiplicity.One || np.AssociationObject.TargetMultiplicity != Sawczyn.EFDesigner.EFModel.Multiplicity.One)) { string parameterName = requiredNavigationProperty.PropertyName.ToLower(); Output($"if ({parameterName} == null) throw new ArgumentNullException(nameof({parameterName}));"); if (requiredNavigationProperty.IsCollection) Output($"{requiredNavigationProperty.PropertyName}.Add({parameterName});"); else if (requiredNavigationProperty.ConstructorParameterOnly) { UnidirectionalAssociation association = requiredNavigationProperty.AssociationObject as UnidirectionalAssociation; Output(association.TargetMultiplicity == Sawczyn.EFDesigner.EFModel.Multiplicity.ZeroMany ? $"{requiredNavigationProperty.PropertyName}.{association.TargetPropertyName}.Add(this);" : $"{requiredNavigationProperty.PropertyName}.{association.TargetPropertyName} = this;"); } else Output($"this.{requiredNavigationProperty.PropertyName} = {parameterName};"); NL(); } foreach (NavigationProperty navigationProperty in modelClass.LocalNavigationProperties() .Where(x => x.AssociationObject.Persistent && (x.IsCollection || x.ClassType.IsDependentType) && !x.ConstructorParameterOnly)) { if (!navigationProperty.IsCollection) Output($"this.{navigationProperty.PropertyName} = new {navigationProperty.ClassType.FullName}();"); else { string collectionType = GetFullContainerName(navigationProperty.AssociationObject.CollectionClass, navigationProperty.ClassType.FullName); Output($"this.{navigationProperty.PropertyName} = new {collectionType}();"); } } NL(); Output("Init();"); Output("}"); NL(); if (!modelClass.IsAbstract) { Output("/// <summary>"); Output("/// Static create function (for use in LINQ queries, etc.)"); Output("/// </summary>"); WriteConstructorComments(modelClass); string newToken = string.Empty; List<string> requiredParameters = GetRequiredParameters(modelClass, null); if (!AllSuperclassesAreNullOrAbstract(modelClass)) { List<string> superclassRequiredParameters = GetRequiredParameters(modelClass.Superclass, null); if (!requiredParameters.Except(superclassRequiredParameters).Any()) newToken = "new "; } Output($"public static {newToken}{modelClass.Name} Create({string.Join(", ", GetRequiredParameters(modelClass, null))})"); Output("{"); Output($"return new {modelClass.Name}({string.Join(", ", GetRequiredParameterNames(modelClass))});"); Output("}"); NL(); } } }