다음을 통해 공유


프로그램 코드에서 모델 탐색 및 업데이트

모델 요소를 만들고 삭제하고, 해당 속성을 설정하고, 요소 간에 링크를 만들고 삭제하는 코드를 작성할 수 있습니다. 모든 변경 내용은 트랜잭션 내에서 수행해야 합니다. 다이어그램에서 요소를 보면 트랜잭션이 끝날 때 다이어그램이 자동으로 "수정"됩니다.

예제 DSL 정의

다음은 이 항목의 예제에 대한 DslDefinition.dsl의 주요 부분입니다.

DSL Definition diagram - family tree model

다음 모델은 이 DSL의 인스턴스입니다.

Tudor Family Tree Model

참조 및 네임스페이스

이 항목의 코드를 실행하려면 다음을 참조해야 합니다.

Microsoft.VisualStudio.Modeling.Sdk.11.0.dll

코드는 다음 네임스페이스를 사용합니다.

using Microsoft.VisualStudio.Modeling;

또한 DSL이 정의된 것과 다른 프로젝트에서 코드를 작성하는 경우 Dsl 프로젝트에서 빌드된 어셈블리를 가져와야 합니다.

속성

DSL 정의에서 정의하는 도메인 속성은 프로그램 코드에서 액세스할 수 있는 속성이 됩니다.

Person henry = ...;

if (henry.BirthDate < 1500) ...

if (henry.Name.EndsWith("VIII")) ...

속성을 설정하려면 트랜잭션 내에서 설정해야 합니다.

henry.Name = "Henry VIII";

DSL 정의에서 속성의 종류계산됨인 경우 설정할 수 없습니다. 자세한 내용은 계산된 스토리지 속성 및 사용자 지정 스토리지 속성을 참조하세요.

관계

DSL 정의에서 정의하는 도메인 관계는 관계의 각 끝에서 클래스에 하나씩 속성 쌍이 됩니다. 속성 이름은 DslDefinition 다이어그램에 관계의 각 측면에 있는 역할의 레이블로 표시됩니다. 역할의 다중성에 따라 속성의 형식은 관계의 다른 쪽 끝에 있는 클래스 또는 해당 클래스의 컬렉션입니다.

foreach (Person child in henry.Children) { ... }

FamilyTreeModel ftree = henry.FamilyTreeModel;

관계의 반대쪽 끝에 있는 속성은 항상 상호적입니다. 링크를 만들거나 삭제하면 두 요소의 역할 속성이 업데이트됩니다. 다음 식(System.Linq의 확장명을 사용)은 예제의 ParentsHaveChildren 관계에 대해 항상 true입니다.

(Person p) => p.Children.All(child => child.Parents.Contains(p))

&& p.Parents.All(parent => parent.Children.Contains(p));

ElementLinks. 관계는 도메인 관계 유형의 인스턴스인 “링크”라는 모델 요소로도 표시됩니다. 링크에는 항상 하나의 원본 요소와 하나의 대상 요소가 있습니다. 원본 요소와 대상 요소는 같을 수 있습니다.

링크 및 해당 속성에 액세스할 수 있습니다.

ParentsHaveChildren link = ParentsHaveChildren.GetLink(henry, edward);

// This is now true:

link == null || link.Parent == henry && link.Child == edward

기본적으로 두 개 이상의 관계 인스턴스가 모델 요소 쌍을 연결할 수 없습니다. 그러나 DSL 정의에서 관계에 대한 Allow Duplicates 플래그가 true이면 둘 이상의 링크가 있을 수 있으며 GetLinks를 사용해야 합니다.

foreach (ParentsHaveChildren link in ParentsHaveChildren.GetLinks(henry, edward)) { ... }

링크에 액세스하는 다른 방법도 있습니다. 예시:

foreach (ParentsHaveChildren link in ParentsHaveChildren.GetLinksToChildren(henry)) { ... }

숨겨진 역할. DSL 정의에서 Is Property Generated가 특정 역할에 대해 false이면 해당 역할에 해당하는 속성이 생성되지 않습니다. 하지만 여전히 링크에 액세스하고 관계의 메서드를 사용하여 링크를 트래버스할 수 있습니다.

foreach (Person p in ParentsHaveChildren.GetChildren(henry)) { ... }

가장 자주 사용되는 예는 모델 요소를 다이어그램에 표시하는 도형에 연결하는 PresentationViewsSubject 관계입니다.

PresentationViewsSubject.GetPresentation(henry)[0] as PersonShape

요소 디렉터리

요소 디렉터리를 사용하여 저장소의 모든 요소에 액세스할 수 있습니다.

store.ElementDirectory.AllElements

다음과 같은 요소를 찾는 메서드도 있습니다.

store.ElementDirectory.FindElements(Person.DomainClassId);

store.ElementDirectory.GetElement(elementId);

클래스 정보에 액세스

클래스, 관계 및 DSL 정의의 다른 측면에 대한 정보를 얻을 수 있습니다. 예시:

DomainClassInfo personClass = henry.GetDomainClass();

DomainPropertyInfo birthProperty =

personClass.FindDomainProperty("BirthDate")

DomainRelationshipInfo relationship =

link.GetDomainRelationship();

DomainRoleInfo sourceRole = relationship.DomainRole[0];

모델 요소의 상위 클래스는 다음과 같습니다.

  • ModelElement - 모든 요소 및 관계가 ModelElement입니다.

  • ElementLink - 모든 관계가 ElementLink입니다.

트랜잭션 내에서 변경 수행

프로그램 코드가 저장소의 무언가를 변경할 때는 항상 트랜잭션 내에서 변경해야 합니다. 이는 모든 모델 요소, 관계, 도형, 다이어그램 및 해당 속성에 적용됩니다. 자세한 내용은 Transaction를 참조하세요.

트랜잭션을 관리하는 가장 편리한 방법은 try...catch 문으로 묶인 using 문을 사용하는 것입니다.

Store store; ...
try
{
  using (Transaction transaction =
    store.TransactionManager.BeginTransaction("update model"))
    // Outermost transaction must always have a name.
  {
    // Make several changes in Store:
    Person p = new Person(store);
    p.FamilyTreeModel = familyTree;
    p.Name = "Edward VI";
    // end of changes to Store

    transaction.Commit(); // Don't forget this!
  } // transaction disposed here
}
catch (Exception ex)
{
  // If an exception occurs, the Store will be
  // rolled back to its previous state.
}

한 트랜잭션 내에서 원하는 만큼 변경할 수 있습니다. 활성 트랜잭션 내에서 새 트랜잭션을 열 수 있습니다.

변경 내용을 영구적으로 만들려면 트랜잭션이 삭제되기 전에 트랜잭션을 Commit해야 합니다. 트랜잭션 내에서 포착되지 않은 예외가 발생하면 저장소가 변경 전 상태로 다시 설정됩니다.

모델 요소 만들기

이 예제는 기존 모델에 요소를 추가합니다.

FamilyTreeModel familyTree = ...; // The root of the model.
using (Transaction t =
    familyTree.Store.TransactionManager
    .BeginTransaction("update model"))
{
  // Create a new model element
  // in the same partition as the model root:
  Person edward = new Person(familyTree.Partition);
  // Set its embedding relationship:
  edward.FamilyTreeModel = familyTree;
          // same as: familyTree.People.Add(edward);
  // Set its properties:
  edward.Name = "Edward VII";
  t.Commit(); // Don't forget this!
}

이 예제에서는 요소를 만드는 데 필요한 다음과 같은 핵심 사항을 보여 줍니다.

  • 저장소의 특정 파티션에 새 요소를 만듭니다. 모델 요소 및 관계의 경우 도형을 제외하고 일반적으로 기본 파티션입니다.

  • 이를 포함 관계의 대상으로 지정합니다. 이 예제의 DslDefinition에서 각 Person은 포함 관계 FamilyTreeHasPeople의 대상이어야 합니다. 이를 위해 Person 개체의 FamilyTreeModel 역할 속성을 설정하거나 FamilyTreeModel 개체의 People 역할 속성에 Person을 추가할 수 있습니다.

  • 새 요소의 속성, 특히 DslDefinition에서 IsName이 true인 속성을 설정합니다. 이 플래그는 요소를 소유자 내에서 고유하게 식별하는 데 사용되는 속성을 표시합니다. 이 경우 Name 속성에는 해당 플래그가 있습니다.

  • 이 DSL의 DSL 정의가 저장소에 로드되어 있어야 합니다. 메뉴 명령과 같은 확장을 작성하는 경우 일반적으로 이미 true입니다. 다른 경우에는 저장소에 모델을 명시적으로 로드하거나 ModelBus를 사용하여 로드할 수 있습니다. 자세한 내용은 방법: 프로그램 코드로 파일에서 모델 열기를 참조하세요.

    이러한 방식으로 요소를 만들면 DSL에 다이어그램이 있는 경우 도형이 자동으로 만들어집니다. 자동으로 할당된 위치에 표시되면 기본 모양, 색 및 기타 기능을 갖습니다. 연결된 도형이 표시되는 위치와 방법을 제어하려면 요소 및 해당 도형 만들기를 참조하세요.

예제 DSL 정의에는 두 개의 관계가 정의되어 있습니다. 각 관계는 관계의 각 끝에서 클래스의 “역할 속성”을 정의합니다.

관계의 인스턴스를 만들 수 있는 세 가지 방법이 있습니다. 이러한 세 가지 방법은 모두 동일한 효과를 갖습니다.

  • 원본 역할 수행자의 속성을 설정합니다. 예시:

    • familyTree.People.Add(edward);

    • edward.Parents.Add(henry);

  • 대상 역할 수행자의 속성을 설정합니다. 예시:

    • edward.familyTreeModel = familyTree;

      이 역할은 다중성이 1..1이므로 값을 할당합니다.

    • henry.Children.Add(edward);

      이 역할은 다중성이 0..*이므로 컬렉션에 추가합니다.

  • 관계의 인스턴스를 명시적으로 생성합니다. 예시:

    • FamilyTreeHasPeople edwardLink = new FamilyTreeHasPeople(familyTreeModel, edward);

    • ParentsHaveChildren edwardHenryLink = new ParentsHaveChildren(henry, edward);

    마지막 방법은 관계 자체에 대한 속성을 설정하려는 경우에 유용합니다.

    이러한 방식으로 요소를 만들면 다이어그램에 커넥터가 자동으로 만들어지지만 요소는 기본 모양, 색 및 기타 기능을 갖습니다. 연결된 커넥터를 만드는 방법을 제어하려면 요소 및 해당 도형 만들기를 참조하세요.

요소 삭제

Delete()를 호출하여 요소를 삭제합니다.

henry.Delete();

이 작업은 다음 항목도 삭제합니다.

  • 요소와의 관계 링크. 예를 들어 edward.Parents는 더 이상 henry를 포함하지 않습니다.

  • PropagatesDelete 플래그가 true인 역할의 요소. 예를 들어 요소를 표시하는 도형이 삭제됩니다.

기본적으로 모든 포함 관계는 대상 역할에서 PropagatesDelete가 true입니다. henry를 삭제하면 familyTree가 삭제되지 않지만 familyTree.Delete()는 모든 Persons를 삭제합니다.

기본적으로 PropagatesDelete는 참조 관계의 역할에 대해 true가 아닙니다.

개체를 삭제할 때 삭제 규칙이 특정 전파를 생략하도록 할 수 있습니다. 이는 한 요소를 다른 요소로 대체하는 경우에 유용합니다. 삭제를 전파하지 않아야 하는 역할의 GUID를 하나 이상 제공합니다. GUID는 관계 클래스에서 가져올 수 있습니다.

henry.Delete(ParentsHaveChildren.SourceDomainRoleId);

(이 특정 예제는 PropagatesDeleteParentsHaveChildren 관계의 역할에 대해 false이기 때문에 아무런 영향을 미치지 않습니다.)

경우에 따라 요소 또는 전파에 의해 삭제되는 요소에 잠금이 존재하여 삭제가 방지됩니다. element.CanDelete()를 사용하여 해당 요소를 삭제할 수 있는지 여부를 확인할 수 있습니다.

역할 속성에서 요소를 제거하여 관계 링크를 삭제할 수 있습니다.

henry.Children.Remove(edward); // or:

edward.Parents.Remove(henry); // or:

링크를 명시적으로 삭제할 수도 있습니다.

edwardHenryLink.Delete();

이러한 세 가지 방법은 모두 동일한 효과를 갖습니다. 그 중 하나만 사용해야 합니다.

역할에 0..1 또는 1..1 다중성이 있는 경우 null 또는 다른 값으로 설정할 수 있습니다.

edward.FamilyTreeModel = null; // 또는

edward.FamilyTreeModel = anotherFamilyTree;

관계의 링크 순서 다시 정렬

특정 모델 요소에서 원본 또는 대상으로 지정된 특정 관계의 링크에는 특정 시퀀스가 있습니다. 추가된 순서대로 표시됩니다. 예를 들어 다음 문은 항상 동일한 순서로 자식을 생성합니다.

foreach (Person child in henry.Children) ...

링크 순서를 변경할 수 있습니다.

ParentsHaveChildren link = GetLink(henry,edward);

ParentsHaveChildren nextLink = GetLink(henry, elizabeth);

DomainRoleInfo role =

link.GetDomainRelationship().DomainRoles[0];

link.MoveBefore(role, nextLink);

잠금

잠금으로 인해 변경이 방지될 수 있습니다. 잠금은 개별 요소, 파티션 및 저장소에서 설정할 수 있습니다. 이러한 수준에 원하는 변경의 종류를 방지하는 잠금이 있는 경우 변경을 시도할 때 예외가 throw될 수 있습니다. 네임스페이스 Microsoft.VisualStudio.Modeling.Immutability에 정의된 확장 메서드인 element.GetLocks()를 사용하여 잠금이 설정되었는지 여부를 검색할 수 있습니다.

자세한 내용은 잠금 정책을 정의하여 읽기 전용 세그먼트 만들기를 참조하세요.

복사 및 붙여넣기

요소 또는 요소 그룹을 IDataObject에 복사할 수 있습니다.

Person person = personShape.ModelElement as Person;
Person adopter = adopterShape.ModelElement as Person;
IDataObject data = new DataObject();
personShape.Diagram.ElementOperations
      .Copy(data, person.Children.ToList<ModelElement>());

요소는 직렬화된 요소 그룹으로 저장됩니다.

IDataObject의 요소를 모델로 병합할 수 있습니다.

using (Transaction t = targetDiagram.Store.
        TransactionManager.BeginTransaction("paste"))
{
  adopterShape.Diagram.ElementOperations.Merge(adopter, data);
}

Merge ()PresentationElement 또는 ModelElement를 허용할 수 있습니다. PresentationElement를 지정하는 경우 대상 다이어그램의 위치를 세 번째 매개 변수로 지정할 수도 있습니다.

다이어그램 탐색 및 업데이트

DSL에서 Person 또는 Song과 같은 개념을 나타내는 도메인 모델 요소는 다이어그램에 표시되는 내용을 나타내는 도형 요소와 별개입니다. 도메인 모델 요소는 개념의 중요한 속성 및 관계를 저장합니다. 도형 요소는 다이어그램에 있는 개체 보기의 크기, 위치 및 색과 해당 구성 요소 부분의 레이아웃을 저장합니다.

프레젠테이션 요소

Class diagram of base shape and element types

DSL 정의에서 사용자가 지정하는 각 요소는 다음 표준 클래스 중 하나에서 파생된 클래스를 만듭니다.

요소의 종류 기본 클래스
도메인 클래스 ModelElement
도메인 관계 ElementLink
도형 NodeShape
커넥터 BinaryLinkShape
다이어그램 Diagram

다이어그램의 요소는 일반적으로 모델 요소를 나타냅니다. 항상 그렇지는 않지만 일반적으로 NodeShape는 도메인 클래스 인스턴스를 나타내고 BinaryLinkShape는 도메인 관계 인스턴스를 나타냅니다. PresentationViewsSubject 관계는 노드 또는 링크 도형을 이러한 요소가 나타내는 모델 요소에 연결합니다.

모든 노드 또는 링크 도형은 하나의 다이어그램에 속합니다. 이진 링크 도형은 두 개의 노드 도형을 연결합니다.

도형에는 두 개의 자식 도형 집합이 있을 수 있습니다. NestedChildShapes 집합의 도형은 부모의 경계 상자로 제한됩니다. RelativeChildShapes 목록의 도형은 레이블 또는 포트와 같이 부모 범위 외부 또는 부분적으로 외부에 나타날 수 있습니다. 다이어그램에는 RelativeChildShapes도 없고 Parent도 없습니다.

도형과 요소 간 탐색

도메인 모델 요소와 도형 요소는 PresentationViewsSubject 관계와 관련이 있습니다.

// using Microsoft.VisualStudio.Modeling;
// using Microsoft.VisualStudio.Modeling.Diagrams;
// using System.Linq;
Person henry = ...;
PersonShape henryShape =
  PresentationViewsSubject.GetPresentation(henry)
    .FirstOrDefault() as PersonShape;

동일한 관계는 다이어그램의 커넥터에 관계를 연결합니다.

Descendants link = Descendants.GetLink(henry, edward);
DescendantConnector dc =
   PresentationViewsSubject.GetPresentation(link)
     .FirstOrDefault() as DescendantConnector;
// dc.FromShape == henryShape && dc.ToShape == edwardShape

또한 이 관계는 모델의 루트를 다이어그램에 연결합니다.

FamilyTreeDiagram diagram =
   PresentationViewsSubject.GetPresentation(familyTree)
      .FirstOrDefault() as FamilyTreeDiagram;

모양이 나타내는 모델 요소를 가져오려면 다음을 사용합니다.

henryShape.ModelElement as Person

diagram.ModelElement as FamilyTreeModel

일반적으로 다이어그램에서 도형과 커넥터 사이를 탐색하는 것은 바람직하지 않습니다. 다이어그램의 모양에 대한 작업이 필요한 경우에만 도형과 커넥터 간에 이동하여 모델의 관계를 탐색하는 것이 좋습니다. 이러한 방법은 각 끝에서 커넥터를 도형에 연결합니다.

personShape.FromRoleLinkShapes, personShape.ToRoleLinkShapes

connector.FromShape, connector.ToShape

많은 도형이 부모 도형과 하나 이상의 자식 레이어로 구성되는 복합 도형입니다. 다른 도형을 기준으로 배치된 도형은 “자식 요소”라고 합니다. 부모 도형이 이동하면 자식 도형도 함께 이동합니다.

“상대 자식 요소”는 부모 도형의 경계 상자 외부에 나타날 수 있습니다. “중첩” 자식 요소는 엄격하게 부모의 범위 내부에 나타납니다.

다이어그램에서 최상위 집합의 도형을 가져오려면 다음을 사용합니다.

Diagram.NestedChildShapes

도형 및 커넥터의 상위 클래스는 다음과 같습니다.

ModelElement

-- PresentationElement

-- ShapeElement

----- NodeShape

------- Diagram

------- YourShape

----- LinkShape

------- BinaryLinkShape

--------- YourConnector

도형 및 커넥터의 속성

대부분의 경우 도형을 명시적으로 변경할 필요가 없습니다. 모델 요소를 변경한 경우 "수정" 규칙이 도형 및 커넥터를 업데이트합니다. 자세한 내용은 변경 내용에 대한 응답 및 전파를 참조하세요.

그러나 모델 요소와 독립적인 속성에서는 도형을 명시적으로 변경하는 것이 유용합니다. 예를 들어 다음과 같은 속성을 변경할 수 있습니다.

  • Size - 도형의 높이와 너비를 결정합니다.

  • Location - 부모 도형 또는 다이어그램을 기준으로 하는 위치

  • StyleSet - 도형 또는 커넥터를 그리는 데 사용되는 펜 및 브러시 집합

  • Hide - 도형을 보이지 않게 만듭니다.

  • Show - 도형을 Hide() 뒤에 표시합니다.

요소 및 해당 도형 만들기

요소를 만들어 포함 관계 트리에 연결하면 도형이 자동으로 만들어지고 요소와 연결됩니다. 이 작업은 트랜잭션이 끝날 때 실행되는 "수정" 규칙에 의해 수행됩니다. 그러나 도형은 자동으로 할당된 위치에 표시되고 해당 모양, 색 및 기타 기능에는 기본값이 있습니다. 도형을 만드는 방법을 제어하기 위해 병합 함수를 사용할 수 있습니다. 먼저 ElementGroup에 추가하려는 요소를 추가한 다음 그룹을 다이어그램에 병합해야 합니다.

이 방법:

  • 속성을 요소 이름으로 할당한 경우 이름을 설정합니다.

  • DSL 정의에서 지정한 요소 병합 지시문을 준수합니다.

이 예제에서는 사용자가 다이어그램을 두 번 클릭할 때 마우스 위치에 도형을 만듭니다. 이 샘플에 대한 DSL 정의에서 ExampleShapeFillColor 속성이 표시되었습니다.

using Microsoft.VisualStudio.Modeling;
using Microsoft.VisualStudio.Modeling.Diagrams;
partial class MyDiagram
{
  public override void OnDoubleClick(DiagramPointEventArgs e)
  {
    base.OnDoubleClick(e);

    using (Transaction t = this.Store.TransactionManager
        .BeginTransaction("double click"))
    {
      ExampleElement element = new ExampleElement(this.Store);
      ElementGroup group = new ElementGroup(element);

      { // To use a shape of a default size and color, omit this block.
        ExampleShape shape = new ExampleShape(this.Partition);
        shape.ModelElement = element;
        shape.AbsoluteBounds = new RectangleD(0, 0, 1.5, 1.0);
        shape.FillColor = System.Drawing.Color.Azure;
        group.Add(shape);
      }

      this.ElementOperations.MergeElementGroupPrototype(
        this,
        group.CreatePrototype(),
        PointD.ToPointF(e.MousePosition));
      t.Commit();
    }
  }
}

도형을 두 개 이상 제공하는 경우 AbsoluteBounds를 사용하여 상대 위치를 설정합니다.

이 메서드를 사용하여 커넥터의 색 및 기타 표시된 속성을 설정할 수도 있습니다.

트랜잭션 사용

도형, 커넥터 및 다이어그램은 ModelElement의 하위 형식이며 저장소에 있습니다. 따라서 트랜잭션 내에서만 변경해야 합니다. 자세한 내용은 방법: 트랜잭션을 사용하여 모델 업데이트를 참조하세요.

문서 보기 및 문서 데이터

Class diagram of standard diagram types

저장소 파티션

모델이 로드되면 해당 다이어그램이 동시에 로드됩니다. 일반적으로 모델은 Store.DefaultPartition에 로드되고 다이어그램 콘텐츠는 다른 파티션에 로드됩니다. 일반적으로 각 파티션의 콘텐츠가 로드되어 별도의 파일에 저장됩니다.