Example #1
0
        private static void ValidateForeignKeyNames(Association element, List <string> errorMessages)
        {
            if (!string.IsNullOrWhiteSpace(element.FKPropertyName))
            {
                string[] foreignKeyPropertyNames = element.GetForeignKeyPropertyNames();
                string   tag = $"({element.Source.Name}:{element.Target.Name})";

                if (element.Dependent == null)
                {
                    errorMessages.Add($"{tag} can't have foreign keys defined; no dependent role found");

                    return;
                }

                int propertyCount = foreignKeyPropertyNames.Length;
                int identityCount = element.Principal.AllIdentityAttributes.Count();

                if (propertyCount != identityCount)
                {
                    errorMessages.Add($"{tag} foreign key must have zero or {identityCount} {(identityCount == 1 ? "property" : "properties")} defined, "
                                      + $"since {element.Principal.Name} has {identityCount} identity properties. Found {propertyCount} instead");
                }

                // validate names
                foreach (string propertyName in foreignKeyPropertyNames)
                {
                    if (!CodeGenerator.IsValidLanguageIndependentIdentifier(propertyName))
                    {
                        errorMessages.Add($"{tag} FK property name '{propertyName}' isn't a valid .NET identifier");
                    }

                    if (element.Dependent.AllAttributes.Except(element.Dependent.Attributes).Any(a => a.Name == propertyName))
                    {
                        errorMessages.Add($"{tag} FK property name '{propertyName}' is used in a base class of {element.Dependent.Name}");
                    }
                }

                // ensure no FKs are autogenerated identities
                errorMessages.AddRange(element.GetFKAutoIdentityErrors()
                                       .Select(attribute => $"{attribute.Name} in {element.Dependent.FullName} is an auto-generated identity. Migration will fail."));
            }
        }
        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;
            }

            foreach (ModelAttribute fkAttribute in element.GetForeignKeyPropertyNames()
                     .Select(propertyName => element.Dependent?.Attributes?.FirstOrDefault(a => a.Name == propertyName))
                     .Where(x => x != null))
            {
                fkAttribute.SetLocks(Locks.None);
                fkAttribute.IsForeignKey = false;
                fkAttribute.RedrawItem();
            }
        }
Example #3
0
        public override void ElementPropertyChanged(ElementPropertyChangedEventArgs e)
        {
            base.ElementPropertyChanged(e);

            Association element = (Association)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();
            BidirectionalAssociation bidirectionalAssociation = element as BidirectionalAssociation;

            switch (e.DomainProperty.Name)
            {
            case "FKPropertyName":
            {
                string fkPropertyName  = e.NewValue?.ToString();
                bool   fkPropertyError = false;

                // these can be multiples, separated by a comma
                string[] priorForeignKeyPropertyNames = e.OldValue?.ToString().Split(',').Select(n => n.Trim()).ToArray() ?? new string[0];

                IEnumerable <ModelAttribute> priorForeignKeyModelAttributes = string.IsNullOrEmpty(e.OldValue?.ToString())
                                                                                  ? Array.Empty <ModelAttribute>()
                                                                                  : priorForeignKeyPropertyNames.Select(oldValue => element.Dependent.Attributes.FirstOrDefault(a => a.Name == oldValue))
                                                                              .ToArray();

                if (!string.IsNullOrEmpty(fkPropertyName))
                {
                    string tag = $"({element.Source.Name}:{element.Target.Name})";

                    if (element.Dependent == null)
                    {
                        errorMessages.Add($"{tag} can't have foreign keys defined; no dependent role found");
                        break;
                    }

                    string[] foreignKeyPropertyNames = element.GetForeignKeyPropertyNames();
                    int      propertyCount           = foreignKeyPropertyNames.Length;
                    int      identityCount           = element.Principal.AllIdentityAttributes.Count();

                    if (propertyCount != identityCount)
                    {
                        errorMessages.Add($"{tag} foreign key must have zero or {identityCount} {(identityCount == 1 ? "property" : "properties")} defined, since "
                                          + $"{element.Principal.Name} has {identityCount} identity properties; found {propertyCount} instead");
                        fkPropertyError = true;
                    }

                    // validate names
                    foreach (string propertyName in foreignKeyPropertyNames)
                    {
                        if (!CodeGenerator.IsValidLanguageIndependentIdentifier(propertyName))
                        {
                            errorMessages.Add($"{tag} FK property name '{propertyName}' isn't a valid .NET identifier");
                            fkPropertyError = true;
                        }

                        if (element.Dependent.AllAttributes.Except(element.Dependent.Attributes).Any(a => a.Name == propertyName))
                        {
                            errorMessages.Add($"{tag} FK property name '{propertyName}' is used in a base class of {element.Dependent.Name}");
                            fkPropertyError = true;
                        }
                    }

                    if (!fkPropertyError)
                    {
                        // remove any flags and locks on the attributes that were foreign keys
                        foreach (ModelAttribute modelAttribute in priorForeignKeyModelAttributes)
                        {
                            modelAttribute.SetLocks(Locks.None);
                            modelAttribute.Summary      = null;
                            modelAttribute.IsForeignKey = false;
                            modelAttribute.RedrawItem();
                        }

                        element.EnsureForeignKeyAttributes();

                        IEnumerable <ModelAttribute> currentForeignKeyModelAttributes = foreignKeyPropertyNames.Select(newValue => element.Dependent.Attributes.FirstOrDefault(a => a.Name == newValue));

                        // add delete flags and locks to the attributes that are now foreign keys
                        foreach (ModelAttribute modelAttribute in currentForeignKeyModelAttributes)
                        {
                            modelAttribute.SetLocks(Locks.None);
                            modelAttribute.Summary = $"Foreign key for {element.GetDisplayText()}";
                            modelAttribute.SetLocks(Locks.Delete);
                            modelAttribute.IsForeignKey = true;
                            modelAttribute.RedrawItem();
                        }
                    }
                }
                else
                {
                    // foreign key was removed
                    // remove locks
                    foreach (ModelAttribute modelAttribute in priorForeignKeyModelAttributes)
                    {
                        modelAttribute.SetLocks(Locks.None);
                        modelAttribute.Summary      = null;
                        modelAttribute.IsForeignKey = false;
                    }
                }
            }

            break;

            case "SourceCustomAttributes":

                if (bidirectionalAssociation != null && !string.IsNullOrWhiteSpace(bidirectionalAssociation.SourceCustomAttributes))
                {
                    bidirectionalAssociation.SourceCustomAttributes = $"[{bidirectionalAssociation.SourceCustomAttributes.Trim('[', ']')}]";
                    CheckSourceForDisplayText(bidirectionalAssociation);
                }

                break;

            case "SourceDisplayText":

                if (bidirectionalAssociation != null)
                {
                    CheckSourceForDisplayText(bidirectionalAssociation);
                }

                break;

            case "SourceMultiplicity":
                Multiplicity sourceMultiplicity = (Multiplicity)e.NewValue;

                // change unidirectional source cardinality
                // if target is dependent
                //    source cardinality is 0..1 or 1
                if (element.Target.IsDependentType && sourceMultiplicity == Multiplicity.ZeroMany)
                {
                    errorMessages.Add($"Can't have a 0..* association from {element.Target.Name} to dependent type {element.Source.Name}");

                    break;
                }

                if ((sourceMultiplicity == Multiplicity.One && element.TargetMultiplicity == Multiplicity.One) ||
                    (sourceMultiplicity == Multiplicity.ZeroOne && element.TargetMultiplicity == Multiplicity.ZeroOne))
                {
                    if (element.SourceRole != EndpointRole.NotSet)
                    {
                        element.SourceRole = EndpointRole.NotSet;
                    }

                    if (element.TargetRole != EndpointRole.NotSet)
                    {
                        element.TargetRole = EndpointRole.NotSet;
                    }
                }
                else
                {
                    SetEndpointRoles(element);
                }

                // cascade delete behavior could now be illegal. Reset to default
                element.SourceDeleteAction = DeleteAction.Default;
                element.TargetDeleteAction = DeleteAction.Default;

                break;

            case "SourcePropertyName":
                string sourcePropertyNameErrorMessage = ValidateAssociationIdentifier(element, element.Target, (string)e.NewValue);

                if (EFModelDiagram.IsDropping && sourcePropertyNameErrorMessage != null)
                {
                    element.Delete();
                }
                else
                {
                    errorMessages.Add(sourcePropertyNameErrorMessage);
                }

                break;

            case "SourceRole":

                if (element.Source.IsDependentType)
                {
                    element.SourceRole = EndpointRole.Dependent;
                    element.TargetRole = EndpointRole.Principal;
                }
                else if (!SetEndpointRoles(element))
                {
                    if (element.SourceRole == EndpointRole.Dependent && element.TargetRole != EndpointRole.Principal)
                    {
                        element.TargetRole = EndpointRole.Principal;
                    }
                    else if (element.SourceRole == EndpointRole.Principal && element.TargetRole != EndpointRole.Dependent)
                    {
                        element.TargetRole = EndpointRole.Dependent;
                    }
                }

                break;

            case "TargetCustomAttributes":

                if (!string.IsNullOrWhiteSpace(element.TargetCustomAttributes))
                {
                    element.TargetCustomAttributes = $"[{element.TargetCustomAttributes.Trim('[', ']')}]";
                    CheckTargetForDisplayText(element);
                }

                break;

            case "TargetDisplayText":

                CheckTargetForDisplayText(element);

                break;

            case "TargetMultiplicity":
                Multiplicity newTargetMultiplicity = (Multiplicity)e.NewValue;

                // change unidirectional target cardinality
                // if target is dependent
                //    target cardinality must be 0..1 or 1
                if (element.Target.IsDependentType && newTargetMultiplicity == Multiplicity.ZeroMany)
                {
                    errorMessages.Add($"Can't have a 0..* association from {element.Source.Name} to dependent type {element.Target.Name}");

                    break;
                }

                if ((element.SourceMultiplicity == Multiplicity.One && newTargetMultiplicity == Multiplicity.One) ||
                    (element.SourceMultiplicity == Multiplicity.ZeroOne && newTargetMultiplicity == Multiplicity.ZeroOne))
                {
                    if (element.SourceRole != EndpointRole.NotSet)
                    {
                        element.SourceRole = EndpointRole.NotSet;
                    }

                    if (element.TargetRole != EndpointRole.NotSet)
                    {
                        element.TargetRole = EndpointRole.NotSet;
                    }
                }
                else
                {
                    SetEndpointRoles(element);
                }

                // cascade delete behavior could now be illegal. Reset to default
                element.SourceDeleteAction = DeleteAction.Default;
                element.TargetDeleteAction = DeleteAction.Default;

                break;

            case "TargetPropertyName":

                // if we're creating an association via drag/drop, it's possible the existing property name
                // is the same as the default property name. The default doesn't get created until the transaction is
                // committed, so the drop's action will cause a name clash. Remove the clashing property, but
                // only if drag/drop.

                string targetPropertyNameErrorMessage = ValidateAssociationIdentifier(element, element.Source, (string)e.NewValue);

                if (EFModelDiagram.IsDropping && targetPropertyNameErrorMessage != null)
                {
                    element.Delete();
                }
                else
                {
                    errorMessages.Add(targetPropertyNameErrorMessage);
                }

                break;

            case "TargetRole":

                if (element.Target.IsDependentType)
                {
                    element.SourceRole = EndpointRole.Principal;
                    element.TargetRole = EndpointRole.Dependent;
                }
                else if (!SetEndpointRoles(element))
                {
                    if (element.TargetRole == EndpointRole.Dependent && element.SourceRole != EndpointRole.Principal)
                    {
                        element.SourceRole = EndpointRole.Principal;
                    }
                    else if (element.TargetRole == EndpointRole.Principal && element.SourceRole != EndpointRole.Dependent)
                    {
                        element.SourceRole = EndpointRole.Dependent;
                    }
                }

                break;
            }

            errorMessages = errorMessages.Where(m => m != null).ToList();

            if (errorMessages.Any())
            {
                current.Rollback();
                ErrorDisplay.Show(string.Join("\n", errorMessages));
            }
        }
        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);
            }
        }