public override void ElementDeleting(ElementDeletingEventArgs e) { base.ElementDeleting(e); Association element = (Association)e.ModelElement; Store store = element.Store; Transaction current = store.TransactionManager.CurrentTransaction; if (current.IsSerializing || ModelRoot.BatchUpdating) { return; } List <ModelAttribute> unnecessaryProperties = element.Dependent?.AllAttributes?.Where(x => x.IsForeignKeyFor == element.Id && !x.IsIdentity).ToList(); if (unnecessaryProperties?.Any() != true) { WarningDisplay.Show($"{element.GetDisplayText()} doesn't specify defined foreign keys. Removing foreign key attribute(s) {string.Join(", ", unnecessaryProperties.Select(x => x.GetDisplayText()))}"); foreach (ModelAttribute fkProperty in unnecessaryProperties) { fkProperty.ClearFKMods(); fkProperty.ModelClass.Attributes.Remove(fkProperty); fkProperty.Delete(); } } }
public bool Process(string inputFile, out List<ModelElement> newElements) { try { if (inputFile == null) throw new ArgumentNullException(nameof(inputFile)); newElements = new List<ModelElement>(); string outputFilename = Path.Combine(Path.GetTempPath(), Path.GetTempFileName()); string logFilename = Path.ChangeExtension(outputFilename, "log"); StatusDisplay.Show("Detecting .NET and EF versions"); string[] parsers = { @"Parsers\EF6Parser.exe" , @"Parsers\EFCore2Parser.exe" , @"Parsers\EFCore3Parser.exe" , @"Parsers\EFCore5Parser.exe" }; Dictionary<string,bool> contexts = new Dictionary<string, bool>(); foreach (string parserPath in parsers) { if (TryProcess(inputFile, ref newElements, parserPath, outputFilename, logFilename, contexts)) return true; } foreach (string logEntry in File.ReadAllLines(logFilename)) WarningDisplay.Show(logEntry); ErrorDisplay.Show(Store, $"Error processing assembly. See Output window or {logFilename} for further information"); return false; } finally { StatusDisplay.Show("Ready"); } }
public override void ElementPropertyChanged(ElementPropertyChangedEventArgs e) { base.ElementPropertyChanged(e); ModelAttribute element = (ModelAttribute)e.ModelElement; ModelClass modelClass = element.ModelClass; 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 "Autoproperty": if (element.AutoProperty && modelClass.ImplementNotify) { WarningDisplay.Show($"{element.Name} is an autoproperty, so will not participate in INotifyPropertyChanged messages"); } break; case "Indexed": if (element.IsIdentity) { element.Indexed = true; } if (element.IsConcurrencyToken) { element.Indexed = false; } if (element.Indexed) { element.Persistent = true; } break; case "Type": string newType = (string)e.NewValue; if (element.IsIdentity) { if (!ValidIdentityAttributeTypes.Contains(ModelAttribute.ToCLRType(newType))) { errorMessages.Add($"Properties of type {newType} can't be used as identity properties."); } else { element.Required = true; element.Persistent = true; } } if (newType != "String") { element.MaxLength = 0; element.StringType = HTML5Type.None; } else { if (!element.IsValidInitialValue(newType)) { element.InitialValue = null; } } break; case "MinLength": int newMinLength = (int)e.NewValue; if (element.Type != "String") { element.MinLength = 0; } if (newMinLength < 0) { errorMessages.Add("MinLength must be zero or a positive number"); } break; case "MaxLength": int newMaxLength = (int)e.NewValue; if (element.Type != "String") { element.MaxLength = 0; } if (newMaxLength < 0) { errorMessages.Add("MaxLength must be zero or a positive number"); } break; case "IdentityType": if (element.IsIdentity) { if (element.IdentityType == IdentityType.None) { errorMessages.Add("Identity properties must have an identity type defined"); } else { element.AutoProperty = true; } } else if (!element.IsIdentity) { element.IdentityType = IdentityType.None; } break; case "ReadOnly": if (!element.Persistent || element.SetterVisibility != SetterAccessModifier.Public) { element.ReadOnly = false; } break; case "IsIdentity": bool newIsIdentity = (bool)e.NewValue; if (newIsIdentity) { if (element.ModelClass.IsDependentType) { errorMessages.Add($"Can't make {element.Name} an identity because {element.ModelClass.Name} is a dependent type and can't have an identity property."); } else { if (!ValidIdentityAttributeTypes.Contains(element.Type)) { errorMessages.Add($"Properties of type {element.Type} can't be used as identity properties."); } else { element.IsConcurrencyToken = false; element.Indexed = true; element.IndexedUnique = true; element.Persistent = true; element.Required = true; if (element.IdentityType == IdentityType.None) { element.IdentityType = IdentityType.AutoGenerated; } } } } else { element.IdentityType = IdentityType.None; } break; case "IsConcurrencyToken": bool newIsConcurrencyToken = (bool)e.NewValue; if (newIsConcurrencyToken) { element.IsIdentity = false; element.Persistent = true; element.Required = true; } break; case "Required": bool newRequired = (bool)e.NewValue; if (!newRequired) { if (element.IsIdentity || element.IsConcurrencyToken) { element.Required = true; } } break; case "Persistent": bool newPersistent = (bool)e.NewValue; if (!newPersistent) { element.IsIdentity = false; element.Indexed = false; element.IndexedUnique = false; element.IdentityType = IdentityType.None; element.IsConcurrencyToken = false; element.Virtual = false; } break; case "Name": string newName = (string)e.NewValue; if (string.IsNullOrEmpty(newName)) { errorMessages.Add("Name must be a valid .NET identifier"); } else { ParseResult fragment; try { fragment = ModelAttribute.Parse(element.ModelClass.ModelRoot, newName); if (fragment == null) { errorMessages.Add($"Could not parse entry '{newName}'"); } else { if (string.IsNullOrEmpty(fragment.Name) || !CodeGenerator.IsValidLanguageIndependentIdentifier(fragment.Name)) { errorMessages.Add("Name must be a valid .NET identifier"); } else if (modelClass.AllAttributes.Except(new[] { element }).Any(x => x.Name == fragment.Name)) { errorMessages.Add("Property name already in use"); } else if (modelClass.AllNavigationProperties().Any(p => p.PropertyName == fragment.Name)) { errorMessages.Add("Property name already in use"); } else { element.Name = fragment.Name; if (fragment.Type != null) { element.Type = fragment.Type; } if (fragment.Required != null) { element.Required = fragment.Required.Value; } if (fragment.MaxLength != null) { element.MaxLength = fragment.MaxLength.Value; } if (fragment.InitialValue != null) { element.InitialValue = fragment.InitialValue; } if (fragment.IsIdentity) { element.IsIdentity = true; // don't reset to false if not entered as part of name } } } } catch (Exception exception) { errorMessages.Add($"Could not parse entry '{newName}': {exception.Message}"); } } break; case "InitialValue": string newInitialValue = (string)e.NewValue; if (!element.IsValidInitialValue(null, newInitialValue)) { errorMessages.Add($"{newInitialValue} isn't a valid value for {element.Type}"); } 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); 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)); } }
private void ProcessEnum([NotNull] EnumDeclarationSyntax enumDecl, NamespaceDeclarationSyntax namespaceDecl = null) { if (enumDecl == null) { throw new ArgumentNullException(nameof(enumDecl)); } ModelRoot modelRoot = Store.ModelRoot(); string enumName = enumDecl.Identifier.Text; if (namespaceDecl == null && enumDecl.Parent is NamespaceDeclarationSyntax enumDeclParent) { namespaceDecl = enumDeclParent; } string namespaceName = namespaceDecl?.Name?.ToString() ?? modelRoot.Namespace; if (Store.Get <ModelClass>().Any(c => c.Name == enumName) || Store.Get <ModelEnum>().Any(c => c.Name == enumName)) { ErrorDisplay.Show($"'{enumName}' already exists in model."); // ReSharper disable once ExpressionIsAlwaysNull return; } Transaction tx = Store.TransactionManager.CurrentTransaction == null ? Store.TransactionManager.BeginTransaction() : null; try { ModelEnum result = new ModelEnum(Store, new PropertyAssignment(ModelEnum.NameDomainPropertyId, enumName)) { Namespace = namespaceName, IsFlags = enumDecl.HasAttribute("Flags") }; SimpleBaseTypeSyntax baseTypeSyntax = enumDecl.DescendantNodes().OfType <SimpleBaseTypeSyntax>().FirstOrDefault(); if (baseTypeSyntax != null) { switch (baseTypeSyntax.Type.ToString()) { case "Int16": case "short": result.ValueType = EnumValueType.Int16; break; case "Int32": case "int": result.ValueType = EnumValueType.Int32; break; case "Int64": case "long": result.ValueType = EnumValueType.Int64; break; default: WarningDisplay.Show($"Could not resolve value type for '{enumName}'. The enum will default to an Int32 value type."); break; } } XMLDocumentation xmlDocumentation; foreach (EnumMemberDeclarationSyntax enumValueDecl in enumDecl.DescendantNodes().OfType <EnumMemberDeclarationSyntax>()) { ModelEnumValue enumValue = new ModelEnumValue(Store, new PropertyAssignment(ModelEnumValue.NameDomainPropertyId, enumValueDecl.Identifier.ToString())); EqualsValueClauseSyntax valueDecl = enumValueDecl.DescendantNodes().OfType <EqualsValueClauseSyntax>().FirstOrDefault(); if (valueDecl != null) { enumValue.Value = valueDecl.Value.ToString(); } xmlDocumentation = new XMLDocumentation(enumValueDecl); enumValue.Summary = xmlDocumentation.Summary; enumValue.Description = xmlDocumentation.Description; result.Values.Add(enumValue); } xmlDocumentation = new XMLDocumentation(enumDecl); result.Summary = xmlDocumentation.Summary; result.Description = xmlDocumentation.Description; modelRoot.Enums.Add(result); } catch { tx = null; throw; } finally { tx?.Commit(); } }
private void ProcessProperties([NotNull] ClassDeclarationSyntax classDecl) { if (classDecl == null) { throw new ArgumentNullException(nameof(classDecl)); } Transaction tx = Store.TransactionManager.CurrentTransaction == null ? Store.TransactionManager.BeginTransaction() : null; try { string className = classDecl.Identifier.Text; ModelRoot modelRoot = Store.ModelRoot(); ModelClass modelClass = Store.Get <ModelClass>().FirstOrDefault(c => c.Name == className); modelClass.Attributes.Clear(); foreach (PropertyDeclarationSyntax propertyDecl in classDecl.DescendantNodes().OfType <PropertyDeclarationSyntax>()) { // if the property has a fat arrow expression as its direct descendent, it's a readonly calculated property // TODO: we should handle this // but for this release, ignore it if (propertyDecl.ChildNodes().OfType <ArrowExpressionClauseSyntax>().Any()) { continue; } AccessorDeclarationSyntax getAccessor = (AccessorDeclarationSyntax)propertyDecl.DescendantNodes().FirstOrDefault(node => node.IsKind(SyntaxKind.GetAccessorDeclaration)); AccessorDeclarationSyntax setAccessor = (AccessorDeclarationSyntax)propertyDecl.DescendantNodes().FirstOrDefault(node => node.IsKind(SyntaxKind.SetAccessorDeclaration)); // if there's no getAccessor, why are we bothering? if (getAccessor == null) { continue; } string propertyName = propertyDecl.Identifier.ToString(); string propertyType = propertyDecl.Type.ToString(); ModelClass target = modelRoot.Classes.FirstOrDefault(t => t.Name == propertyType); // is the property type a generic? // assume it's a list // TODO: this really isn't a good assumption. Fix later if (propertyDecl.ChildNodes().OfType <GenericNameSyntax>().Any()) { GenericNameSyntax genericDecl = propertyDecl.ChildNodes().OfType <GenericNameSyntax>().FirstOrDefault(); List <string> contentTypes = genericDecl.DescendantNodes().OfType <IdentifierNameSyntax>().Select(i => i.Identifier.ToString()).ToList(); // there can only be one generic argument if (contentTypes.Count != 1) { WarningDisplay.Show($"Found {className}.{propertyName}, but its type ({genericDecl.Identifier}<{String.Join(", ", contentTypes)}>) isn't anything expected. Ignoring..."); continue; } propertyType = contentTypes[0]; target = modelRoot.Classes.FirstOrDefault(t => t.Name == propertyType); if (target == null) { target = new ModelClass(Store, new PropertyAssignment(ModelClass.NameDomainPropertyId, propertyType)); modelRoot.Classes.Add(target); } ProcessAssociation(modelClass, target, propertyDecl, true); continue; } // is the property type an existing ModelClass? if (target != null) { ProcessAssociation(modelClass, target, propertyDecl); continue; } bool propertyShowsNullable = propertyDecl.DescendantNodes().OfType <NullableTypeSyntax>().Any(); // is the property type something we don't know about? if (!modelRoot.IsValidCLRType(propertyType)) { // might be an enum. If so, we'll handle it like a CLR type // if it's nullable, it's definitely an enum, but if we don't know about it, it could be an enum or a class if (!KnownEnums.Contains(propertyType) && !propertyShowsNullable) { // assume it's a class and create the class target = new ModelClass(Store, new PropertyAssignment(ModelClass.NameDomainPropertyId, propertyType)); modelRoot.Classes.Add(target); ProcessAssociation(modelClass, target, propertyDecl); continue; } } // if we're here, it's just a property (CLR or enum) try { // ReSharper disable once UseObjectOrCollectionInitializer ModelAttribute modelAttribute = new ModelAttribute(Store, new PropertyAssignment(ModelAttribute.NameDomainPropertyId, propertyName)) { Type = ModelAttribute.ToCLRType(propertyDecl.Type.ToString()).Trim('?'), Required = propertyDecl.HasAttribute("RequiredAttribute") || !propertyShowsNullable, Indexed = propertyDecl.HasAttribute("IndexedAttribute"), IsIdentity = propertyDecl.HasAttribute("KeyAttribute"), Virtual = propertyDecl.DescendantTokens().Any(t => t.IsKind(SyntaxKind.VirtualKeyword)) }; if (modelAttribute.Type.ToLower() == "string") { AttributeSyntax maxLengthAttribute = propertyDecl.GetAttribute("MaxLengthAttribute"); AttributeArgumentSyntax maxLength = maxLengthAttribute?.GetAttributeArguments()?.FirstOrDefault(); if (maxLength != null) { modelAttribute.MaxLength = TryParse(maxLength.Expression.ToString(), out int _max) ? _max : -1; } AttributeSyntax minLengthAttribute = propertyDecl.GetAttribute("MinLengthAttribute"); AttributeArgumentSyntax minLength = minLengthAttribute?.GetAttributeArguments()?.FirstOrDefault(); if (minLength != null) { modelAttribute.MinLength = TryParse(minLength.Expression.ToString(), out int _min) ? _min : 0; } } else { modelAttribute.MaxLength = -1; modelAttribute.MinLength = 0; } // if no setAccessor, it's a calculated readonly property if (setAccessor == null) { modelAttribute.Persistent = false; modelAttribute.ReadOnly = true; } modelAttribute.AutoProperty = !getAccessor.DescendantNodes().Any(node => node.IsKind(SyntaxKind.Block)) && !setAccessor.DescendantNodes().Any(node => node.IsKind(SyntaxKind.Block)); modelAttribute.SetterVisibility = setAccessor.Modifiers.Any(m => m.ToString() == "protected") ? SetterAccessModifier.Protected : setAccessor.Modifiers.Any(m => m.ToString() == "internal") ? SetterAccessModifier.Internal : SetterAccessModifier.Public; XMLDocumentation xmlDocumentation = new XMLDocumentation(propertyDecl); modelAttribute.Summary = xmlDocumentation.Summary; modelAttribute.Description = xmlDocumentation.Description; modelClass.Attributes.Add(modelAttribute); } catch { WarningDisplay.Show($"Could not parse '{className}.{propertyDecl.Identifier}'."); } } } catch { tx = null; throw; } finally { tx?.Commit(); } }
public bool Process(string filename) { if (String.IsNullOrEmpty(filename)) { throw new ArgumentNullException(nameof(filename)); } try { // read the file string fileContents = File.ReadAllText(filename); // parse the contents SyntaxTree tree = CSharpSyntaxTree.ParseText(fileContents); if (tree.GetRoot() is CompilationUnitSyntax root) { List <ClassDeclarationSyntax> classDecls = root.DescendantNodes().OfType <ClassDeclarationSyntax>().Where(classDecl => classDecl.BaseList == null || classDecl.BaseList.Types.FirstOrDefault()?.ToString() != "DbContext").ToList(); List <EnumDeclarationSyntax> enumDecls = root.DescendantNodes().OfType <EnumDeclarationSyntax>().ToList(); if (!classDecls.Any() && !enumDecls.Any()) { WarningDisplay.Show($"Couldn't find any classes or enums to add to the model in {filename}"); return(false); } // keep this order: enums, classes, class properties foreach (EnumDeclarationSyntax enumDecl in enumDecls) { ProcessEnum(enumDecl); } List <ModelClass> processedClasses = new List <ModelClass>(); foreach (ClassDeclarationSyntax classDecl in classDecls) { processedClasses.Add(ProcessClass(classDecl)); } // process last so all classes and enums are already in the model foreach (ClassDeclarationSyntax classDecl in classDecls) { ProcessProperties(classDecl); } // now that all the properties are in, go through the classes again and ensure identities are present based on convention // ReSharper disable once LoopCanBePartlyConvertedToQuery foreach (ModelClass modelClass in processedClasses.Where(c => !c.AllIdentityAttributes.Any())) { // no identity attribute. Only look in current class for attributes that could be identity by convention List <ModelAttribute> identitiesByConvention = modelClass.Attributes.Where(a => a.Name == "Id" || a.Name == $"{modelClass.Name}Id").ToList(); // if both 'Id' and '[ClassName]Id' are present, don't do anything since we don't know which to make the identity if (identitiesByConvention.Count == 1) { using (Transaction transaction = Store.TransactionManager.BeginTransaction("Add identity")) { identitiesByConvention[0].IsIdentity = true; transaction.Commit(); } } } } } catch { ErrorDisplay.Show("Error interpreting " + filename); return(false); } return(true); }
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. "); } } }
private static bool DoHandleDrop([NotNull] Store store, [NotNull] string filename) { if (store == null) { throw new ArgumentNullException(nameof(store)); } if (filename == null) { throw new ArgumentNullException(nameof(filename)); } try { if (string.IsNullOrEmpty(filename)) { return(false); } // read the file string fileContents = File.ReadAllText(filename); // parse the contents SyntaxTree tree = CSharpSyntaxTree.ParseText(fileContents); if (tree.GetRoot() is CompilationUnitSyntax root) { List <ClassDeclarationSyntax> classDecls = root.DescendantNodes().OfType <ClassDeclarationSyntax>().Where(classDecl => classDecl.BaseList == null || classDecl.BaseList.Types.FirstOrDefault()?.ToString() != "DbContext").ToList(); List <EnumDeclarationSyntax> enumDecls = root.DescendantNodes().OfType <EnumDeclarationSyntax>().ToList(); if (!classDecls.Any() && !enumDecls.Any()) { WarningDisplay.Show("Couldn't find any classes or enums to add to the model"); return(false); } // keep this order: enums, classes, class properties foreach (EnumDeclarationSyntax enumDecl in enumDecls) { ProcessEnum(store, enumDecl); } foreach (ClassDeclarationSyntax classDecl in classDecls) { ProcessClass(store, classDecl); } // process last so all classes and enums are already in the model foreach (ClassDeclarationSyntax classDecl in classDecls) { ProcessProperties(store, classDecl); } } } catch { ErrorDisplay.Show("Error interpretting " + filename); return(false); } return(true); }
public static void FixupForeignKeys(Association element) { List <ModelAttribute> fkProperties = element.Source.Attributes.Where(x => x.IsForeignKeyFor == element.Id) .Union(element.Target.Attributes.Where(x => x.IsForeignKeyFor == element.Id)) .ToList(); // EF6 can't have declared foreign keys for 1..1 / 0-1..1 / 1..0-1 / 0-1..0-1 relationships if (!string.IsNullOrEmpty(element.FKPropertyName) && element.Source.ModelRoot.EntityFrameworkVersion == EFVersion.EF6 && element.SourceMultiplicity != Multiplicity.ZeroMany && element.TargetMultiplicity != Multiplicity.ZeroMany) { element.FKPropertyName = null; } // if no FKs, remove all the attributes for this element if (string.IsNullOrEmpty(element.FKPropertyName) || element.Dependent == null) { List <ModelAttribute> unnecessaryProperties = fkProperties.Where(x => !x.IsIdentity).ToList(); if (unnecessaryProperties.Any()) { WarningDisplay.Show($"{element.GetDisplayText()} doesn't specify defined foreign keys. Removing foreign key attribute(s) {string.Join(", ", unnecessaryProperties.Select(x => x.GetDisplayText()))}"); } foreach (ModelAttribute attribute in unnecessaryProperties) { attribute.ClearFKMods(string.Empty); attribute.Delete(); } return; } // synchronize what's there to what should be there string[] currentForeignKeyPropertyNames = element.GetForeignKeyPropertyNames(); (IEnumerable <string> add, IEnumerable <ModelAttribute> remove) = fkProperties.Synchronize(currentForeignKeyPropertyNames, (attribute, name) => attribute.Name == name); List <ModelAttribute> removeList = remove.ToList(); fkProperties = fkProperties.Except(removeList).ToList(); // remove extras if (removeList.Any()) { WarningDisplay.Show($"{element.GetDisplayText()} has extra foreign keys. Removing unnecessary foreign key attribute(s) {string.Join(", ", removeList.Select(x => x.GetDisplayText()))}"); } for (int index = 0; index < removeList.Count; index++) { ModelAttribute attribute = removeList[index]; attribute.ClearFKMods(string.Empty); attribute.Delete(); removeList.RemoveAt(index--); } // reparent existing properties if needed foreach (ModelAttribute existing in fkProperties.Where(x => x.ModelClass != element.Dependent)) { existing.ClearFKMods(); existing.ModelClass.MoveAttribute(existing, element.Dependent); existing.SetFKMods(element); } // create new properties if they don't already exist foreach (string propertyName in add.Where(n => element.Dependent.Attributes.All(a => a.Name != n))) { element.Dependent.Attributes.Add(new ModelAttribute(element.Store, new PropertyAssignment(ModelAttribute.NameDomainPropertyId, propertyName))); } // make a pass through and fixup the types, summaries, etc. based on the principal's identity attributes ModelAttribute[] principalIdentityAttributes = element.Principal.AllIdentityAttributes.ToArray(); string summaryBoilerplate = element.GetSummaryBoilerplate(); for (int index = 0; index < currentForeignKeyPropertyNames.Length; index++) { ModelAttribute fkProperty = element.Dependent.Attributes.First(x => x.Name == currentForeignKeyPropertyNames[index]); ModelAttribute idProperty = principalIdentityAttributes[index]; bool required = element.Dependent == element.Source ? element.TargetMultiplicity == Multiplicity.One : element.SourceMultiplicity == Multiplicity.One; fkProperty.SetFKMods(element , summaryBoilerplate , required , idProperty.Type); } }
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 bool ProcessFile() { // read the file string fileContents; try { fileContents = File.ReadAllText(filename); } catch (Exception ex) { WarningDisplay.Show($"Error reading {filename}: {ex.Message}"); return(false); } // parse the contents SyntaxTree tree = CSharpSyntaxTree.ParseText(fileContents); if (tree.GetRoot() is CompilationUnitSyntax root) { List <ClassDeclarationSyntax> classDecls = root.DescendantNodes() .OfType <ClassDeclarationSyntax>() .Where(classDecl => classDecl.BaseList == null || classDecl.BaseList.Types.FirstOrDefault()?.ToString() != "DbContext") .ToList(); List <EnumDeclarationSyntax> enumDecls = root.DescendantNodes().OfType <EnumDeclarationSyntax>().ToList(); if (!classDecls.Any() && !enumDecls.Any()) { WarningDisplay.Show($"Couldn't find any classes or enums to add to the model in {filename}"); } else { // keep this order: enums, classes, class properties, identities foreach (EnumDeclarationSyntax enumDecl in enumDecls) { ProcessEnum(enumDecl); } List <ModelClass> processedClasses = new List <ModelClass>(); foreach (ClassDeclarationSyntax classDecl in classDecls) { processedClasses.Add(ProcessClass(classDecl)); } // process last so all classes and enums are already in the model foreach (ClassDeclarationSyntax classDecl in classDecls) { ProcessProperties(classDecl); } // now that all the properties are in, go through the classes again and ensure identities are present based on convention ProcessIdentities(processedClasses); return(true); } } return(false); }