规则在模型内传递更改

可以创建存储规则,以在可视化和建模 SDK(VMSDK)中将更改从一个元素传播到另一个元素。 当 Store 中的任何元素发生更改时,通常会在提交最外部的事务时立即调度执行这些规则。 不同类型的事件有不同类型的规则,例如添加元素或删除它。 可以将规则附加到特定类型的元素、形状或关系图。 许多内置功能由规则定义:例如,规则可确保模型更改时更新关系图。 可以通过添加自己的规则来自定义特定于域的语言。

存储规则特别适用于在存储内传播更改,即对模型元素、关系、形状或连接符及其域属性的更改。 当用户调用撤消或恢复命令时,规则不会运行。 相反,事务管理器可确保存储内容还原到正确的状态。 如果要将更改传播到存储区外部的资源,请使用存储事件。 有关详细信息,请参阅 事件处理程序在模型外传播更改

例如,假设你想要指定每当用户(或代码)创建 ExampleDomainClass 类型的新元素时,另一种类型的附加元素将在模型的另一部分创建。 可以编写 AddRule 并将其与 ExampleDomainClass 相关联。 在规则中编写代码以创建其他元素。

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.Modeling;

namespace ExampleNamespace
{
 // Attribute associates the rule with a domain class:
 [RuleOn(typeof(ExampleDomainClass), FireTime=TimeToFire.TopLevelCommit)]
 // The rule is a class derived from one of the abstract rules:
 class MyAddRule : AddRule
 {
  // Override the abstract method:
  public override void ElementAdded(ElementAddedEventArgs e)
  {
    base.ElementAdded(e);
    ExampleDomainClass element = e.ModelElement;
    Store store = element.Store;
    // Ignore this call if we're currently loading a model:
    if (store.TransactionManager.CurrentTransaction.IsSerializing)
       return;

    // Code here propagates change as required - for example:
      AnotherDomainClass echo = new AnotherDomainClass(element.Partition);
      echo.Name = element.Name;
      echo.Parent = element.Parent;
    }
  }
 // The rule must be registered:
 public partial class ExampleDomainModel
 {
   protected override Type[] GetCustomDomainModelTypes()
   {
     List<Type> types = new List<Type>(base.GetCustomDomainModelTypes());
     types.Add(typeof(MyAddRule));
     // If you add more rules, list them here.
     return types.ToArray();
   }
 }
}

注释

规则的代码应仅更改应用商店中元素的状态;也就是说,规则应仅更改模型元素、关系、形状、连接线、关系图或其属性。 如果要将更改传播到存储区外部的资源,请定义 Store 事件。 有关详细信息,请参阅 事件处理程序在模型外传播更改

定义规则

  1. 将规则定义为以属性为前缀的 RuleOn 类。 该属性将规则与某个域类、关系或关系图元素相关联。 该规则将应用于此类的每个实例,这些实例可能是抽象的。

  2. 通过将规则添加到域模型类中返回的 GetCustomDomainModelTypes() 集来注册该规则。

  3. 从其中一个抽象规则类派生规则类,并编写执行方法的代码。

    以下部分更详细地介绍了这些步骤。

在域类上定义规则

  • 在自定义代码文件中,定义一个类,并为其 RuleOnAttribute 添加属性前缀:

    [RuleOn(typeof(ExampleElement),
         // Usual value - but required, because it is not the default:
         FireTime = TimeToFire.TopLevelCommit)]
    class MyRule ...
    
    
  • 第一个参数中的主题类型可以是领域类、领域关系、形状、连接器或图表。 通常,将规则应用于域类和关系。

    FireTime通常是TopLevelCommit。 这可确保仅在完成事务中的所有主要更改后才执行该规则。 替代选项包括 Inline,在更改后立即执行规则,以及 LocalCommit,该规则在当前事务结束时执行(可能不是最外层)。 还可以设置规则的优先级以影响其在队列中的排序,但这是实现所需结果的不可靠方法。

  • 可以将抽象类指定为主题类型。

  • 该规则适用于主题类的所有实例。

  • FireTime默认值为 TimeToFire.TopLevelCommit。 这会导致在提交最外部事务时执行规则。 替代方法是 TimeToFire.Inline。 这会导致在触发事件后迅速执行规则。

注册该规则的步骤

  • 将您的规则类添加到在域模型中由 GetCustomDomainModelTypes 返回的类型列表中:

    public partial class ExampleDomainModel
     {
       protected override Type[] GetCustomDomainModelTypes()
       {
         List<Type> types = new List<Type>(base.GetCustomDomainModelTypes());
         types.Add(typeof(MyAddRule));
         // If you add more rules, list them here.
         return types.ToArray();
       }
     }
    
    
  • 如果不确定域模型类的名称,请查看文件 Dsl\GeneratedCode\DomainModel.cs

  • 在 DSL 项目中的自定义代码文件中编写此代码。

编写规则的代码

  • 从以下基类之一派生规则类:

    基类 Trigger
    AddRule 添加了元素、链接或形状。

    除新元素外,使用此属性可检测新关系。
    ChangeRule 域属性值已更改。 方法参数提供旧值和新值。

    对于形状,当内置 AbsoluteBounds 属性发生更改或形状被移动时,将触发此规则。

    在许多情况下,在属性处理程序中重写 OnValueChangedOnValueChanging 会更为方便。 这些方法在更改前后立即调用。 相比之下,规则通常在交易结束时执行。 有关详细信息,请参阅 域属性值更改处理程序注意: 创建或删除链接时,不会触发此规则。 而是为域关系编写一个 AddRule 和一个 DeleteRule
    DeletingRule 在即将删除元素或链接时触发。 属性 ModelElement.IsDeleting 在整个事务期间保持为 true。
    DeleteRule 在删除元素或链接时执行。 在执行所有其他规则(包括 DeletingRules)后,执行该规则。 ModelElement.IsDeleting 为 false,ModelElement.IsDeleted 为 true。 若要允许后续撤消,该元素实际上不会从内存中删除,而是从 Store.ElementDirectory 中删除该元素。
    MoveRule 元素从一个存储分区移动到另一个存储分区。

    (请注意,这与形状的图形位置无关。
    RolePlayerChangeRule 此规则仅适用于域关系。 如果将模型元素显式分配给链接的任一端,则会触发它。
    RolePlayerPositionChangeRule 在使用链接上的 MoveBefore 或 MoveToIndex 方法更改到或从元素的链接顺序时,触发事件。
    TransactionBeginningRule 在创建事务时执行。
    TransactionCommittingRule 在事务即将提交时执行。
    TransactionRollingBackRule 在事务即将回滚时执行。
  • 每个类都有可以被重写的方法。 在您的类中键入 override 以发现它。 此方法的参数标识正在更改的元素。

    请注意有关规则的以下几点:

  1. 事务变更集可能会触发许多规则。 通常,在提交最外部的事务时执行规则。 它们按未指定的顺序执行。

  2. 规则始终在事务内执行。 因此,无需创建新的事务才能进行更改。

  3. 在事务回滚时或执行撤消或重做操作时,不会执行规则。 这些作会将应用商店的所有内容重置为其以前的状态。 因此,如果规则更改应用商店外部的任何内容的状态,它可能不会与应用商店内容保持同步。 若要更新应用商店外部的状态,最好使用事件。 有关详细信息,请参阅 事件处理程序在模型外传播更改

  4. 从文件加载模型时,会执行一些规则。 若要确定加载或保存的过程是否正在进行,请使用 store.TransactionManager.CurrentTransaction.IsSerializing

  5. 如果规则的代码创建更多规则触发器,它们将添加到触发列表的末尾,并在事务完成之前执行。 DeletedRules 在所有其他规则之后执行。 一个规则可以在事务中运行多次,每次更改一次。

  6. 如果想要在规则之间传递信息,可以将信息存储在TransactionContext。 仅仅是在事务过程中维护的字典。 事务结束时会进行释放。 每个规则中的事件参数都提供对它的访问权限。 请记住,规则不会按可预测的顺序执行。

  7. 考虑其他替代方法后使用规则。 例如,如果要在值更改时更新属性,请考虑使用计算属性。 如果要限制形状的大小或位置,请使用一个 BoundsRule。 如果要响应属性值中的更改,请将处理程序 OnValueChanged 添加到该属性。 有关详细信息,请参阅 “响应和传播更改”。

Example

以下示例在实例化域关系以链接两个元素时更新属性。 规则不仅在用户创建关系图上的链接时触发,还会在程序代码创建链接时触发。

若要测试此示例,请使用任务流解决方案模板创建 DSL,并在 Dsl 项目中的文件中插入以下代码。 生成并运行解决方案,并在调试项目中打开示例文件。 在注释形状与流程元素之间绘制评论链接。 批注中的文本将更改,以报告已连接到的最近元素。

在实践中,通常会为每个 AddRule 编写 DeleteRule。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.Modeling;

namespace Company.TaskRuleExample
{

  [RuleOn(typeof(CommentReferencesSubjects))]
  public class RoleRule : AddRule
  {

    public override void ElementAdded(ElementAddedEventArgs e)
    {
      base.ElementAdded(e);
      CommentReferencesSubjects link = e.ModelElement as CommentReferencesSubjects;
      Comment comment = link.Comment;
      FlowElement subject = link.Subject;
      Transaction current = link.Store.TransactionManager.CurrentTransaction;
      // Don't want to run when we're just loading from file:
      if (current.IsSerializing) return;
      comment.Text = "Flow has " + subject.FlowTo.Count + " outgoing connections";
    }

  }

  public partial class TaskRuleExampleDomainModel
  {
    protected override Type[] GetCustomDomainModelTypes()
    {
      List<Type> types = new List<Type>(base.GetCustomDomainModelTypes());
      types.Add(typeof(RoleRule));
      return types.ToArray();
    }
  }

}