규칙으로 모델 내부의 변경 내용 전파

VMSDK(Visualization and Modeling SDK)에서 한 요소에서 다른 요소로 변경 내용을 전파하는 저장소 규칙을 만들 수 있습니다. 저장소에 있는 요소가 변경되면 규칙은 일반적으로 가장 바깥쪽 트랜잭션이 커밋될 때 실행되도록 예약됩니다. 요소 추가나 삭제 같은 다양한 종류의 이벤트에 대한 다양한 유형의 규칙이 존재합니다. 특정 형식의 요소, 도형 또는 다이어그램에 규칙을 추가할 수 있습니다. 규칙은 많은 기본 제공 기능을 정의합니다. 예를 들어 규칙은 모델이 변경될 때 다이어그램이 업데이트되게 합니다. 자체 규칙을 추가하여 도메인별 언어를 사용자 지정할 수 있습니다.

저장소 규칙은 저장소 내에서 변경 내용(모델 요소, 관계, 도형 또는 연결선 및 관련 도메인 속성의 변경 내용)을 전파하는 데 특히 유용합니다. 사용자가 실행 취소 또는 다시 실행 명령을 호출할 때는 규칙이 실행되지 않습니다. 대신 트랜잭션 관리자가 저장소 콘텐츠가 올바른 상태로 복원되었는지를 확인합니다. 변경 내용을 저장소 외부의 리소스에 전파하려면 저장소 이벤트를 사용해야 합니다. 자세한 내용은 이벤트 처리기로 모델 외부의 변경 내용 전파를 참조하세요.

예를 들어 사용자(또는 코드)가 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();
   }
 }
}

참고 항목

규칙의 코드는 저장소 내의 요소에 대한 상태만 변경해야 합니다. 즉 규칙은 모델 요소, 관계, 도형, 연결선, 다이어그램 또는 관련 속성만 변경해야 합니다. 변경 내용을 저장소 외부의 리소스에 전파하려면 저장소 이벤트를 정의해야 합니다. 자세한 내용은 이벤트 처리기로 모델 외부의 변경 내용 전파를 참조하세요.

규칙을 정의하려면

  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 프로젝트의 사용자 지정 코드 파일에 작성합니다.

규칙의 코드를 작성하려면

  • 다음 기본 클래스 중 하나에서 규칙 클래스를 파생합니다.

    기본 클래스 트리거
    AddRule 요소, 링크 또는 도형이 추가됩니다.

    이를 새 요소와 함께 사용하여 새 관계를 검색하세요.
    ChangeRule 도메인 속성 값이 변경되었습니다. 메서드 인수는 이전 값과 새 값을 제공합니다.

    도형의 경우 이 규칙은 (도형을 이동했을 때) 기본 제공 AbsoluteBounds 속성이 변경되면 트리거됩니다.

    대부분의 경우 속성 처리기에서 OnValueChanged 또는 OnValueChanging을 재정의하는 방법이 더 편리합니다. 이러한 메서드는 변경 전후에 즉시 호출됩니다. 하지만 규칙은 대부분 트랜잭션 끝에서 실행됩니다. 자세한 내용은 도메인 속성 값 변경 처리기를 참조하세요. 참고: 이 규칙은 링크를 만들거나 삭제할 때는 트리거되지 않습니다. 대신 도메인 관계에 대해 AddRuleDeleteRule을 작성하세요.
    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 핸들러를 속성에 추가하세요. 자세한 내용은 변경 내용에 대한 응답 및 전파를 참조하세요.

예시

다음 예에서는 두 요소를 연결하기 위해 도메인 관계가 인스턴스화될 때 속성을 업데이트 합니다. 규칙은 사용자가 다이어그램에서 링크를 만들 때 뿐만 아니라 프로그램 코드에서 링크를 만들 때에도 트리거됩니다.

이 예제를 테스트하려면 작업 흐름 솔루션 템플릿을 사용하여 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();
    }
  }

}