Пример #1
0
        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();
                }
            }
        }
Пример #2
0
      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");
         }
      }
Пример #3
0
        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));
            }
        }
Пример #4
0
        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));
            }
        }
Пример #5
0
        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();
            }
        }
Пример #6
0
        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();
            }
        }
Пример #7
0
        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);
        }
Пример #8
0
        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. ");
                }
            }
        }
Пример #9
0
        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);
        }
Пример #10
0
        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);
            }
        }
Пример #11
0
        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));
            }
        }
Пример #12
0
        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);
        }