自定义删除行为

删除一个元素通常会导致同时删除相关的元素。 将删除连接到该元素的所有关系以及任何子元素。 此行为名为删除传播。 可以自定义删除传播,例如安排删除其他相关元素。 通过编写程序代码,可以根据模型的状态删除传播。 还可能发生其他更改以响应删除。

本主题包括下列章节:

  • 默认删除行为

  • 设置角色的“传播删除”选项

  • 重写删除闭包 – 在删除可能导致删除相邻元素的情况下使用此技术。

  • 使用 OnDeleting 和 OnDeleted – 在响应可以包括其他操作(例如在存储内部或外部更新值)的情况下使用这些方法。

  • 删除规则 – 在一项更改可能会导致其他更改的情况下,使用规则以在存储内传播任何类型的更新。

  • 删除事件 – 使用存储事件以在存储外传播更新,例如传播到其他 Visual Studio 文档。

  • UnMerge – 使用 UnMerge 操作来撤消已将子元素附加到其父级的合并操作。

默认删除行为

默认情况下,以下规则控制删除传播:

  • 如果删除某个元素,也将删除所有嵌入元素。 嵌入元素是作为嵌入关系的目标的元素,而此元素是嵌入关系的源。 例如,如果存在从 Album 到 Song 的嵌入关系,则当删除特定的 Album 时,还将删除它的所有 Song。

    相反,删除 Song 并不会删除 Album。

  • 默认情况下,删除不会沿着引用关系传播。 如果存在从 Album 到 Artist 的引用关系 ArtistPlaysOnAlbum,则删除 Album 不会删除任何相关 Artist,并且删除 Artist 也不会删除任何 Album。

    但是,删除将沿着一些内置关系传播。 例如,当删除模型元素时,还将删除它在关系图上的形状。 元素和形状通过 PresentationViewsSubject 引用关系相关联。

  • 不管是在源角色上还是在目标角色上,都将删除连接到该元素的每个关系。 对方角色的元素的角色属性不再包含删除的元素。

设置角色的“传播删除”选项

可以使删除沿着引用关系传播,或从嵌入子级传播到其父级。

设置删除传播

  1. 在 DSL 定义关系图上,选择想要传播删除的角色。 该角色由域关系框的左侧或右侧的线表示。

    例如,如果想要指定在删除 Album 时,也将删除相关的 Artist,则选择已连接到域类 Artist 的角色。

  2. 在“属性”窗口中,设置**“传播删除”**属性。

  3. 按 F5 并验证:

    • 当删除此关系的实例时,也将删除选定角色的元素。

    • 当删除对方角色的元素时,将删除此关系的实例,也将删除此角色的相关元素。

还可以在**“DSL 详细信息”窗口中查看“传播删除”选项。 选择一个域类,并在“DSL 详细信息”窗口中,通过单击在窗口一侧的按钮打开“删除行为”页面。 将为每个关系的对方角色显示“传播”**选项。 **“删除样式”列指示“传播”**选项是否在其默认设置上,但它不具有任何单独作用。

通过使用程序代码删除传播

DSL 定义文件中的选项仅允许你选择是否将删除传播到邻近内容。 若要实现删除传播的更复杂方案,你可以编写程序代码。

备注

若要将程序代码添加到 DSL 定义,请在 Dsl 项目中创建单独的代码文件,并编写分部定义以在生成的代码文件夹中增加类。有关详细信息,请参阅编写代码以自定义域特定语言

定义删除闭包

删除操作使用给定了初始选择的类 YourModelDeleteClosure 来确定要删除的元素。 它将重复调用 ShouldVisitRelationship() 和 ShouldVisitRolePlayer(),从而遍历这些关系图。 可以重写这些方法。 ShouldVisitRolePlayer 与链接的标识和一个链接角色的元素一起提供。 它应返回以下值之一:

  • VisitorFilterResult.Yes – 应删除该元素并且查看器应继续尝试元素的其他链接。

  • VisitorFilterResult.DoNotCare – 不应删除该元素,除非其他查询答复它应删除。

  • VisitorFilterResult.Never – 不得删除该元素,即使另一个查询回答 Yes 也是如此,并且查看器不应尝试该元素的其他链接。

// When a musician is deleted, delete their albums with a low rating.
// Override methods in <YourDsl>DeleteClosure in DomainModel.cs
partial class MusicLibDeleteClosure
{
  public override VisitorFilterResult ShouldVisitRolePlayer
    (ElementWalker walker, ModelElement sourceElement, ElementLink elementLink, 
    DomainRoleInfo targetDomainRole, ModelElement targetRolePlayer)
  {
    ArtistAppearsInAlbum link = elementLink as ArtistAppearsInAlbum;
    if (link != null 
       && targetDomainRole.RolePlayer.Id == Album.DomainClassId)
    {
      // Count other unvisited links to the Album of this link.
      if (ArtistAppearsInAlbum.GetLinksToArtists(link.Album)
              .Where(linkAlbumArtist => 
                     linkAlbumArtist != link &&
                     !walker.Visited(linkAlbumArtist))
              .Count() == 0)
      {
        // Should delete this role player:
        return VisitorFilterResult.Yes; 
      }
      else
        // Don’t delete unless another relationship deletes it:
        return VisitorFilterResult.DoNotCare; 
    }
    else 
    {
      // Test for and respond to other relationships and roles here.

      // Not the relationship or role we’re interested in.
      return base.ShouldVisitRolePlayer(walker, sourceElement, 
             elementLink, targetDomainRole, targetRolePlayer);
    }
  }
}

闭包技术可确保在开始删除之前确定要删除的元素和链接集。 查看器还将闭包的结果和来自模型其他部分的结果组合在一起。

但是,该技术假设删除在关系图中仅影响其邻近内容:无法使用此方法删除模型的另一部分中的元素。 如果想要添加元素或进行其他更改以响应删除,则不能使用此技术。

使用 OnDeleting 和 OnDeleted

可以在域类或域关系中重写 OnDeleting() 或 OnDeleted()。

  1. 在将要删除元素时,但在该元素的关系已断开连接前,调用 OnDeleting。 该元素仍可在其他元素中来回导航,并且仍位于 store.ElementDirectory 中。

    如果同时删除多个元素,则在执行删除操作前为所有元素调用 OnDeleting。

    IsDeleting 为 true。

  2. 已删除该元素后,调用 OnDeleted。 它将保留在 CLR 堆中,以便在需要时可执行“撤消”,但它已与其他元素取消链接并已从 store.ElementDirectory 中删除。 对于关系,角色仍将引用旧的角色扮演者。IsDeleted 为 True。

  3. 当用户在创建元素后调用“撤消”时,以及在“重做”中重复以前的删除时,将调用 OnDeleting 和 OnDeleted。 使用 this.Store.InUndoRedoOrRollback 来避免在这些情况下更新存储元素。 有关详细信息,请参阅如何:使用事务更新模型

例如,以下代码将在删除 Album 的最后一个子级 Song 时删除该 Album:

// Delete the parent Album when the last Song is deleted.
// Override methods in the embedding relationship between Album and Song:
partial class AlbumHasSongs
{
  protected override void OnDeleted()
  {
    base.OnDeleted();
    // Don't perform in-store actions in undo:
    if (this.Store.InUndoRedoOrRollback) return;
    // Relationship source and target still work:
    // Don't bother if source is already on its way out:
    if (!this.Album.IsDeleting && !this.Album.IsDeleted)
    {
      if (this.Album.Songs.Count == 0)
      { 
        this.Album.Delete();
} } } }

从关系的删除触发通常比从角色元素触发更有用,因为这将同时在删除元素和删除关系本身时起作用。 但是,对于引用关系,你可能想要在删除相关元素时而不是在删除关系本身时传播删除。 此示例将在删除 Album 的最后一个参与的 Artist 时删除该 Album,但它不会在删除关系时响应:

using System.Linq; ...
// Assumes a many-many reference relationship 
// between Artist and Album.  
partial class Artist
{
  protected override void OnDeleting()
  {
    base.OnDeleting();
    if (this.Store.InUndoRedoOrRollback) return;
    List<Album> toDelete = new List<Album>();
    foreach (Album album in this.Albums)
    {
      if (album.Artists.Where(artist => !artist.IsDeleting)
                        .Count() == 0)
      {
        toDelete.Add(album);
      }
    }
    foreach (Album album in toDelete)
    {
      album.Delete();
} } }

当在元素上执行 Delete 时,将调用 OnDeleting 和 OnDeleted。 这些方法始终内联执行 – 即在实际删除前后立即执行。 如果代码将删除两个或多个元素,则将在所有元素上按顺序交替调用 OnDeleting 和 OnDeleted。

删除规则和事件

作为 OnDelete 处理程序的替代方法,你可以定义删除规则和删除事件。

  1. DeletingDelete 规则仅在事务中(而不是在“撤消”或“重做”中)触发。 可以设置它们以进行排队,从而在执行删除的事务末尾执行它们。 Deleting 规则始终在队列中的任何 Deleted 规则之前执行。

    使用规则来传播仅影响存储中的元素的更改,包括关系、关系图元素及其属性。 通常,Deleting 规则用于传播删除,而 Delete 规则用于创建替换元素和关系。

    有关详细信息,请参阅规则在模型内部传播更改

  2. 在事务的末尾调用 Deleted 存储事件,并且它将在撤消或重做之后进行调用。 因此,它可以用于将删除传播到存储外的对象,例如文件、数据库项或 Visual Studio 中的其他对象。

    有关详细信息,请参阅事件处理程序在模型外部传播更改

    警告

    已删除某个元素后,可以访问其域属性值,但无法导航关系链接。但是,如果在关系上设置了 Deleted 事件,则还可以访问作为其角色扮演者的两个元素。因此,如果想要响应模型元素的删除,但不想要访问它链接到的元素,则在关系上而不是模型元素的域类上设置 Delete 事件。

示例 Deletion 规则

  [RuleOn(typeof(Album), FireTime = TimeToFire.TopLevelCommit)]
  internal class AlbumDeletingRule : DeletingRule
  {
    public override void ElementDeleting(ElementDeletingEventArgs e)
    {
      base.ElementDeleting(e);
      // ...perform tasks to propagate imminent deletion
    }
  }
  [RuleOn(typeof(Album), FireTime = TimeToFire.TopLevelCommit)]
  internal class AlbumDeletedRule : DeleteRule
  {
    public override void ElementDeleted(ElementDeletedEventArgs e)
    {
      base.ElementDeleted(e);
      // ...perform tasks such as creating new elements
    }
  }

  // The rule must be registered:
  public partial class MusicLibDomainModel
  {
    protected override Type[] GetCustomDomainModelTypes()
    {
      List<Type> types = new List<Type>(base.GetCustomDomainModelTypes());
      types.Add(typeof(AlbumDeletingRule));
      types.Add(typeof(AlbumDeletedRule));
      // If you add more rules, list them here. 
      return types.ToArray();
    }
  }

示例 Deleted 事件

  partial class NestedShapesSampleDocData
  {
    protected override void OnDocumentLoaded(EventArgs e)
    {
      base.OnDocumentLoaded(e);
      DomainRelationshipInfo commentRelationship = 
            this.Store.DomainDataDirectory
            .FindDomainRelationship(typeof(CommentsReferenceComponents));
     
      this.Store.EventManagerDirectory.ElementDeleted.Add(commentRelationship,
        new EventHandler<ElementDeletedEventArgs>(CommentLinkDeleted));
    }

    private void CommentLinkDeleted (object sender, ElementDeletedEventArgs e)
    {
      CommentsReferenceComponents link = e.ModelElement as CommentsReferenceComponents;
      Comment comment = link.Comment;
      Component component = link.Subject;
      if (comment.IsDeleted)
      {
        // The link was deleted because the comment was deleted.
        System.Windows.Forms.MessageBox.Show("Removed comment on " + component.Name);
      }
      else
      {
        // It was just the link that was deleted - the comment itself remains.
        System.Windows.Forms.MessageBox.Show("Removed comment link to " 
             + component.Name);
      }
    }
  }

UnMerge

将子元素附加到其父级的操作称为合并。 当从工具箱创建新元素或元素组时、从模型的另一个部分进行移动时,或从剪贴板进行复制时,将会发生此情况。 除了在父级和其新子级之间创建嵌入关系,合并操作还可以设置其他关系、创建辅助元素以及设置元素中的属性值。 合并操作封装在元素合并指令 (EMD) 中。

EMD 还封装取消合并或 MergeDisconnect 补充操作。 如果你具有已通过使用合并构造的元素的群集,则建议使用关联的取消合并将元素从该群集中移除(如果想要使剩余元素保留在一致的状态下)。 通常,取消合并操作将使用前面部分中所述的技术。

有关详细信息,请参阅自定义元素创建和移动

请参见

概念

自定义复制行为

自定义元素创建和移动

其他资源

编写代码以自定义域特定语言