共用方式為


自訂檔案儲存體和 XML 序列化

當使用者在 Visual Studio 中儲存特定領域語言 (DSL) 的執行個體或模型時,就會建立或更新 XML 檔案。 檔案可以重新載入以在市集中重新建立模型。

您可以藉由在 DSL 總管中的 XML 序列化行為下調整設定,自訂序列化配置。 每個網域類別、屬性和關聯性都有 Xml 串行化行為下的節點。 關聯性位於其來源類別底下。 也有對應至圖形、連接器和圖表類別的節點。

您也可以撰寫程式碼以進行更進階的自訂。

注意

如果您想要以特定格式儲存模型,但不需要從該表單重新載入模型,請考慮使用文字範本從模型產生輸出,而不是自訂序列化配置。 如需詳細資訊,請參閱從特定領域語言產生程式碼

模型和圖表檔案

每個模型都會儲存在兩個檔案中:

  • 模型檔案的名稱如下 Model1.mydsl。 會儲存模型元素和關聯性及其屬性。 例如的.mydsl擴展名是由 DSL 定義中 Editor 節點的 FileExtension 屬性所決定。

  • 圖表檔案的名稱如下 Model1.mydsl.diagram。 會儲存圖形、連接器及其位置、色彩、線條粗細,以及圖表外觀的其他詳細資料。 如果使用者刪除檔案 .diagram ,則模型中的基本資訊不會遺失。 只有圖表配置會遺失。 開啟模型檔案時,會建立一組預設的圖形和連接器。

變更 DSL 副檔名

  1. 開啟 DSL 定義。 在 [DSL 總管] 中,按一下 [編輯器] 節點。

  2. 在 [屬性] 視窗中,編輯 FileExtension 屬性。 請勿包含擴展名的縮寫 .

  3. 在 [方案總管] 中,變更 DslPackage\ProjectItemTemplates 中兩個項目範本檔案的名稱。 這些檔案具有遵循以下格式的名稱:

    myDsl.diagram

    myDsl.myDsl

預設序列化配置

為了建立此主題的範例,使用了下列 DSL 定義。

DSL 定義圖表 - 系列樹狀結構模型

此 DSL 是用來建立在螢幕上具有下列外觀的模型。

家譜圖表、工具箱和總管

此模型已儲存,然後在 XML 文字編輯器中重新開啟:

<?xml version="1.0" encoding="utf-8"?>
<familyTreeModel xmlns:dm0="http://schemas.microsoft.com/VisualStudio/2008/DslTools/Core" dslVersion="1.0.0.0" Id="f817b728-e920-458e-bb99-98edc469d78f" xmlns="http://schemas.microsoft.com/dsltools/FamilyTree">
  <people>
    <person name="Henry VIII" birthYear="1491" deathYear="1547" age="519">
      <children>
        <personMoniker name="/f817b728-e920-458e-bb99-98edc469d78f/Elizabeth I" />
        <personMoniker name="/f817b728-e920-458e-bb99-98edc469d78f/Mary" />
      </children>
    </person>
    <person name="Elizabeth I" birthYear="1533" deathYear="1603" age="477" />
    <person name="Mary" birthYear="1515" deathYear="1558" age="495" />
  </people>
</familyTreeModel>

請注意有關序列化模型的下列重點:

  • 每個 XML 節點都具有與領域類別名稱相同的名稱,但是初始字母為小寫。 例如,familyTreeModelperson

  • Name 和 BirthYear 等領域屬性會序列化為 XML 節點中的屬性。 同樣地,屬性名稱的初始字元會轉換成小寫。

  • 每個關聯性都會序列化為在關聯性來源端內成為巢狀的 XML 節點。 節點具有與來源角色屬性相同的名稱,但是初始字元為小寫。

    例如,在 DSL 定義中,People 的角色其來源為 FamilyTree 類別。 在 XML 中,People 角色是以節點內familyTreeModel巢狀的people節點來表示。

  • 每個內嵌關聯性的目標端都會序列化為在關聯性下巢狀的節點。 例如,people 節點包含數個 person 節點。

  • 每個參考關聯性的目標端都會序列化為 Moniker,將參考編碼為目標元素。

    例如,在 person 節點下,可能會有 children 關聯性。 此節點包含 Moniker,例如:

    <personMoniker name="/f817b728-e920-458e-bb99-98edc469d78f/Elizabeth I" />
    

了解 Moniker

Moniker 是用來代表模型與圖表檔案的不同部分之間的交叉參考。 它們也會用於 檔案中 .diagram ,以參考模型檔案中的節點。 Moniker 有兩種形式:

  • 識別碼 Moniker 會引用目標元素的 GUID。 例如:

    <personShapeMoniker Id="f79734c0-3da1-4d72-9514-848fa9e75157" />
    
  • 限定索引鍵 Moniker 會以名為 Moniker 索引鍵的指定領域屬性值識別目標元素。 目標專案的Moniker前面會加上內嵌關聯性樹狀結構中其父元素的Moniker。

    下列範例取自 DSL,其中有名為 Album 的網域類別,其與名為 Song 的網域類別有內嵌關聯性:

    <albumMoniker title="/My Favorites/Jazz after Teatime" />
    <songMoniker title="/My Favorites/Jazz after Teatime/Hot tea" />
    

    如果目標類別具有定義域屬性,則會使用限定索引鍵Moniker索引鍵在 Xml 串行化行為設定為 true 的網域屬性。 在此範例中,針對領域類別 "Album" 和 "Song" 中名為 "Title" 的領域屬性設定此選項。

限定索引鍵 Moniker 比識別碼 Moniker 更容易讀取。 如果您想要讓模型檔案的 XML 成為人類可讀取的,請考慮使用合格的密鑰 Moniker。 不過,用戶可以將多個元素設定為具有相同Moniker索引鍵。 重複的索引鍵可能會導致檔案無法正確重新載入。 因此,如果您定義使用限定索引鍵 Moniker 參考的領域類別,您應該考慮防止使用者儲存具有重複 Moniker 的檔案。

設定識別碼 Moniker 所參考的領域類別

  1. 請確定 [Is Moniker Key] 是類別及其基底類別中每個領域屬性的 false

    1. 在 [DSL 總管] 中,展開 Xml Serialization Behavior\Class Data\<領域類別>\Element Data

    2. 確認每個領域屬性的 [是 Moniker 索引鍵]false

    3. 如果領域類別有基底類別,請重複該類別中的程序。

  2. 為領域類別設定 Serialize Id = true

    您可以在 [XML 序列化行為] 底下找到此屬性。

設定限定索引鍵 Moniker 所參考的領域類別

  • 為現有領域類別的領域屬性設定 [是 Moniker 索引鍵]。 屬性的類型必須是 string

    1. 在 [DSL 總管] 中,展開 Xml Serialization Behavior\Class Data\<領域類別>\Element Data,然後選取領域屬性。

    2. 在 [屬性] 視窗中,將 [Is Moniker Key] 設定為 true

  • - 或 -

    使用具名領域類別工具建立新的領域類別。

    此工具會建立名為 Name 之領域屬性的新類別。 此領域屬性的 [Is Element Name] 和 [Is Moniker Key] 屬性會初始化為 true

  • - 或 -

    建立從領域類別到具有 Moniker 索引鍵屬性的另一個類別的繼承關聯性。

避免重複 Moniker

如果您使用合格的索引鍵 Monikers,則使用者模型中的兩個元素在索引鍵屬性中可能會有相同的值。 例如,如果您的 DSL 類別 Person 具有屬性 Name,則使用者可以將兩個元素的 Name 設定為相同。 雖然模型可以儲存至檔案,但不會正確重載。

有數種方法可協助避免這種情況:

  • 為索引鍵領域屬性設定 [Is Element Name] = true。 選取 DSL 定義圖表上的領域屬性,然後在 [屬性] 視窗中設定值。

    當使用者建立類別的新執行個體時,這個值會導致領域屬性自動獲指派不同的值。 預設行為會將數字新增至類別名稱的結尾。 這不會防止使用者將名稱變更為重複名稱,但在儲存模型之前,使用者未設定值的情況會有所説明。

  • 啟用 DSL 的驗證。 在 [DSL 總管] 中,選取 Editor\Validation,然後將 [Uses...] 屬性設定為 true

    有自動產生的驗證方法會檢查模棱兩可。 方法位於 Load 驗證類別中。 這可確保使用者會被警告,可能無法重新開啟檔案。

    如需詳細資訊,請參閱特定領域語言中的驗證

Moniker 路徑和限定詞

限定索引鍵 Moniker 結尾為 Moniker 索引鍵,並在內嵌樹狀結構中加上其父系的 Moniker 前置詞。 例如,如果 Album 的 Moniker 為:

<albumMoniker title="/My Favorites/Jazz after Teatime" />

然後該 Album 中的其中一首 Song 是:

<songMoniker title="/My Favorites/Jazz after Teatime/Hot tea" />

不過,如果系統會改為以識別碼參考 Album,則 Moniker 會如下所示:

<albumMoniker Id="77472c3a-9bf9-4085-976a-d97a4745237c" />
<songMoniker title="/77472c3a-9bf9-4085-976a-d97a4745237c/Hot tea" />

請注意,因為 GUID 是唯一的,所以永遠不會加上其父系的 Moniker 前置詞。

如果您知道特定領域屬性在模型中一律會有唯一值,您可以針對該屬性將 [Is Moniker Qualifier] 設定為 true。 這會導致它當做限定符使用,而不要使用父系的Moniker。 例如,如果您同時 為 Album 類別的 Title 網域屬性設定 Is Moniker 限定 符和 Is Moniker Key ,則模型的名稱或標識符不會用於 Album 的 Moniker 及其內嵌子系:

<albumMoniker name="Jazz after Teatime" />
<songMoniker title="/Jazz after Teatime/Hot tea" />

自訂 XML 的結構

若要進行下列自訂,請展開 [DSL 總管] 中的 [Xml Serialization Behavior] 節點。 在領域類別下,展開 [Element Data] 節點,以查看來源為此類別的屬性和關聯性清單。 選取關聯性,並在 [屬性] 視窗中調整其選項。

  • 將 [Omit Element] 設定為 true,以省略來源角色節點,僅留下目標元素的清單。 如果來源和目標類別之間有多個關聯性,則不應該設定此選項。

    <familyTreeModel ...>
      <!-- The following node is omitted by using Omit Element: -->
      <!-- <people> -->
        <person name="Henry VIII" .../>
        <person name="Elizabeth I" .../>
      <!-- </people> -->
    </familyTreeModel>
    
  • 設定 [Use Full Form],將目標節點內嵌在代表關聯性執行個體的節點中。 當您將領域屬性新增至領域關聯時,會自動設定此選項。

    <familyTreeModel ...>
      <people>
        <!-- The following node is inserted by using Use Full Form: -->
        <familyTreeModelHasPeople myRelationshipProperty="x1">
          <person name="Henry VIII" .../>
        </familyTreeModelHasPeople>
        <familyTreeModelHasPeople myRelationshipProperty="x2">
          <person name="Elizabeth I" .../>
        </familyTreeModelHasPeople>
      </people>
    </familyTreeModel>
    
  • 設定 [表示法] = [元素],將領域屬性儲存為元素,而不是儲存為屬性值。

    <person name="Elizabeth I" birthYear="1533">
      <deathYear>1603</deathYear>
    </person>
    
  • 若要變更序列化屬性和關聯性的順序,請以滑鼠右鍵按一下 [元素資料] 底下的項目,並使用 [上移] 或 [下移] 功能表命令。

使用程式碼的主要自訂

您可以取代部分或所有序列化演算法。

建議您研究 Dsl\Generated Code\Serializer.csSerializationHelper.cs 中的程式碼。

自訂特定類別的序列化

  1. 在 [Xml Serialization Behavior] 底下該類別的節點中設定 [Is Custom]

  2. 轉換所有範本、建置解決方案,並調查產生的編譯錯誤。 每個錯誤附近的註解會說明您必須提供的程式碼。

為整個模型提供您自己的序列化

  1. 覆寫 Dsl\GeneratedCode\SerializationHelper.cs 中的方法

注意

從 Visual Studio 2022 17.13 開始,預設串行化實作不再支援使用 BinaryFormatter 串行化或還原串行化自定義數據類型,因為 BinaryFormatter 的安全性風險。

如果您針對任何定義網域屬性使用自訂資料類型,則必須覆寫 類別中的 SerializationHelper 串行化方法,或實 TypeConverter 作能夠將每個自訂資料類型轉換成字串或從字串轉換的 。

雖然我們不建議您基於安全性考慮使用 BinaryFormatter ,但如果您必須維持與舊版模型回溯相容性,而使用 BinaryFormatter 串行化,您可以實 TypeConverter 作 ,以還原串行化二進位數據。 下列代碼段可作為實作此相容性的範本:

class MyCustomDataTypeConverter : TypeConverter
{
    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
    {
        return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
    }

    public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
    {
        return destinationType == typeof(string) || base.CanConvertTo(context, destinationType);
    }

    public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
    {
        if (value is string text)
        {
            // First, try to parse the string as if it were returned by MyCustomDataType.ToString().
            if (MyCustomDataType.TryParse(text, out var custom))
                return custom;

            // Fall back to trying to deserialize the old BinaryFormatter serialization format.
            var decoded = Convert.FromBase64String(text);
            using (var memory = new MemoryStream(decoded, false))
            {
                var binaryFormatter = new BinaryFormatter();
                return binaryFormatter.Deserialize(memory) as MyCustomDataType;
            }
        }

        return base.ConvertFrom(context, culture, value);
    }

    public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
    {
        if (destinationType == typeof(string) && value is MyCustomDataType custom)
            return custom.ToString();

        return base.ConvertTo(context, culture, value, destinationType);
    }
}

// ...

[TypeConverter(MyCustomDataTypeConverter)]
class MyCustomDataType
{
    // ...
}

XML 序列化行為中的選項

在 DSL 總管中,Xml 串行化行為節點包含每個網域類別、關聯性、圖形、連接器和圖表類別的子節點。 在這些節點底下,是來源為該元素的屬性和關聯性清單。 關聯性會以自己的權限和來源類別來表示。

下表摘要說明您可以在 DSL 定義的這一節中設定的選項。 在每個案例中,選取 [DSL 總管] 中的元素,然後在 [屬性] 視窗中設定選項。

XML 類別資料

這些元素可在 [DSL 總管] 的 Xml Serialization Behavior\Class Data 底下找到。

屬性 說明
Has Custom Element Schema 如果為 True,指出領域類別有自訂元素結構描述
是自訂 如果您想要撰寫這個網域類別的串行化和還原串行化程式代碼,請將值 設定為 True

建置解決方案並調查錯誤,以探索詳細指示。
領域類別 套用此類別資料節點的領域類別。 唯讀。
元素名稱 這個類別元素的 XML 節點名稱。 預設值是領域類別名稱的小寫版本。
Moniker Attribute Name Moniker 元素中用來包含參考的屬性名稱。 如果空白,則會使用索引鍵屬性或識別碼的名稱。

在此範例中,其為 「name」: <personMoniker name="/Mike Nash"/>
Moniker Element Name XML 元素名稱,用於參考此類別元素的 Moniker。

預設值是尾碼為「Moniker」的小寫版本類別名稱。 例如: personMoniker
Moniker Type Name XSD 類型名稱,替此類型元素的 Moniker 所產生。 XSD 位於 Dsl\Generated Code\*Schema.xsd
Serialize Id 如果為 True,則元素 GUID 會包含在檔案中。 如果沒有標示為Moniker Key的屬性,且DSL定義這個類別的參考關聯性,則必須將值設定為True
類型名稱 XML 類型名稱,自指定的領域類別中的 XSD 產生。
備註 與此元素相關的非正式備註

Xml Property Data

XML 屬性節點位於類別節點底下。

屬性 說明
領域屬性 套用 XML 序列化組態資料的屬性。 唯讀。
Is Moniker Key 如果值設定為 True,則會使用 屬性做為索引鍵,用來建立參考這個網域類別實例的 Moniker。
Is Moniker Qualifier 如果值設定為 True,則會使用 屬性在 Moniker 中建立限定符。 如果為 false,而且如果此網域類別的 SerializeId 不是 true,Monikers 就會由內嵌樹狀結構中父元素的 Moniker 限定。
表示法 如果值設定為 Attribute,則屬性會串行化為 xml 屬性;如果值設定為 Element,則會將其串行化為元素;如果值設定為 Ignore,則不會串行化。
Xml Name 名稱,用於 XML 屬性或代表屬性的元素。 根據預設,此值是網域屬性名稱的小寫版本。
備註 與此元素相關的非正式備註

Xml Role data

角色資料節點位於來源類別節點底下。

屬性 說明
Has Custom Moniker 如果您想要提供自己的程式碼來產生和解析周遊此關聯性的 Moniker,請將此屬性設定為 true。

如需詳細指示,請建置解決方案,然後按兩下錯誤訊息。
領域關聯 指定這些選項套用的關聯性。 唯讀。
Omit Element 若為 True,會從結構描述中省略對應到來源角色的 XML 節點。

如果來源和目標類別之間有多個關聯性,此角色節點會區分屬於兩個關聯性的連結。 因此,建議您在此案例中不要設定此選項。
Role Element Name 指定衍生自來源角色的 XML 元素名稱。 預設值是角色屬性名稱。
Use Full Form 如果為 true,則每個目標元素或 Moniker 都會包含在代表關聯性的 XML 節點中。 如果關聯性有自己的領域屬性,這應該設定為 true。