public override void ElementPropertyChanged(ElementPropertyChangedEventArgs e) { base.ElementPropertyChanged(e); ModelClass element = (ModelClass)e.ModelElement; if (element.IsDeleted) { return; } Store store = element.Store; Transaction current = store.TransactionManager.CurrentTransaction; if (current.IsSerializing || ModelRoot.BatchUpdating) { return; } if (Equals(e.NewValue, e.OldValue)) { return; } List <string> errorMessages = EFCoreValidator.GetErrors(element).ToList(); switch (e.DomainProperty.Name) { case "DbSetName": { string newDbSetName = (string)e.NewValue; if (element.IsDependentType) { if (!string.IsNullOrEmpty(newDbSetName)) { element.DbSetName = string.Empty; } } else { if (string.IsNullOrEmpty(newDbSetName)) { element.DbSetName = MakeDefaultName(element.Name); } if (current.Name.ToLowerInvariant() != "paste" && (string.IsNullOrWhiteSpace(newDbSetName) || !CodeGenerator.IsValidLanguageIndependentIdentifier(newDbSetName))) { errorMessages.Add($"DbSet name '{newDbSetName}' isn't a valid .NET identifier."); } else if (store.GetAll <ModelClass>() .Except(new[] { element }) .Any(x => x.DbSetName == newDbSetName)) { errorMessages.Add($"DbSet name '{newDbSetName}' already in use"); } } break; } case "ImplementNotify": { bool newImplementNotify = (bool)e.NewValue; if (newImplementNotify) { List <string> nameList = element.Attributes.Where(x => x.AutoProperty).Select(x => x.Name).ToList(); if (nameList.Any()) { string names = nameList.Count > 1 ? string.Join(", ", nameList.Take(nameList.Count - 1)) + " and " + nameList.Last() : nameList.First(); string verb = nameList.Count > 1 ? "is an autoproperty" : "are autoproperties"; WarningDisplay.Show($"{names} {verb}, so will not participate in INotifyPropertyChanged messages"); } } PresentationHelper.UpdateClassDisplay(element); break; } case "IsAbstract": { bool newIsAbstract = (bool)e.NewValue; if (newIsAbstract && element.IsDependentType) { errorMessages.Add($"Can't make {element.Name} abstract since it's a dependent type"); break; } PresentationHelper.UpdateClassDisplay(element); break; } case "IsDependentType": { bool newIsDependentType = (bool)e.NewValue; if (newIsDependentType) { if (element.IsAbstract) { errorMessages.Add($"Can't make {element.Name} a dependent class since it's abstract"); break; } // dependent type can't be source in an association if (store.GetAll <UnidirectionalAssociation>() .Any(a => a.Source == element)) { errorMessages.Add($"Can't make {element.Name} a dependent class since it references other classes"); break; } if (store.GetAll <BidirectionalAssociation>() .Any(a => a.Source == element || a.Target == element)) { errorMessages.Add($"Can't make {element.Name} a dependent class since it's in a bidirectional association"); break; } if (store.GetAll <Association>() .Any(a => a.Target == element && a.TargetMultiplicity == Multiplicity.ZeroMany)) { errorMessages.Add($"Can't make {element.Name} a dependent class since it's the target of a 0..* association"); break; } foreach (ModelAttribute modelAttribute in element.AllAttributes.Where(a => a.IsIdentity)) { modelAttribute.IsIdentity = false; } foreach (UnidirectionalAssociation association in Association.GetLinksToTargets(element).OfType <UnidirectionalAssociation>()) { if (association.SourceMultiplicity == Multiplicity.ZeroMany) { association.SourceMultiplicity = Multiplicity.ZeroOne; } if (association.TargetMultiplicity == Multiplicity.ZeroMany) { association.TargetMultiplicity = Multiplicity.ZeroOne; } association.TargetRole = EndpointRole.Dependent; } element.TableName = string.Empty; element.DbSetName = string.Empty; } else { element.DbSetName = MakeDefaultName(element.Name); element.TableName = MakeDefaultName(element.Name); } PresentationHelper.UpdateClassDisplay(element); break; } case "Name": { string newName = (string)e.NewValue; if (current.Name.ToLowerInvariant() != "paste" && (string.IsNullOrWhiteSpace(newName) || !CodeGenerator.IsValidLanguageIndependentIdentifier(newName))) { errorMessages.Add($"Class name '{newName}' isn't a valid .NET identifier."); } else if (store.ElementDirectory .AllElements .OfType <ModelClass>() .Except(new[] { element }) .Any(x => x.Name == newName)) { errorMessages.Add($"Class name '{newName}' already in use by another class"); } else if (store.ElementDirectory .AllElements .OfType <ModelEnum>() .Any(x => x.Name == newName)) { errorMessages.Add($"Class name '{newName}' already in use by an enum"); } else if (!string.IsNullOrEmpty((string)e.OldValue)) { string oldDefaultName = MakeDefaultName((string)e.OldValue); string newDefaultName = MakeDefaultName(newName); if (element.DbSetName == oldDefaultName) { element.DbSetName = newDefaultName; } if (element.TableName == oldDefaultName) { element.TableName = newDefaultName; } } break; } case "Namespace": { string newNamespace = (string)e.NewValue; if (current.Name.ToLowerInvariant() != "paste") { errorMessages.Add(CommonRules.ValidateNamespace(newNamespace, CodeGenerator.IsValidLanguageIndependentIdentifier)); } break; } case "TableName": { string newTableName = (string)e.NewValue; if (element.IsDependentType) { if (!string.IsNullOrEmpty(newTableName)) { element.TableName = string.Empty; } } else { if (string.IsNullOrEmpty(newTableName)) { element.TableName = MakeDefaultName(element.Name); } if (store.GetAll <ModelClass>() .Except(new[] { element }) .Any(x => x.TableName == newTableName)) { errorMessages.Add($"Table name '{newTableName}' already in use"); } } break; } } errorMessages = errorMessages.Where(m => m != null).ToList(); if (errorMessages.Any()) { current.Rollback(); ErrorDisplay.Show(store, string.Join("\n", errorMessages)); } }
public override void ElementPropertyChanged(ElementPropertyChangedEventArgs e) { base.ElementPropertyChanged(e); ModelRoot element = (ModelRoot)e.ModelElement; Store store = element.Store; Transaction current = store.TransactionManager.CurrentTransaction; if (current.IsSerializing) { return; } if (Equals(e.NewValue, e.OldValue)) { return; } List <string> errorMessages = EFCoreValidator.GetErrors(element).ToList(); bool redraw = false; switch (e.DomainProperty.Name) { case "ConnectionString": if (e.NewValue != null) { element.ConnectionStringName = null; } break; case "ConnectionStringName": if (e.NewValue != null) { element.ConnectionString = null; } break; case "DatabaseSchema": if (string.IsNullOrEmpty((string)e.NewValue)) { element.DatabaseSchema = "dbo"; } break; case "EntityFrameworkVersion": element.EntityFrameworkPackageVersion = "Latest"; if (element.EntityFrameworkVersion == EFVersion.EFCore) { element.InheritanceStrategy = CodeStrategy.TablePerHierarchy; } break; case "EnumOutputDirectory": if (string.IsNullOrEmpty((string)e.NewValue) && !string.IsNullOrEmpty(element.EntityOutputDirectory)) { element.EnumOutputDirectory = element.EntityOutputDirectory; } break; case "StructOutputDirectory": if (string.IsNullOrEmpty((string)e.NewValue) && !string.IsNullOrEmpty(element.EntityOutputDirectory)) { element.StructOutputDirectory = element.EntityOutputDirectory; } break; case "EntityOutputDirectory": if (string.IsNullOrEmpty(element.EnumOutputDirectory) || element.EnumOutputDirectory == (string)e.OldValue) { element.EnumOutputDirectory = (string)e.NewValue; } if (string.IsNullOrEmpty(element.StructOutputDirectory) || element.StructOutputDirectory == (string)e.OldValue) { element.StructOutputDirectory = (string)e.NewValue; } break; case "FileNameMarker": string newFileNameMarker = (string)e.NewValue; if (!Regex.Match($"a.{newFileNameMarker}.cs", @"^(?!^(PRN|AUX|CLOCK\$|NUL|CON|COM\d|LPT\d|\..*)(\..+)?$)[^\x00-\x1f\\?*:\"";|/]+$") .Success) { errorMessages.Add("Invalid value to make part of file name"); } break; case "InheritanceStrategy": if ((element.EntityFrameworkVersion == EFVersion.EFCore) && (element.NuGetPackageVersion.MajorMinorVersionNum < 2.1)) { element.InheritanceStrategy = CodeStrategy.TablePerHierarchy; } break; case "LayoutAlgorithm": ModelDisplay.LayoutDiagram(element.Classes.FirstOrDefault()?.GetActiveDiagram() as EFModelDiagram); break; case "Namespace": errorMessages.Add(CommonRules.ValidateNamespace((string)e.NewValue, CodeGenerator.IsValidLanguageIndependentIdentifier)); break; case "ShowCascadeDeletes": // need these change rules to fire even though nothing in Association has changed // so we need to set this early -- requires guarding against recursion. bool newShowCascadeDeletes = (bool)e.NewValue; if (element.ShowCascadeDeletes != newShowCascadeDeletes) { element.ShowCascadeDeletes = newShowCascadeDeletes; foreach (Association association in store.ElementDirectory.FindElements <Association>()) { AssociationChangeRules.UpdateDisplayForCascadeDelete(association); } } redraw = true; break; case "ShowWarningsInDesigner": redraw = true; break; case "WarnOnMissingDocumentation": if (element.ShowWarningsInDesigner) { redraw = true; } ModelRoot.ExecuteValidator?.Invoke(); break; } errorMessages = errorMessages.Where(m => m != null).ToList(); if (errorMessages.Any()) { current.Rollback(); ErrorDisplay.Show(string.Join("\n", errorMessages)); } if (redraw) { element.InvalidateDiagrams(); } }
public override void ElementPropertyChanged(ElementPropertyChangedEventArgs e) { base.ElementPropertyChanged(e); ModelEnum element = (ModelEnum)e.ModelElement; if (element.IsDeleted) { return; } Store store = element.Store; Transaction currentTransaction = store.TransactionManager.CurrentTransaction; if (currentTransaction.IsSerializing) { return; } if (Equals(e.NewValue, e.OldValue)) { return; } string errorMessage = null; switch (e.DomainProperty.Name) { case "Name": if (currentTransaction.Name.ToLowerInvariant() == "paste") { return; } if (string.IsNullOrWhiteSpace(element.Name) || !CodeGenerator.IsValidLanguageIndependentIdentifier(element.Name)) { errorMessage = "Name must be a valid .NET identifier"; } else if (store.GetAll <ModelClass>().Any(x => x.FullName == element.FullName)) { errorMessage = "Enum name already in use by a class"; } else if (store.GetAll <ModelEnum>().Except(new[] { element }).Any(x => x.FullName == element.FullName)) { errorMessage = "Enum name already in use by another enum"; } else { // rename type names for ModelAttributes that reference this enum foreach (ModelAttribute modelAttribute in store.GetAll <ModelAttribute>().Where(a => a.Type == (string)e.OldValue)) { modelAttribute.Type = element.Name; if (!string.IsNullOrEmpty(modelAttribute.InitialValue)) { string[] parts = modelAttribute.InitialValue.Split('.'); parts[0] = (string)e.NewValue; modelAttribute.InitialValue = string.Join(".", parts); } } } break; case "Namespace": if (currentTransaction.Name.ToLowerInvariant() != "paste") { errorMessage = CommonRules.ValidateNamespace(element.Namespace, CodeGenerator.IsValidLanguageIndependentIdentifier); } break; case "IsFlags": element.SetFlagValues(); break; case "ValueType": EnumValueType newValueType = (EnumValueType)e.NewValue; List <ModelAttribute> modelAttributes = store.ElementDirectory .AllElements .OfType <ModelAttribute>() .Where(a => a.Type == element.Name && a.IsIdentity) .ToList(); if (modelAttributes.Any()) { string classList = string.Join(", ", modelAttributes.Select(a => a.ModelClass.Name + "." + a.Name)); errorMessage = $"Can't change {element.Name} value type to {newValueType}. It's not a valid identity type, and {element.Name} is used as an identity type in {classList}"; } break; } if (errorMessage != null) { currentTransaction.Rollback(); ErrorDisplay.Show(store, errorMessage); } }
public override void ElementPropertyChanged(ElementPropertyChangedEventArgs e) { base.ElementPropertyChanged(e); ModelClass element = (ModelClass)e.ModelElement; ModelRoot modelRoot = element.ModelRoot; if (element.IsDeleted) { return; } Store store = element.Store; Transaction current = store.TransactionManager.CurrentTransaction; if (current.IsSerializing || ModelRoot.BatchUpdating) { return; } if (Equals(e.NewValue, e.OldValue)) { return; } List <string> errorMessages = new List <string>(); switch (e.DomainProperty.Name) { case "BaseClass": { if (element.IsDependentType) { errorMessages.Add($"Can't give {element.Name} a base class since it's a dependent type"); } break; } case "CustomInterfaces": { if (modelRoot.ShowInterfaceIndicators) { PresentationHelper.UpdateClassDisplay(element); } break; } case "DbSetName": { string newDbSetName = (string)e.NewValue; if (element.IsDependentType) { if (!string.IsNullOrEmpty(newDbSetName)) { element.DbSetName = string.Empty; } } else { if (string.IsNullOrEmpty(newDbSetName)) { element.DbSetName = MakeDefaultTableAndSetName(element.Name); } if (current.Name.ToLowerInvariant() != "paste" && (string.IsNullOrWhiteSpace(newDbSetName) || !CodeGenerator.IsValidLanguageIndependentIdentifier(newDbSetName))) { errorMessages.Add($"DbSet name '{newDbSetName}' isn't a valid .NET identifier."); } else if (store.GetAll <ModelClass>() .Except(new[] { element }) .Any(x => x.DbSetName == newDbSetName)) { errorMessages.Add($"DbSet name '{newDbSetName}' already in use"); } } break; } case "ImplementNotify": { bool newImplementNotify = (bool)e.NewValue; if (newImplementNotify) { List <string> nameList = element.Attributes.Where(x => x.AutoProperty).Select(x => x.Name).ToList(); if (nameList.Any()) { string names = nameList.Count > 1 ? string.Join(", ", nameList.Take(nameList.Count - 1)) + " and " + nameList.Last() : nameList.First(); string verb = nameList.Count > 1 ? "is an autoproperty" : "are autoproperties"; WarningDisplay.Show($"{names} {verb}, so will not participate in INotifyPropertyChanged messages"); } } PresentationHelper.UpdateClassDisplay(element); break; } case "IsAbstract": { bool newIsAbstract = (bool)e.NewValue; if (newIsAbstract && element.IsDependentType) { errorMessages.Add($"Can't make {element.Name} abstract since it's a dependent type"); break; } PresentationHelper.UpdateClassDisplay(element); break; } case "IsDatabaseView": { bool newIsView = (bool)e.NewValue; if (newIsView) { if (element.IsDependentType) { errorMessages.Add($"Can't base {element.Name} off a view since it's a dependent type"); break; } if (element.IsQueryType) { errorMessages.Add($"Can't base {element.Name} off a view since it's a query type"); break; } if (string.IsNullOrEmpty(element.ViewName)) { element.ViewName = element.TableName; } if (string.IsNullOrEmpty(element.ViewName)) { element.ViewName = MakeDefaultTableAndSetName(element.Name); } if (modelRoot.IsEFCore5Plus) { VerifyKeylessTypeEFCore5(); } else { VerifyKeylessType(); } } break; } case "IsDependentType": { if (element.IsDependentType) { if (element.IsDatabaseView) { errorMessages.Add($"Can't make {element.Name} a dependent class since it's backed by a database view"); break; } if (element.IsQueryType) { errorMessages.Add($"Can't make {element.Name} a dependent class since it's a query type"); break; } if (element.BaseClass != null) { errorMessages.Add($"Can't make {element.Name} a dependent class since it has a base class"); break; } string subclasses = string.Join(", ", store.GetAll <Generalization>().Where(g => g.Superclass == element).Select(g => g.Subclass.Name)); if (!string.IsNullOrEmpty(subclasses)) { errorMessages.Add($"Can't make {element.Name} a dependent class since it has subclass(es) {subclasses}"); break; } if (element.IsAbstract) { errorMessages.Add($"Can't make {element.Name} a dependent class since it's abstract"); break; } List <Association> principalAssociations = store.GetAll <Association>().Where(a => a.Principal == element).ToList(); if (principalAssociations.Any()) { string badAssociations = string.Join(", ", principalAssociations.Select(a => a.GetDisplayText())); errorMessages.Add($"Can't make {element.Name} a dependent class since it's the principal end in: {badAssociations}"); break; } List <UnidirectionalAssociation> entityTargets = store.GetAll <UnidirectionalAssociation>().Where(a => a.Source == element && !a.Target.IsDependentType).ToList(); if (entityTargets.Any()) { string badAssociations = string.Join(", ", entityTargets.Select(a => a.GetDisplayText())); errorMessages.Add($"Can't make {element.Name} a dependent class since it has unidirectional associations to entities in: {badAssociations}"); break; } List <BidirectionalAssociation> bidirectionalAssociations = store.GetAll <BidirectionalAssociation>().Where(a => a.Source == element || a.Target == element).ToList(); if (bidirectionalAssociations.Any()) { if (!modelRoot.IsEFCore5Plus) { string badAssociations = string.Join(", ", entityTargets.Select(a => a.GetDisplayText())); errorMessages.Add($"Can't make {element.Name} a dependent class since it has bidirectional associations in: {badAssociations}"); break; } bidirectionalAssociations = bidirectionalAssociations.Where(a => (a.Source == element && a.TargetMultiplicity != Multiplicity.One) || (a.Target == element && a.SourceMultiplicity != Multiplicity.One)) .ToList(); if (bidirectionalAssociations.Any()) { string badAssociations = string.Join(", ", entityTargets.Select(a => a.GetDisplayText())); errorMessages.Add($"Can't make {element.Name} a dependent class since it has bidirectional associations without 1 or 0/1 ownership multiplicity in: {badAssociations}. The other end must be a single, required reference."); break; } } if (element.ModelRoot.EntityFrameworkVersion == EFVersion.EF6 || element.ModelRoot.GetEntityFrameworkPackageVersionNum() < 2.2) { if (store.GetAll <Association>().Any(a => a.Target == element && a.TargetMultiplicity == Multiplicity.ZeroMany)) { errorMessages.Add($"Can't make {element.Name} a dependent class since it's the target of a 0..* association"); break; } foreach (UnidirectionalAssociation association in Association.GetLinksToTargets(element).OfType <UnidirectionalAssociation>()) { if (association.SourceMultiplicity == Multiplicity.ZeroMany) { association.SourceMultiplicity = Multiplicity.ZeroOne; } if (association.TargetMultiplicity == Multiplicity.ZeroMany) { association.TargetMultiplicity = Multiplicity.ZeroOne; } association.TargetRole = EndpointRole.Dependent; } element.TableName = string.Empty; } foreach (ModelAttribute modelAttribute in element.AllAttributes.Where(a => a.IsIdentity)) { modelAttribute.IsIdentity = false; } element.DbSetName = string.Empty; } else { element.DbSetName = MakeDefaultTableAndSetName(element.Name); element.TableName = MakeDefaultTableAndSetName(element.Name); } // Remove any foreign keys in any incoming or outgoing associations foreach (Association association in Association.GetLinksToTargets(element).Union(Association.GetLinksToSources(element)).Distinct()) { association.FKPropertyName = null; } PresentationHelper.UpdateClassDisplay(element); break; } case "IsPropertyBag": { if (element.Superclass != null && !element.Superclass.IsPropertyBag) { element.Superclass.IsPropertyBag = true; } if (element.Subclasses.Any()) { foreach (ModelClass subclass in element.Subclasses) { subclass.IsPropertyBag = true; } } PresentationHelper.UpdateClassDisplay(element); break; } case "IsQueryType": { if ((bool)e.NewValue) { if (element.IsDependentType) { errorMessages.Add($"Can't make {element.Name} a query type since it's a dependent class"); break; } if (modelRoot.EntityFrameworkVersion == EFVersion.EF6) { element.IsQueryType = false; } else if (modelRoot.IsEFCore5Plus) { VerifyKeylessTypeEFCore5(); } else { VerifyKeylessType(); } } break; } case "Name": { if (current.Name.ToLowerInvariant() == "paste") { return; } if (string.IsNullOrWhiteSpace(element.Name) || !CodeGenerator.IsValidLanguageIndependentIdentifier(element.Name)) { errorMessages.Add("Name must be a valid .NET identifier"); } else if (store.GetAll <ModelClass>().Except(new[] { element }).Any(x => x.FullName == element.FullName)) { errorMessages.Add($"Class name '{element.FullName}' already in use by another class"); } else if (store.GetAll <ModelEnum>().Any(x => x.FullName == element.FullName)) { errorMessages.Add($"Class name '{element.FullName}' already in use by an enum"); } else if (!string.IsNullOrEmpty((string)e.OldValue)) { string oldDefaultName = MakeDefaultTableAndSetName((string)e.OldValue); string newDefaultName = MakeDefaultTableAndSetName(element.Name); if (element.DbSetName == oldDefaultName) { element.DbSetName = newDefaultName; } if (element.TableName == oldDefaultName) { element.TableName = newDefaultName; } } break; } case "Namespace": { string newNamespace = (string)e.NewValue; if (current.Name.ToLowerInvariant() != "paste") { errorMessages.Add(CommonRules.ValidateNamespace(newNamespace, CodeGenerator.IsValidLanguageIndependentIdentifier)); } break; } case "TableName": { if (!element.IsDatabaseView) { string newTableName = (string)e.NewValue; if (element.IsDependentType) { if (!modelRoot.IsEFCore5Plus && !string.IsNullOrEmpty(newTableName)) { element.TableName = string.Empty; } } else { if (string.IsNullOrEmpty(newTableName)) { element.TableName = MakeDefaultTableAndSetName(element.Name); } if (store.GetAll <ModelClass>() .Except(new[] { element }) .Any(x => x.TableName == newTableName)) { errorMessages.Add($"Table name '{newTableName}' already in use"); } } } break; } case "ViewName": { if (element.IsDatabaseView) { string newViewName = (string)e.NewValue; if (element.IsDependentType) { if (!modelRoot.IsEFCore5Plus && !string.IsNullOrEmpty(newViewName)) { element.TableName = string.Empty; } } else { if (string.IsNullOrEmpty(newViewName)) { element.TableName = MakeDefaultTableAndSetName(element.Name); } if (store.GetAll <ModelClass>() .Except(new[] { element }) .Any(x => x.TableName == newViewName)) { errorMessages.Add($"Table name '{newViewName}' already in use"); } } } break; } } errorMessages = errorMessages.Where(m => m != null).ToList(); if (errorMessages.Any()) { current.Rollback(); ErrorDisplay.Show(store, string.Join("\n", errorMessages)); } void VerifyKeylessTypeEFCore5() { // TODO: Find definitive documentation on query type restrictions in EFCore5+ // Restrictions: // ================================= // Cannot have a key defined. List <string> allIdentityAttributeNames = element.AllIdentityAttributeNames.ToList(); if (allIdentityAttributeNames.Any()) { errorMessages.Add($"{element.Name} can't be mapped to a Sql query since it has identity attribute(s) {string.Join(", ", allIdentityAttributeNames)}. Set their 'Is Identity' property to false first."); } // Only support a subset of navigation mapping capabilities, specifically: // - They may never act as the principal end of a relationship. string badAssociations = string.Join(", " , store.ElementDirectory.AllElements .OfType <Association>() .Where(a => a.Principal == element) .Select(a => a.GetDisplayText())); if (!string.IsNullOrEmpty(badAssociations)) { errorMessages.Add($"{element.Name} can't be mapped to a Sql query since it is the principal end of association(s) {badAssociations}."); } // - They may not have navigations to owned entities badAssociations = string.Join(", " , store.ElementDirectory.AllElements .OfType <Association>() .Where(a => (a is UnidirectionalAssociation && a.Source == element && a.Target.IsDependentType) || (a is BidirectionalAssociation b && b.Source == element && b.Target.IsDependentType) || (a is BidirectionalAssociation c && c.Target == element && c.Source.IsDependentType)) .Select(a => a.GetDisplayText())); if (!string.IsNullOrEmpty(badAssociations)) { errorMessages.Add($"{element.Name} can't be mapped to a Sql query since it has association(s) to dependent type(s) in {badAssociations}."); } // - Entities cannot contain navigation properties to query types. badAssociations = string.Join(", " , store.ElementDirectory.AllElements .OfType <Association>() .Where(a => (a is UnidirectionalAssociation && a.Source == element && a.Target.IsQueryType) || (a is BidirectionalAssociation b && b.Source == element && b.Target.IsQueryType) || (a is BidirectionalAssociation c && c.Target == element && c.Source.IsQueryType)) .Select(a => a.GetDisplayText())); if (!string.IsNullOrEmpty(badAssociations)) { errorMessages.Add($"{element.Name} can't be mapped to a Sql query since it has association to sql-mapped type(s) in {badAssociations}."); } // - They can only contain reference navigation properties pointing to regular entities. badAssociations = string.Join(", " , store.ElementDirectory.AllElements .OfType <Association>() .Where(a => (a is UnidirectionalAssociation && a.Source == element && a.TargetMultiplicity == Multiplicity.ZeroMany) || (a is BidirectionalAssociation b && b.Source == element && b.TargetMultiplicity == Multiplicity.ZeroMany) || (a is BidirectionalAssociation c && c.Target == element && c.SourceMultiplicity == Multiplicity.ZeroMany)) .Select(a => a.GetDisplayText())); if (!string.IsNullOrEmpty(badAssociations)) { errorMessages.Add($"{element.Name} can't be mapped to a Sql query since it has zero-to-many association(s) in {badAssociations}. Only to-one or to-zero-or-one associations are allowed. "); } } void VerifyKeylessType() { // Restrictions: // ================================= // Cannot have a key defined. List <string> allIdentityAttributeNames = element.AllIdentityAttributeNames.ToList(); if (allIdentityAttributeNames.Any()) { errorMessages.Add($"{element.Name} can't be mapped to a Sql query since it has identity attribute(s) {string.Join(", ", allIdentityAttributeNames)}. Set their 'Is Identity' property to false first."); } // Only support a subset of navigation mapping capabilities, specifically: // - They may never act as the principal end of a relationship. string badAssociations = string.Join(", " , store.ElementDirectory.AllElements .OfType <Association>() .Where(a => a.Principal == element) .Select(a => a.GetDisplayText())); if (!string.IsNullOrEmpty(badAssociations)) { errorMessages.Add($"{element.Name} can't be mapped to a Sql query since it is the principal end of association(s) {badAssociations}."); } // - They may not have navigations to owned entities badAssociations = string.Join(", " , store.ElementDirectory.AllElements .OfType <Association>() .Where(a => (a is UnidirectionalAssociation && a.Source == element && a.Target.IsDependentType) || (a is BidirectionalAssociation b && b.Source == element && b.Target.IsDependentType) || (a is BidirectionalAssociation c && c.Target == element && c.Source.IsDependentType)) .Select(a => a.GetDisplayText())); if (!string.IsNullOrEmpty(badAssociations)) { errorMessages.Add($"{element.Name} can't be mapped to a Sql query since it has association(s) to dependent type(s) in {badAssociations}."); } // - Entities cannot contain navigation properties to query types. badAssociations = string.Join(", " , store.ElementDirectory.AllElements .OfType <Association>() .Where(a => (a is UnidirectionalAssociation && a.Source == element && a.Target.IsQueryType) || (a is BidirectionalAssociation b && b.Source == element && b.Target.IsQueryType) || (a is BidirectionalAssociation c && c.Target == element && c.Source.IsQueryType)) .Select(a => a.GetDisplayText())); if (!string.IsNullOrEmpty(badAssociations)) { errorMessages.Add($"{element.Name} can't be mapped to a Sql query since it has association to sql-mapped type(s) in {badAssociations}."); } // - They can only contain reference navigation properties pointing to regular entities. badAssociations = string.Join(", " , store.ElementDirectory.AllElements .OfType <Association>() .Where(a => (a is UnidirectionalAssociation && a.Source == element && a.TargetMultiplicity == Multiplicity.ZeroMany) || (a is BidirectionalAssociation b && b.Source == element && b.TargetMultiplicity == Multiplicity.ZeroMany) || (a is BidirectionalAssociation c && c.Target == element && c.SourceMultiplicity == Multiplicity.ZeroMany)) .Select(a => a.GetDisplayText())); if (!string.IsNullOrEmpty(badAssociations)) { errorMessages.Add($"{element.Name} can't be mapped to a Sql query since it has zero-to-many association(s) in {badAssociations}. Only to-one or to-zero-or-one associations are allowed. "); } } }
public override void ElementPropertyChanged(ElementPropertyChangedEventArgs e) { base.ElementPropertyChanged(e); ModelClass element = (ModelClass)e.ModelElement; Store store = element.Store; Transaction current = store.TransactionManager.CurrentTransaction; if (current.IsSerializing) { return; } List <string> errorMessages = EFCoreValidator.GetErrors(element).ToList(); switch (e.DomainProperty.Name) { case "IsAbstract": bool newIsAbstract = (bool)e.NewValue; foreach (ClassShape classShape in PresentationViewsSubject.GetPresentation(element).OfType <ClassShape>()) { if (newIsAbstract) { classShape.OutlineColor = Color.OrangeRed; classShape.OutlineThickness = 0.02f; classShape.OutlineDashStyle = element.ImplementNotify ? DashStyle.Dot : DashStyle.Dash; } else if (element.ImplementNotify) { classShape.OutlineColor = Color.CornflowerBlue; classShape.OutlineThickness = 0.02f; classShape.OutlineDashStyle = DashStyle.Dot; } else { classShape.OutlineColor = Color.Black; classShape.OutlineThickness = 0.01f; classShape.OutlineDashStyle = DashStyle.Solid; } } break; case "ImplementNotify": bool newImplementNotify = (bool)e.NewValue; if (!element.IsAbstract) // IsAbstract takes precedence { foreach (ClassShape classShape in PresentationViewsSubject.GetPresentation(element).OfType <ClassShape>()) { if (newImplementNotify) { classShape.OutlineColor = Color.CornflowerBlue; classShape.OutlineThickness = 0.02f; classShape.OutlineDashStyle = DashStyle.Dot; } else { classShape.OutlineColor = Color.Black; classShape.OutlineThickness = 0.01f; classShape.OutlineDashStyle = DashStyle.Solid; } } } break; case "TableName": string newTableName = (string)e.NewValue; if (string.IsNullOrEmpty(newTableName)) { element.TableName = MakeDefaultName(element.Name); } if (store.ElementDirectory .AllElements .OfType <ModelClass>() .Except(new[] { element }) .Any(x => x.TableName == newTableName)) { errorMessages.Add($"Table name '{newTableName}' already in use"); } break; case "DbSetName": string newDbSetName = (string)e.NewValue; if (string.IsNullOrEmpty(newDbSetName)) { element.DbSetName = MakeDefaultName(element.Name); } if (current.Name.ToLowerInvariant() != "paste" && (string.IsNullOrWhiteSpace(newDbSetName) || !CodeGenerator.IsValidLanguageIndependentIdentifier(newDbSetName))) { errorMessages.Add("DbSet name must be a valid .NET identifier"); } else if (store.ElementDirectory .AllElements .OfType <ModelClass>() .Except(new[] { element }) .Any(x => x.DbSetName == newDbSetName)) { errorMessages.Add($"DbSet name '{newDbSetName}' already in use"); } break; case "Name": string newName = (string)e.NewValue; if (current.Name.ToLowerInvariant() != "paste" && (string.IsNullOrWhiteSpace(newName) || !CodeGenerator.IsValidLanguageIndependentIdentifier(newName))) { errorMessages.Add("Name must be a valid .NET identifier"); } else if (store.ElementDirectory .AllElements .OfType <ModelClass>() .Except(new[] { element }) .Any(x => x.Name == newName)) { errorMessages.Add($"Class name '{newName}' already in use by another class"); } else if (store.ElementDirectory .AllElements .OfType <ModelEnum>() .Any(x => x.Name == newName)) { errorMessages.Add($"Class name '{newName}' already in use by an enum"); } else if (!string.IsNullOrEmpty((string)e.OldValue)) { string oldDefaultName = MakeDefaultName((string)e.OldValue); string newDefaultName = MakeDefaultName(newName); if (element.DbSetName == oldDefaultName) { element.DbSetName = newDefaultName; } if (element.TableName == oldDefaultName) { element.TableName = newDefaultName; } } break; case "Namespace": string newNamespace = (string)e.NewValue; if (current.Name.ToLowerInvariant() != "paste") { errorMessages.Add(CommonRules.ValidateNamespace(newNamespace, CodeGenerator.IsValidLanguageIndependentIdentifier)); } break; } errorMessages = errorMessages.Where(m => m != null).ToList(); if (errorMessages.Any()) { current.Rollback(); MessageBox.Show(string.Join("; ", errorMessages)); } }
public override void ElementPropertyChanged(ElementPropertyChangedEventArgs e) { base.ElementPropertyChanged(e); ModelEnum element = (ModelEnum)e.ModelElement; Store store = element.Store; Transaction currentTransaction = store.TransactionManager.CurrentTransaction; if (currentTransaction.IsSerializing) { return; } if (Equals(e.NewValue, e.OldValue)) { return; } string errorMessage = null; switch (e.DomainProperty.Name) { case "Name": if (currentTransaction.Name.ToLowerInvariant() == "paste") { return; } if (string.IsNullOrWhiteSpace(element.Name) || !CodeGenerator.IsValidLanguageIndependentIdentifier(element.Name)) { errorMessage = "Name must be a valid .NET identifier"; } else if (store.ElementDirectory .AllElements .OfType <ModelClass>() .Any(x => x.Name == element.Name)) { errorMessage = "Enum name already in use by a class"; } else if (store.ElementDirectory .AllElements .OfType <ModelEnum>() .Except(new[] { element }) .Any(x => x.Name == element.Name)) { errorMessage = "Enum name already in use by another enum"; } else { // rename type names for ModelAttributes that reference this enum foreach (ModelAttribute modelAttribute in store.ElementDirectory.AllElements.OfType <ModelAttribute>().Where(a => a.Type == (string)e.OldValue)) { modelAttribute.Type = element.Name; } } break; case "Namespace": if (string.IsNullOrWhiteSpace(element.Namespace)) { element.Namespace = element.ModelRoot.Namespace; } if (currentTransaction.Name.ToLowerInvariant() != "paste") { errorMessage = CommonRules.ValidateNamespace(element.Namespace, CodeGenerator.IsValidLanguageIndependentIdentifier); } break; case "IsFlags": element.SetFlagValues(); break; } if (errorMessage != null) { currentTransaction.Rollback(); ErrorDisplay.Show(errorMessage); } }
public override void ElementPropertyChanged(ElementPropertyChangedEventArgs e) { base.ElementPropertyChanged(e); ModelClass element = (ModelClass)e.ModelElement; Store store = element.Store; Transaction current = store.TransactionManager.CurrentTransaction; if (current.IsSerializing) { return; } if (Equals(e.NewValue, e.OldValue)) { return; } List <string> errorMessages = EFCoreValidator.GetErrors(element).ToList(); switch (e.DomainProperty.Name) { case "IsDependentType": bool newIsStruct = (bool)e.NewValue; if (newIsStruct) { List <Association> associations = store.ElementDirectory .AllElements .OfType <Association>() .Where(a => (a.Source == element && a.SourceMultiplicity == Multiplicity.ZeroMany) || (a.Target == element && a.TargetMultiplicity == Multiplicity.ZeroMany)) .ToList(); if (associations.Any()) { List <string> classNameList = associations.Select(a => a.Target.Name).ToList(); if (classNameList.Count > 1) { classNameList[classNameList.Count - 1] = "and " + classNameList[classNameList.Count - 1]; } string classNames = string.Join(", ", classNameList); errorMessages.Add($"Can't have a 0..* association to a dependent type. Found 0..* link(s) with {classNames}"); break; } foreach (ModelAttribute modelAttribute in element.AllAttributes.Where(a => a.IsIdentity)) { modelAttribute.IsIdentity = false; } } break; case "IsAbstract": bool newIsAbstract = (bool)e.NewValue; foreach (ClassShape classShape in PresentationViewsSubject.GetPresentation(element).OfType <ClassShape>()) { if (newIsAbstract) { classShape.OutlineColor = Color.OrangeRed; classShape.OutlineThickness = 0.02f; classShape.OutlineDashStyle = element.ImplementNotify ? DashStyle.Dot : DashStyle.Dash; } else if (element.ImplementNotify) { classShape.OutlineColor = Color.CornflowerBlue; classShape.OutlineThickness = 0.02f; classShape.OutlineDashStyle = DashStyle.Dot; } else { classShape.OutlineColor = Color.Black; classShape.OutlineThickness = 0.01f; classShape.OutlineDashStyle = DashStyle.Solid; } } break; case "ImplementNotify": bool newImplementNotify = (bool)e.NewValue; if (!element.IsAbstract) // IsAbstract takes precedence { foreach (ClassShape classShape in PresentationViewsSubject.GetPresentation(element).OfType <ClassShape>()) { if (newImplementNotify) { classShape.OutlineColor = Color.CornflowerBlue; classShape.OutlineThickness = 0.02f; classShape.OutlineDashStyle = DashStyle.Dot; } else { classShape.OutlineColor = Color.Black; classShape.OutlineThickness = 0.01f; classShape.OutlineDashStyle = DashStyle.Solid; } } } if (element.ImplementNotify) { foreach (ModelAttribute modelAttribute in element.Attributes.Where(x => x.AutoProperty)) { WarningDisplay.Show($"{modelAttribute.Name} is an autoproperty, so will not participate in INotifyPropertyChanged messages"); } } break; case "TableName": string newTableName = (string)e.NewValue; if (string.IsNullOrEmpty(newTableName)) { element.TableName = MakeDefaultName(element.Name); } if (store.ElementDirectory .AllElements .OfType <ModelClass>() .Except(new[] { element }) .Any(x => x.TableName == newTableName)) { errorMessages.Add($"Table name '{newTableName}' already in use"); } break; case "DbSetName": string newDbSetName = (string)e.NewValue; if (string.IsNullOrEmpty(newDbSetName)) { element.DbSetName = MakeDefaultName(element.Name); } if (current.Name.ToLowerInvariant() != "paste" && (string.IsNullOrWhiteSpace(newDbSetName) || !CodeGenerator.IsValidLanguageIndependentIdentifier(newDbSetName))) { errorMessages.Add($"DbSet name '{newDbSetName}' isn't a valid .NET identifier."); } else if (store.ElementDirectory .AllElements .OfType <ModelClass>() .Except(new[] { element }) .Any(x => x.DbSetName == newDbSetName)) { errorMessages.Add($"DbSet name '{newDbSetName}' already in use"); } break; case "Name": string newName = (string)e.NewValue; if (current.Name.ToLowerInvariant() != "paste" && (string.IsNullOrWhiteSpace(newName) || !CodeGenerator.IsValidLanguageIndependentIdentifier(newName))) { errorMessages.Add($"Class name '{newName}' isn't a valid .NET identifier."); } else if (store.ElementDirectory .AllElements .OfType <ModelClass>() .Except(new[] { element }) .Any(x => x.Name == newName)) { errorMessages.Add($"Class name '{newName}' already in use by another class"); } else if (store.ElementDirectory .AllElements .OfType <ModelEnum>() .Any(x => x.Name == newName)) { errorMessages.Add($"Class name '{newName}' already in use by an enum"); } else if (!string.IsNullOrEmpty((string)e.OldValue)) { string oldDefaultName = MakeDefaultName((string)e.OldValue); string newDefaultName = MakeDefaultName(newName); if (element.DbSetName == oldDefaultName) { element.DbSetName = newDefaultName; } if (element.TableName == oldDefaultName) { element.TableName = newDefaultName; } } break; case "Namespace": string newNamespace = (string)e.NewValue; if (current.Name.ToLowerInvariant() != "paste") { errorMessages.Add(CommonRules.ValidateNamespace(newNamespace, CodeGenerator.IsValidLanguageIndependentIdentifier)); } break; } errorMessages = errorMessages.Where(m => m != null).ToList(); if (errorMessages.Any()) { current.Rollback(); ErrorDisplay.Show(string.Join("; ", errorMessages)); } }
public override void ElementPropertyChanged(ElementPropertyChangedEventArgs e) { base.ElementPropertyChanged(e); ModelRoot element = (ModelRoot)e.ModelElement; Store store = element.Store; Transaction current = store.TransactionManager.CurrentTransaction; if (current.IsSerializing || ModelRoot.BatchUpdating) { return; } if (Equals(e.NewValue, e.OldValue)) { return; } List <string> errorMessages = EFCoreValidator.GetErrors(element).ToList(); bool redraw = false; switch (e.DomainProperty.Name) { case "ConnectionString": if (e.NewValue != null) { element.ConnectionStringName = null; } break; case "ConnectionStringName": if (e.NewValue != null) { element.ConnectionString = null; } break; case "EntityFrameworkVersion": element.EntityFrameworkPackageVersion = "Latest"; if (element.EntityFrameworkVersion == EFVersion.EFCore) { element.InheritanceStrategy = CodeStrategy.TablePerHierarchy; } if (element.EntityFrameworkVersion == EFVersion.EF6) { List <Association> associations = store.ElementDirectory .AllElements .OfType <Association>() .Where(a => !string.IsNullOrEmpty(a.FKPropertyName) && a.SourceMultiplicity != Multiplicity.ZeroMany && a.TargetMultiplicity != Multiplicity.ZeroMany) .ToList(); string message = $"This will remove declared foreign key properties from {associations.Count} association{(associations.Count == 1 ? "" : "s")}. Are you sure?"; if (associations.Any() && BooleanQuestionDisplay.Show(store, message) == true) { foreach (Association association in associations) { association.FKPropertyName = null; AssociationChangedRules.FixupForeignKeys(association); } } } ModelRoot.ExecuteValidator?.Invoke(); break; case "EntityOutputDirectory": if (string.IsNullOrEmpty(element.EnumOutputDirectory) || element.EnumOutputDirectory == (string)e.OldValue) { element.EnumOutputDirectory = (string)e.NewValue; } if (string.IsNullOrEmpty(element.StructOutputDirectory) || element.StructOutputDirectory == (string)e.OldValue) { element.StructOutputDirectory = (string)e.NewValue; } break; case "EnumOutputDirectory": if (string.IsNullOrEmpty((string)e.NewValue) && !string.IsNullOrEmpty(element.EntityOutputDirectory)) { element.EnumOutputDirectory = element.EntityOutputDirectory; } break; case "ExposeForeignKeys": if (!element.ExposeForeignKeys) { foreach (Association association in element.Store.GetAll <Association>() .Where(a => (a.SourceRole == EndpointRole.Dependent || a.TargetRole == EndpointRole.Dependent) && !string.IsNullOrWhiteSpace(a.FKPropertyName))) { association.FKPropertyName = null; AssociationChangedRules.FixupForeignKeys(association); } } break; case "FileNameMarker": string newFileNameMarker = (string)e.NewValue; if (!Regex.Match($"a.{newFileNameMarker}.cs", @"^(?!^(PRN|AUX|CLOCK\$|NUL|CON|COM\d|LPT\d|\..*)(\..+)?$)[^\x00-\x1f\\?*:\"";|/]+$") .Success) { errorMessages.Add("Invalid value to make part of file name"); } break; case "GridColor": foreach (EFModelDiagram diagram in element.GetDiagrams()) { diagram.GridColor = (Color)e.NewValue; } redraw = true; break; case "InheritanceStrategy": if ((element.EntityFrameworkVersion == EFVersion.EFCore) && (element.NuGetPackageVersion.MajorMinorVersionNum < 2.1)) { element.InheritanceStrategy = CodeStrategy.TablePerHierarchy; } break; case "Namespace": errorMessages.Add(CommonRules.ValidateNamespace((string)e.NewValue, CodeGenerator.IsValidLanguageIndependentIdentifier)); break; case "ShowCascadeDeletes": // Normally you'd think that we should be able to register this in a AssociateValueWith call // in AssociationConnector, but that doesn't appear to work. So call the update method here. foreach (Association association in store.ElementDirectory.FindElements <Association>()) { PresentationHelper.UpdateAssociationDisplay(association); } redraw = true; break; case "ShowGrid": foreach (EFModelDiagram diagram in element.GetDiagrams()) { diagram.ShowGrid = (bool)e.NewValue; } redraw = true; break; case "ShowWarningsInDesigner": redraw = true; if ((bool)e.NewValue) { ModelRoot.ExecuteValidator?.Invoke(); } break; case "SnapToGrid": foreach (EFModelDiagram diagram in element.GetDiagrams()) { diagram.SnapToGrid = (bool)e.NewValue; } redraw = true; break; case "StructOutputDirectory": if (string.IsNullOrEmpty((string)e.NewValue) && !string.IsNullOrEmpty(element.EntityOutputDirectory)) { element.StructOutputDirectory = element.EntityOutputDirectory; } break; case "WarnOnMissingDocumentation": if (element.ShowWarningsInDesigner) { redraw = true; } ModelRoot.ExecuteValidator?.Invoke(); break; } errorMessages = errorMessages.Where(m => m != null).ToList(); if (errorMessages.Any()) { current.Rollback(); ErrorDisplay.Show(store, string.Join("\n", errorMessages)); } if (redraw) { foreach (EFModelDiagram diagram in element.GetDiagrams().Where(d => d.ActiveDiagramView != null)) { diagram.Invalidate(true); } } }
public override void ElementPropertyChanged(ElementPropertyChangedEventArgs e) { base.ElementPropertyChanged(e); ModelEnum element = (ModelEnum)e.ModelElement; Store store = element.Store; Transaction current = store.TransactionManager.CurrentTransaction; if (current.IsSerializing) { return; } string errorMessage = null; switch (e.DomainProperty.Name) { case "Name": string newName = (string)e.NewValue; if (current.Name.ToLowerInvariant() == "paste") { return; } if (current.Name.ToLowerInvariant() != "paste" && (string.IsNullOrWhiteSpace(newName) || !CodeGenerator.IsValidLanguageIndependentIdentifier(newName))) { errorMessage = "Name must be a valid .NET identifier"; } else if (store.ElementDirectory .AllElements .OfType <ModelClass>() .Any(x => x.Name == newName)) { errorMessage = "Enum name already in use by a class"; } else if (store.ElementDirectory .AllElements .OfType <ModelEnum>() .Except(new[] { element }) .Any(x => x.Name == newName)) { errorMessage = "Enum name already in use by another enum"; } break; case "Namespace": string newNamespace = (string)e.NewValue; if (current.Name.ToLowerInvariant() != "paste") { errorMessage = CommonRules.ValidateNamespace(newNamespace, CodeGenerator.IsValidLanguageIndependentIdentifier); } break; case "IsFlags": bool isFlags = (bool)e.NewValue; element.SetFlagValues(); break; } if (errorMessage != null) { current.Rollback(); MessageBox.Show(errorMessage); } }