共用方式為


自訂檔案儲存和 XML 串行化

當使用者在 Visual Studio 中儲存網域特定語言(DSL)的實例或 模型時,會建立或更新 XML 檔案。 檔案可以重載以在市集中重新建立模型。

您可以透過在 DSL 檢視器中調整 XML 序列化行為 下的設定,來自定義序列化配置。 在每個網域類別、屬性和關聯性下,Xml 序列化行為 都有一個節點。 關聯性位於其來源類別之下。 還有對應於形狀、連接器和圖表類別的節點。

您也可以撰寫程式代碼以取得更進階的自定義。

注意

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

模型和圖表檔案

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

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

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

變更 DSL 檔案的副檔名

  1. 開啟 DSL 定義。 在 DSL Explorer 中,點擊 [編輯器] 節點。

  2. 在 [屬性] 視窗中,編輯 檔案副檔名 屬性。 請勿包含檔案名稱副檔名的初始標籤 .

  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 關聯性。 此節點包含名稱,例如:

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

瞭解Monikers

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

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

    <personShapeMoniker Id="f79734c0-3da1-4d72-9514-848fa9e75157" />
    
  • 合格的 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」 的網域屬性設定。

合格的金鑰識別符比標識碼識別符更容易讀取。 如果您希望模型文件的 XML 易於閱讀,請考慮使用合格的鍵名符。 不過,用戶也可以將多個元素設置為具有相同名稱鍵。 重複的索引鍵可能會導致檔案無法正確重載。 因此,如果您定義了一個使用限定索引鍵標識符進行參考的網域類別,您應該考慮防止使用者儲存具有重複標識符的檔案。

若要設定 ID Monikers 所參考的網域類別

  1. 請確保在類別及其基類中,每個網域屬性的 被設為 false 作為 Moniker Key

    1. 在 DSL Explorer 中,展開 XML 序列化行為\類別數據\<定義域類別>\元素數據

    2. 確認每個網域屬性 是Moniker金鑰false

    3. 如果網域類別有基類,請重複該類別中的程式。

  2. 設定 串行化標識碼 = true 給網域類別。

    您可以在 Xml 串行化行為中找到這個屬性。

若要設定由限定鍵標識符參考的網域類別

  • 為現有網域類別的網域屬性設定 Is Moniker Key。 屬性的類型必須 string

    1. 在 [DSL 總管] 中,展開 Xml 序列化行為\類別資料\<網域類別>\Element Data,然後選取網域屬性。

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

  • -或-

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

    此工具會建立名為 Name 之網域屬性的新類別。 Is 元素名稱Is Moniker Key 的這個網域屬性會初始化為 true

  • -或-

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

避免重複名稱

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

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

  • 對於索引鍵域屬性,將 設置為元素名稱 = true。 選取 DSL 定義圖表上的網域屬性,然後在 [屬性] 視窗中設定值。

    當使用者建立 類別的新實例時,這個值會導致網域屬性自動指派不同的值。 默認行為會將數位加入類別名稱的結尾。 這不會阻止使用者將名稱更改為重複的名稱,但在使用者未在儲存模型之前設定值的情況下,這功能會有所幫助。

  • 啟用 DSL 的驗證。 在 [DSL 總管] 中,選取 [編輯器\驗證],並將 [使用...] 屬性設定為 true

    有一種自動產生的驗證方法,用於檢查含糊不清之處。 方法位於 Load 驗證類別中。 這可確保使用者會被警告,可能無法重新開啟檔案。

    如需詳細資訊,請參閱 Domain-Specific語言中的驗證。

Moniker 路徑和限定符

有效的鍵標識符以標識符鍵結尾,並以內嵌樹狀結構中其父節點的標識符為前綴。 例如,如果專輯的Moniker為:

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

然後該專輯中的其中一首歌曲可以是:

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

不過,如果系統會改為以標識符參考專輯,則Moniker會如下所示:

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

請注意,由於全域唯一識別碼是獨一無二的,因此永遠不會在其父系名稱之前加上前綴。

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

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

自訂 XML 的結構

若要進行下列自定義,請在 DSL 總管中展開 XML 串行化行為 節點。 在定義域類別下,展開 [元素數據] 節點,以查看在此類別中來源的屬性和關聯性清單。 選取關聯性,並在 [屬性] 視窗中調整其選項。

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

    <familyTreeModel ...>
      <!-- The following node is omitted by using Omit Element: -->
      <!-- <people> -->
        <person name="Henry VIII" .../>
        <person name="Elizabeth I" .../>
      <!-- </people> -->
    </familyTreeModel>
    
  • 設定 使用完整格式,將目標節點內嵌在代表關聯性實例的節點中。 當您將網域屬性新增至網域關聯性時,會自動設定此選項。

    <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>
    
  • Representation = Element 設定為將定義域屬性儲存為元素,而不是屬性值。

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

使用程式碼進行的主要自訂設定

您可以取代部分或所有串行化演算法。

建議您在 Dsl\Generated Code\Serializer.csSerializationHelper.cs中研究程序代碼。

自訂特定類別的序列化

  1. Xml 序列化行為下,將 設定為該類別節點中的自定義

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

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

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

注意

從 Visual Studio 2022 17.13 開始,預設序列化方法不再支援使用 BinaryFormatter 來序列化或反序列化自定義數據類型,因為 BinaryFormatter 存在 安全性風險。

如果您對任何定義域屬性使用自定義數據類型,您需要覆寫 SerializationHelper 類別中的序列化方法,或實作一個 TypeConverter,該 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 串行化行為\類別數據下。

財產 描述
具有自定義項目架構 如果為 True,表示網域類別具有自定義元素架構
為自定義 如果您想要為此網域類別撰寫自己的串行化和還原串行化程式代碼,請將值設定為 True

建置解決方案並調查錯誤,以探索詳細指示。
Domain 類別 此類別資料節點所適用的網域類別。 唯讀模式。
元素名稱 這個類別專案的 Xml 節點名稱。 預設值是網域類別名稱的小寫版本。
Moniker 屬性名稱 Moniker 元素中用來包含參考的屬性名稱。 如果為空,將使用鍵屬性或 ID 的名稱。

在此範例中,其為 「name」 <personMoniker name="/Mike Nash"/>
Moniker 元素名稱 用於標識此類別元素的 XML 項目名稱。

預設值是類別名稱的小寫版本,並在其後加上「Moniker」。 例如,personMoniker
Moniker 類型名稱 產生此類別元素別名的 xsd 型別名稱。 XSD 位於 Dsl\Generated Code\*Schema.xsd
序列化識別碼 如果為 True,則元素 GUID 會包含在檔案中。 如果沒有任何屬性標示 Is Moniker Key 且 DSL 定義這個類別的參考關聯性,則 的值必須設定為 True
類型名稱 從指定網域類別生成的 xsd 中生成的 xml 類型名稱。
筆記 與此專案相關聯的非正式筆記

Xml 屬性數據

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

財產 描述
網域屬性 適用於 xml 序列化設定資料的屬性。 唯讀的。
是 Moniker 金鑰 如果值設定為 True,則使用該屬性作為鍵,以建立參考這個領域類別實例的 moniker。
是Moniker限定符 如果值設定為 True,則會使用 屬性在 Moniker 中建立限定符。 如果為 false,且此網域類別的 SerializeId 不是 true,則識別符號會受內嵌樹狀結構中父元素的識別符號限定。
代表 如果值設定為 Attribute,則屬性會序列化為 XML 屬性,如果值設定為 Element,則會將其序列化為元素,如果值設定為 Ignore,則不會序列化。
Xml 名稱 用於表示屬性的 xml 屬性或項目的名稱。 根據預設,此值是網域屬性名稱的小寫版本。
筆記 與此專案相關聯的非正式筆記

Xml 角色數據

角色數據節點可在來源類別節點下找到。

財產 描述
具有自定義Moniker 如果您想要提供自己的程式代碼來產生和解析遍歷這個關聯性的標識符,請將此設定為 true。

如需詳細指示,請建置解決方案,然後雙擊錯誤訊息。
網域關聯性 指定這些選項套用的關聯性。 唯讀。
省略元素 如果為 true,則會從架構中省略對應至來源角色的 XML 節點。

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