private EfiTransaction(EFArtifact artifact, string originatorId, bool createdXmlTxn, EfiTransactionContext context) { _artifact = artifact; _changes = new EfiChangeGroup(this); _createdXmlTxn = createdXmlTxn; _originatorId = originatorId; _efiTransactionContext = context != null ? context : new EfiTransactionContext(); }
protected override void OnAfterHandleXmlModelTransactionCompleted( object sender, XmlTransactionEventArgs xmlTransactionEventArgs, EfiChangeGroup changeGroup) { base.OnAfterHandleXmlModelTransactionCompleted(sender, xmlTransactionEventArgs, changeGroup); Debug.Assert(_layerManager != null, "LayerManager must not be null"); if (_layerManager != null) { var changes = from ixc in xmlTransactionEventArgs.Transaction.Changes() select new Tuple<XObject, XObjectChange>(ixc.Node, ixc.Action); _layerManager.OnAfterTransactionCommitted(changes); } }
internal EfiChangedEventArgs(EfiChangeGroup changes) { _changes = changes; }
internal void RecordChangeGroup(EfiChangeGroup changeGroup) { if (changeGroup != null && changeGroup.Count > 0) { _changeGroups.Enqueue(changeGroup); } }
internal EDMXModelChangeEventArgs(EfiChangeGroup changeGroup) { _efiChangeGroup = changeGroup; }
private void ProcessExternalAddOrUpdateChange( EfiChangeGroup changeGroup, List<EFContainer> containersToRenormalize, ExternalXMLModelChange undoModelChange) { var changedEFObject = undoModelChange.ChangedEFObject; var parentEFObject = undoModelChange.Parent; if (parentEFObject != null) { var parentEFContainer = parentEFObject as EFContainer; Debug.Assert(parentEFContainer != null, "parentEFObject was not an EFContainer!"); // if this is an "add" for an element, then the parent will parse the new child. // if this is an "add" for an attribute, then we have to directly set the XObject // if this is an "update" then we will just record it in our model if (parentEFContainer != null && undoModelChange.Action == XObjectChange.Add) { var newXElement = undoModelChange.XNode as XElement; if (newXElement != null && parentEFContainer.ReparseSingleElement(new List<XName>(), newXElement)) { var newEFObject = ModelItemAnnotation.GetModelItem(undoModelChange.XNode); Debug.Assert(newEFObject != null, "Couldn't find the ModelItemAnnotation for the newly created XLinq node"); if (newEFObject != null) { var newEFElement = newEFObject as EFElement; Debug.Assert(newEFElement != null, "If the XObject was an XElement, then we should have an EFElement as well"); if (newEFElement != null) { XmlModelHelper.NormalizeAndResolve(newEFElement); } // we need to rediscover the XLinq annotation to pick up the new EFObject that just got created changedEFObject = ModelItemAnnotation.GetModelItem(undoModelChange.XNode); } } else if (undoModelChange.XNode is XAttribute) { var staleItemBinding = changedEFObject as ItemBinding; var staleDefaultableValue = changedEFObject as DefaultableValue; // item bindings might have gotten added after their parents got added in which case the MIA will be stale. // we have to step through the new parent, set the xobject manually, rebind, and reset the annotation on the xlinq node. if (staleItemBinding != null) { foreach (var child in parentEFContainer.Children) { var updatedItemBinding = child as ItemBinding; if (updatedItemBinding != null && updatedItemBinding.EFTypeName == staleItemBinding.EFTypeName) { updatedItemBinding.SetXObject(undoModelChange.XNode); updatedItemBinding.Rebind(); changedEFObject = updatedItemBinding; } } } // for defaultable values that got added after parents were added, we have to discover the actual EFObject in the Escher tree, // set the xobject, and reset the annotation on the existing xlinq node else if (staleDefaultableValue != null) { foreach (var child in parentEFContainer.Children) { var updatedDefaultableValue = child as DefaultableValue; if (updatedDefaultableValue != null && updatedDefaultableValue.EFTypeName == staleDefaultableValue.EFTypeName) { updatedDefaultableValue.SetXObject(undoModelChange.XNode); changedEFObject = updatedDefaultableValue; } } } ModelItemAnnotation.SetModelItem(undoModelChange.XNode, changedEFObject); } } else if (undoModelChange.Action == XObjectChange.Value) { Debug.Assert(undoModelChange.XNode is XAttribute, "The only 'value' change Escher supports is to XAttributes"); if (undoModelChange.XNode is XAttribute) { var existingItemBinding = changedEFObject as ItemBinding; if (existingItemBinding != null) { existingItemBinding.Rebind(); } // we have to normalize and resolve the parents of DefaultableValues // because this change could affect the parent's RefName, affecting SingleItemBindings var defaultableValue = changedEFObject as DefaultableValue; if (defaultableValue != null) { XmlModelHelper.NormalizeAndResolve(parentEFContainer); #if DEBUG var xattr = undoModelChange.XNode as XAttribute; var defaultableValueValue = defaultableValue.ObjectValue as string; if (defaultableValueValue != null) { // verify that the defaultableValue's value & the XAttribute's value are the same Debug.Assert(xattr.Value == defaultableValueValue); } #endif } } } else { Debug.Assert(false, "Encountered a non-valid type of change to the XML: " + undoModelChange.Action.ToString()); throw new ChangeProcessingFailedException(); } // if an object's state is unresolved, then queue it for re-normalization. This occurs // for example, if itembinding changes occur before the 'add' changes for their targeted objects var itemBinding = changedEFObject as ItemBinding; if ((itemBinding != null && false == itemBinding.Resolved) || parentEFContainer.State != EFElementState.Resolved) { containersToRenormalize.Add(parentEFContainer); } CheckObjectToRenormalize(changedEFObject, ref containersToRenormalize); // now tell the views that a new item has been created; this will happen for both an 'add' and a 'change' string oldValue = null; string newValue = null; string property = null; GetOldAndNewValues(undoModelChange.XmlChange, out property, out oldValue, out newValue); changeGroup.RecordModelChange(GetChangeType(undoModelChange.XmlChange), changedEFObject, property, oldValue, newValue); } else { // we got a change event on a root-node in the document. // This will cause an NRE when looking for the model-item annotation, // so throw this exception to cause the caller to reload the doc throw new ChangeProcessingFailedException(); } }
private static void ProcessExternalRemoveChange( EfiChangeGroup changeGroup, IList<ItemBinding> bindingsForRebind, ExternalXMLModelChange modelChange) { var changedEFObject = modelChange.ChangedEFObject; var parentEFObject = modelChange.Parent; if (changedEFObject != null) { var staleItemBinding = changedEFObject as ItemBinding; var staleDefaultableValue = changedEFObject as DefaultableValue; var parentEFContainer = parentEFObject as EFContainer; Debug.Assert(parentEFContainer != null, "parentEfObject was not an EFContainer!"); if (staleItemBinding != null) { // if this is an itembinding, then we have to directly null out the xobject and rebind since the refname is a "symlink" to the xattribute foreach (var child in parentEFContainer.Children) { var updatedItemBinding = child as ItemBinding; if (updatedItemBinding != null && updatedItemBinding.EFTypeName == staleItemBinding.EFTypeName) { updatedItemBinding.SetXObject(null); updatedItemBinding.Rebind(); changedEFObject = updatedItemBinding; break; } } } else if (staleDefaultableValue != null) { foreach (var child in parentEFContainer.Children) { var updatedDefaultableValue = child as DefaultableValue; if (updatedDefaultableValue != null && updatedDefaultableValue.EFTypeName == staleDefaultableValue.EFTypeName) { updatedDefaultableValue.SetXObject(null); changedEFObject = updatedDefaultableValue; break; } } } else { // Find all the dependent binding. var visitor = new AntiDependencyCollectorVisitor(); visitor.Traverse(changedEFObject); foreach (var binding in visitor.AntiDependencyBindings) { if (!bindingsForRebind.Contains(binding)) { bindingsForRebind.Add(binding); } } // Delete(false) because it has already been removed from XLinq tree changedEFObject.Delete(false); } // record the change so views get updated changeGroup.RecordModelChange(EfiChange.EfiChangeType.Delete, changedEFObject, String.Empty, String.Empty, String.Empty); } else { throw new ChangeProcessingFailedException(); } }
protected virtual void OnAfterHandleXmlModelTransactionCompleted( object sender, XmlTransactionEventArgs xmlTransactionEventArgs, EfiChangeGroup changeGroup) { }
protected virtual void OnHandleXmlModelTransactionCompleted( object sender, XmlTransactionEventArgs xmlTransactionEventArgs, bool isUndoOrRedo, out EfiChangeGroup changeGroup) { // If this is an undo/redo XML transaction there is no EfiTransaction, thus the artifact will not // be made dirty as necessary. We will have to do it manually here. var efiTransaction = xmlTransactionEventArgs.Transaction.UserState as EfiTransaction; if (efiTransaction == null && isUndoOrRedo) { Artifact.IsDirty = true; } // When an XML transaction completes it could either be a normal, undo, or redo transaction. // In all cases we will need to clear the "validity" of the artifact so that any successive // validations will not short-circuit. // Ideally we can skip this in the event of any major error that causes the reloading // of the artifact but we'll be safe. SetValidityDirtyForErrorClass(ErrorClass.All, true); // the change group to send back to the caller changeGroup = null; // if the transaction is aborting, drop and reload if (xmlTransactionEventArgs.Transaction.Status == XmlTransactionStatus.Aborted) { ReloadArtifact(); return; } if (efiTransaction != null) { changeGroup = ProcessDesignerChange(xmlTransactionEventArgs, efiTransaction); if (changeGroup != null) { ModelManager.RecordChangeGroup(changeGroup); } } else { // TODO: when we want SxS again, we should handle these operations in addition to undo/redo if (isUndoOrRedo) { try { changeGroup = ProcessUndoRedoChanges(xmlTransactionEventArgs); if (changeGroup != null) { ModelManager.RecordChangeGroup(changeGroup); // we have to manually route the change groups here because we can't rely on ProcessUndoRedoChange to do it since nothing // gets updated in the Xml Model ModelManager.RouteChangeGroups(); } } catch (ChangeProcessingFailedException) { ReloadArtifact(); } catch (Exception e) { Debug.Fail("Unexpected exception caught while processing undo/redo", e.Message); ReloadArtifact(); } } } }
private void ProcessModelChanges(EfiChangeGroup changeGroup) { EntityDesignerDiagram diagram = null; diagram = GetDiagram(); var disableFixUpDiagramSelection = false; // If diagram is null it might have been deleted, in this case just return. if (diagram == null) { return; } // Store the value of the disableFixUpDiagramSelection. disableFixUpDiagramSelection = diagram.DisableFixUpDiagramSelection; try { // // This turns off layout during "serialization" and fixes bad perf during update-model scenarios. // We set this to true here, and then set it false in the finally block because there were some problems with // the DSL fix not drawing self-associations correctly when this is set to true all the time. // if (changeGroup.Transaction != null && (changeGroup.Transaction.OriginatorId == EfiTransactionOriginator.UpdateModelFromDatabaseId || changeGroup.Transaction.OriginatorId == EfiTransactionOriginator.UndoRedoOriginatorId)) { Store.PropertyBag["WorkaroundFixSerializationTransaction"] = true; } // If coming from property window, we don't want the selection to change. else if (changeGroup.Transaction.OriginatorId == EfiTransactionOriginator.PropertyWindowOriginatorId && diagram != null) { diagram.DisableFixUpDiagramSelection = true; } // sort changes and discover the artifact that has changed var extraElementsToProcess = new HashSet<EFObject>(); var changes = changeGroup.SortChangesForProcessing(new ChangeComparer()); var artifacts = CurrentArtifactsInView; /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // FIRST STEP: updating DSL Model Elements. /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // tx name is not shown anywhere since we don't use the DSL undo manager using (var t = Store.TransactionManager.BeginTransaction( InternalTransactionIdPrefix + changeGroup.Transaction.OriginatorId, true)) { // start auto-arranger for arranging new elements // don't do this for undo/redo because we will translate the new diagram EFObject information into the DSL ShapeElements // don't do this for update model because we want to set up an entirely new transaction for auto-layout with the new shapes if (changeGroup.Transaction.OriginatorId != EfiTransactionOriginator.UndoRedoOriginatorId && changeGroup.Transaction.OriginatorId != EfiTransactionOriginator.UpdateModelFromDatabaseId) { if (diagram != null) { diagram.Arranger.Start(PointD.Empty); } } var hasChangesForCurrentArtifactInView = false; foreach (var change in changes) { if (artifacts.Contains(change.Changed.Artifact)) { // only process changes if the artifact is designer-safe. if (change.Changed.Artifact.IsDesignerSafe) { hasChangesForCurrentArtifactInView = true; if (change.Changed is ModelDiagram.BaseDiagramObject) { ProcessSingleDiagramModelChange(change, extraElementsToProcess); } else if (ProcessSingleModelChange(change)) { break; } } } } foreach (var efobject in extraElementsToProcess) { var et = efobject as ConceptualEntityType; if (et != null) { foreach (var p in et.Properties()) { OnEFObjectCreatedOrUpdated(p, ModelXRef); } foreach (var np in et.NavigationProperties()) { OnEFObjectCreatedOrUpdated(np, ModelXRef); } if (et.BaseType.Target != null) { OnEFObjectCreatedOrUpdated(et.BaseType, ModelXRef); } } else { Debug.Fail("Unexpected type of model node in extraElementsToProcess"); } } if (hasChangesForCurrentArtifactInView & t.HasPendingChanges) { t.Commit(); } if (changeGroup.Transaction.OriginatorId != EfiTransactionOriginator.UndoRedoOriginatorId && changeGroup.Transaction.OriginatorId != EfiTransactionOriginator.UpdateModelFromDatabaseId) { Debug.Assert(diagram != null, "Should have discovered the diagram if autoArrange == true"); if (diagram != null) { diagram.Arranger.End(); } } if (ModelHasElements() == false) { var edd = GetDiagram(); if (edd != null) { edd.ResetWatermark(edd.ActiveDiagramView); } } } // we need to keep track of the entity shapes that needed to be auto layout. var shapesToAutoLayout = new List<ShapeElement>(); /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // SECOND STEP: updating DSL Presentation Elements. /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Given the changegroup and any transaction that originates from the model, see if there were any diagram EFObjects were changed. // Go through each one, find the corresponding ShapeElement (DSL presentation element) that was created, and add it to the view model xref. if (changeGroup.Transaction != null) { using (var t = Store.TransactionManager.BeginTransaction( InternalTransactionIdPrefix + changeGroup.Transaction.OriginatorId, true)) { var hasDiagramObjectChanges = false; var moveDiagram2Shapes = (changeGroup.Transaction.OriginatorId == EfiTransactionOriginator.UndoRedoOriginatorId || changeGroup.Transaction.OriginatorId == EfiTransactionOriginator.UpdateModelFromDatabaseId); foreach (var change in changes) { if (artifacts.Contains(change.Changed.Artifact)) { if (change.Changed.Artifact.IsDesignerSafe) { if (change.Changed is DefaultableValue && change.Changed.Parent is ModelDesigner.Diagram && ModelXRef.ContainsKey(change.Changed.Parent)) { hasDiagramObjectChanges = true; ModelTranslatorContextItem.GetEntityModelTranslator(EditingContext) .SynchronizeSingleDslModelElement(this, change.Changed.Parent); } else { // this will set the ShapeElements to their original position as well as add them to the XRef ModelDiagram.BaseDiagramObject modelDiagramObject = null; var currentNode = change.Changed; while (currentNode != null) { var baseDiagram = currentNode as ModelDiagram.BaseDiagramObject; if (baseDiagram != null) { modelDiagramObject = baseDiagram; break; } currentNode = currentNode.Parent; } // Call TranslateDiagramObject to sync Escher Designer Model element and DSL PEL (Presentation Element). // We need to call this even when the change type is 'delete' because it might affect DSL PEL. // (For example: when you change entity type shape's FillColor to a default color, the FillColor attribute will be deleted since it is not needed, // in that case, we still need to update DSL PEL). if (modelDiagramObject != null && modelDiagramObject.Diagram != null && modelDiagramObject.Diagram.Id == DiagramId) { hasDiagramObjectChanges = true; EntityModelToDslModelTranslatorStrategy.TranslateDiagramObject( this, modelDiagramObject, moveDiagram2Shapes, shapesToAutoLayout); } } } } } if (hasDiagramObjectChanges && t.HasPendingChanges) { t.Commit(); } } } if (changeGroup.Transaction != null && shapesToAutoLayout.Count > 0) { diagram.AutoLayoutDiagram(shapesToAutoLayout); } } finally { Store.PropertyBag["WorkaroundFixSerializationTransaction"] = false; if (diagram != null) { diagram.DisableFixUpDiagramSelection = disableFixUpDiagramSelection; } } if (_shouldClearAndReloadDiagram) { try { ClearAndReloadDiagram(); } finally { _shouldClearAndReloadDiagram = false; } } }