【Revit】拡張ストレージ周辺の基本的な操作のサンプルコード (値の保存、復元など)【C#】

# 概要
RevitAPIを使用して、拡張ストレージ周辺の基本的な操作に関するサンプルコードを記載してみました。
スキーマ作成やロード、値の保存、復元、削除方法について記載しています。
※コードはRevit2024のRevitAPIで作成しています。

# 用語
### 拡張ストレージ
Revitの物件ファイル内にデータを保存するための機能です。

### スキーマ
拡張ストレージのデータ構造を定義するための領域です。`SchemaBuilder`というクラスを使用して作成できます。
スキーマは拡張ストレージの中に複数作成することができます。

### エンティティ
スキーマを実際に使用してデータを保存するためのものです。スキーマクラスのインスタンスのようなイメージだと思います。

### フィールド
スキーマ内に定義できるデータの項目です。
フィールドはスキーマの中に複数作成することができます。

# サンプルコード
ここでは拡張ストレージに「スキーマA」と「スキーマB」という二つのスキーマを保持していることとして、コードを記載しています。
登場クラス、インターフェースは以下になります。

- [SchemaOperatorクラス](#schemaoperatorクラス)
- [SchemaBaseクラス](#schemabaseクラス)
- [ISchemaBaseインターフェース](#ischemabaseインターフェース)
- [ISchemaAインターフェース](#ischemaaインターフェース)
- [SchemaAクラス](#schemaaクラス)
- [SchemaBクラス](#schemabクラス)
- [SchemaManagerクラス](#schemamanagerクラスsaveschemaentitycommandクラス)
- [SaveSchemaEntityCommandクラス](#schemamanagerクラスsaveschemaentitycommandクラス)

### SchemaOperatorクラス
- 1つのスキーマに関する操作を行うクラスです。
- `Document.IsValidObject`プロパティで.NETラッパーがまだ有効かどうかを確認できます。このプロパティを使用することでRevitAPIでの開発においてより堅牢なコードを作成することができます。
参考：
  - [IsValidObject Property](https://www.revitapidocs.com/2024/8202bf72-a9b4-0db1-5632-5a7ebb5791c5.htm)
  - [Revit API の無効なオブジェクト](https://stackoverflow.com/questions/38052936/revit-api-invalid-object)
- 安全のため、エンティティのセット (`SetEntity()`)は`IExternalEventHandler`を使用してアイドリング時と同様のタイミングで行っています。
```C#:SchemaOperator.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Autodesk.Revit.DB;
using Autodesk.Revit.DB.ExtensibleStorage;

namespace RevitSchema
{
    /// <summary>
    /// スキーマを操作するクラス
    /// </summary>
    public class SchemaOperator
    {
        /// <summary>
        /// 物件データ
        /// </summary>
        private Document _document;

        /// <summary>
        /// スキーマエンティティ
        /// </summary>
        private Entity _schemaEntity;

        /// <summary>
        /// スキーマ名
        /// </summary>
        private string _schemaName;

        /// <summary>
        /// フィールド名のリスト
        /// </summary>
        private IReadOnlyList<string> _fieldNames;

        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="document">物件データ</param>
        /// <param name="schemaName">スキーマ名</param>
        /// <param name="fieldNames">フィールド名のリスト</param>
        public SchemaOperator(Document document, string schemaName, IReadOnlyList<string> fieldNames)
        {
            _document = document;
            _schemaName = schemaName;
            _fieldNames = fieldNames;

            // 物件データにスキーマが存在するか確認
            var schema = GetSchema();

            if (schema == null)
            {
                // 新しくスキーマを作成する
                _schemaEntity = CreateSchemaEntity();
                SaveSchemaEntity();
            }
            else
            {
                // 既存のスキーマを読み込む
                LoadSchemaEntity(schema);

                if (!IsValidFields(_schemaEntity))
                {
                    // フィールド構成が不正ならスキーマを更新する
                    var newSchemaEntity = UpdateSchemaEntity(_schemaEntity);

                    // スキーマ削除
                    using (var tx = new Transaction(_document))
                    {
                        try
                        {
                            tx.Start("Transaction Name");
                            _document.ProjectInformation.DeleteEntity(schema);
                            tx.Commit();
                        }
                        catch (Exception ex)
                        {
                            tx.RollBack();
                            return;
                        }
                    }

                    // 新しいスキーマを物件データに付与
                    _schemaEntity = newSchemaEntity;
                    SaveSchemaEntity();
                }
            }
        }

        /// <summary>
        /// 物件データに現在のスキーマエンティティを書き込む
        /// </summary>
        public void SaveSchemaEntity()
        {
            using (var tx = new Transaction(_document))
            {
                try
                {
                    tx.Start("Transaction Name");

                    _document.ProjectInformation.SetEntity(_schemaEntity);

                    tx.Commit();
                }
                catch (Exception ex)
                {
                    tx.RollBack();
                }
            }
        }

        /// <summary>
        /// スキーマエンティティをセットする
        /// </summary>
        /// <param name="entity">スキーマエンティティ</param>
        public void SetSchemaEntity(Entity entity) => _schemaEntity = entity;

        /// <summary>
        /// スキーマエンティティのインスタンスの複製を取得する
        /// </summary>
        /// <returns></returns>
        public Entity CloneSchemaEntity() => new Entity(_schemaEntity);

        /// <summary>
        /// 物件データからスキーマエンティティを取得する
        /// </summary>
        /// <returns>取得したスキーマエンティティ</returns>
        private void LoadSchemaEntity(Schema schema) => _schemaEntity = _document.ProjectInformation.GetEntity(schema);

        /// <summary>
        /// 対象スキーマを取得する
        /// </summary>
        /// <returns>スキーマ</returns>
        public Schema GetSchema()
        {
            var guids = _document.ProjectInformation.GetEntitySchemaGuids();
            var targetGuid = guids.FirstOrDefault(guid => Schema.Lookup(guid)?.SchemaName == _schemaName);

            return targetGuid == default ? null : Schema.Lookup(targetGuid);
        }

        /// <summary>
        /// スキーマエンティティを作成する
        /// </summary>
        /// <returns>スキーマエンティティ</returns>
        private Entity CreateSchemaEntity()
        {
            var schemaId = Guid.NewGuid();
            Schema schema = null;
            using (var builder = new SchemaBuilder(schemaId))
            {
                // スキーマの名前とアクセスレベルを設定
                builder.SetSchemaName(_schemaName);
                builder.SetReadAccessLevel(AccessLevel.Public);

                // フィールドの追加
                foreach (var fieldName in _fieldNames)
                {
                    builder.AddMapField(fieldName, typeof(string), typeof(string));
                }

                schema = builder.Finish();
            }

            return new Entity(schema);
        }

        /// <summary>
        /// スキーマのフィールド構成が正しいかチェックする
        /// </summary>
        /// <param name="schema">スキーマ</param>
        /// <returns>正しい場合にtrue</returns>
        private bool IsValidFields(Entity schema)
        {
            try
            {
                foreach (var field in _fieldNames)
                {
                    var fieldValue = schema.Get<IDictionary<string, string>>(field);
                }
            }
            catch (Exception)
            {
                // 取得できないフィールドがある場合はエラーとなる
                return false;
            }

            return true;
        }

        /// <summary>
        /// スキーマの更新処理を行う
        /// </summary>
        /// <param name="schema">読み込んだスキーマ</param>
        /// <returns>更新後のスキーマ</returns>
        private Entity UpdateSchemaEntity(Entity schema)
        {
            Schema newSchema = null;
            var guid = Guid.NewGuid();

            using (var builder = new SchemaBuilder(guid))
            {
                builder.SetSchemaName(_schemaName);
                builder.SetReadAccessLevel(AccessLevel.Public);

                foreach (var fieldName in _fieldNames)
                {
                    builder.AddMapField(fieldName, typeof(string), typeof(string));
                }

                newSchema = builder.Finish();
            }

            var newSchemaEntity = new Entity(newSchema);

            // 更新前のスキーマから新しいスキーマへ値をコピーする
            foreach (var field in _fieldNames)
            {
                try
                {
                    var fieldValue = schema.Get<IDictionary<string, string>>(field);
                    newSchemaEntity.Set(field, fieldValue);
                }
                catch (Exception)
                {
                    // フィールドが存在しない場合は引き継げないため、後続の処理を続行する
                    continue;
                }
            }

            return newSchemaEntity;
        }

        /// <summary>
        /// スキーマ内の全てのフィールドをクリアする
        /// </summary>
        public void ClearAllFields()
        {
            var entity = new Entity(_schemaEntity);

            try
            {
                foreach (var fieldName in _fieldNames)
                {
                    // フィールドをクリア
                    _schemaEntity.Clear(fieldName);
                }
            }
            catch (Exception)
            {
                SetSchemaEntity(entity);
                throw;
            }
        }

        /// <summary>
        /// スキーマの対象のフィールドをクリアする
        /// </summary>
        /// <param name="fieldName">クリアするフィールド名</param>
        public void ClearTargetFields(string fieldName) => _schemaEntity.Clear(fieldName);

        /// <summary>
        /// 対象のフィールドの指定したKeyに値をセットする
        /// </summary>
        /// <param name="key">取得対象のキー</param>
        /// <param name="fieldName">フィールド名</param
        /// <param name="value">セットする値</param>
        public void SetValue(string fieldName, string key, string value)
        {
            var fields = _schemaEntity.Get<IDictionary<string, string>>(fieldName);
            if (fields.ContainsKey(key))
            {
                fields[key] = value;
            }
            else
            {
                fields.Add(key, value);
            }

            _schemaEntity.Set(fieldName, fields);
        }

        /// <summary>
        /// 対象のフィールドから指定したKeyのValueを取得する
        /// </summary>
        /// <param name="key">取得対象のキー</param>
        /// <param name="fieldName">フィールド名</param>
        /// <returns>取得したValue</returns>
        public string GetValue(string fieldName, string key)
        {
            try
            {
                var field = _schemaEntity.Get<IDictionary<string, string>>(fieldName);
                return field.TryGetValue(key, out string result) ? result : null;
            }
            catch (Exception ex)
            {
                return null;
            }
        }

        /// <summary>
        /// 対象フィールドを取得する
        /// </summary>
        /// <param name="fieldName">取得対象マップフィールド</param>
        /// <returns>フィールドのディクショナリ</returns>
        public Dictionary<string, string> GetFileds(string fieldName)
            => _schemaEntity.Get<IDictionary<string, string>>(fieldName).ToDictionary(x => x.Key, y => y.Value);

        /// <summary>
        /// FieldNameとkeyから拡張ストレージ内のデータを削除する
        /// </summary>
        /// <param name="fieldName">削除対象のフィールド名</param>
        /// <param name="key">削除対象のKey</param>
        public void DeleteFieldValue(string fieldName, string key)
        {
            if (_document == null || key == string.Empty || !_document.IsValidObject)
            {
                return;
            }

            var field = _schemaEntity.Get<IDictionary<string, string>>(fieldName);
            field.Remove(key);

            _schemaEntity.Set(fieldName, field);

            return;
        }
    }
}
```
### SchemaBaseクラス
- ひとつのスキーマのベースとなる抽象クラスです。
抽象クラスにしているため単体ではインスタンス化できないので、派生クラスをインスタンス化する必要があります。
- このクラスの`SetValue()`を呼び出すことで、拡張ストレージに値を保存することができます。
- このクラスの`GetValue()`を呼び出すことで、拡張ストレージから値を取得することができます。
```C#
using Autodesk.Revit.DB;
using System.Collections.Generic;
using System.Linq;

namespace RevitSchema
{
    /// <summary>
    /// スキーマの抽象クラス
    /// </summary>
    public abstract class SchemaBase : ISchemaBase
    {
        /// <summary>
        /// スキーマ操作クラス
        /// </summary>
        public SchemaOperator SchemaOperator { get; }

        /// <summary>
        /// 物件データ
        /// </summary>
        public Document Document { get; }

        /// <summary>
        /// スキーマ名
        /// </summary>
        public string SchemaName { get; }

        /// <summary>
        /// フィールド名のリスト
        /// </summary>
        public IReadOnlyList<string> FieldNames { get; }

        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="document">物件データ</param>
        /// <param name="schemaName">スキーマ名</param>
        /// <param name="fieldNames">スキーマのフィールド名のリスト</param>
        public SchemaBase(Document document, string schemaName, IReadOnlyList<string> fieldNames)
        {
            Document = document;
            SchemaName = schemaName;
            FieldNames = fieldNames.ToList();

            SchemaOperator = new SchemaOperator(document, schemaName, fieldNames);
        }

        /// <summary>
        /// 物件データにスキーマエンティティを書き込む
        /// </summary>
        public void SaveSchemaEntity() => SchemaOperator.SaveSchemaEntity();

        /// <summary>
        /// スキーマの対象のフィールドをクリアする
        /// </summary>
        /// <param name="fieldName">クリアするフィールド名</param>
        public void ClearField(string fieldName)
        {
            SchemaOperator.ClearTargetFields(fieldName);
            SchemaManager.Instance.RaiseSaveSchemaEntityEvent();
        }

        /// <summary>
        /// スキーマ内の全てのフィールドをクリアする
        /// </summary>
        public void ClearAllFields()
        {
            SchemaOperator.ClearAllFields();
            SchemaManager.Instance.RaiseSaveSchemaEntityEvent();
        }

        /// <summary>
        /// FieldNameとkeyから拡張ストレージ内のデータを削除する
        /// </summary>
        /// <param name="fieldName">削除対象のフィールド名</param>
        /// <param name="key">削除対象のKey</param>
        public void DeleteValue(string fieldName, string key)
        {
            SchemaOperator.DeleteFieldValue(fieldName, key);
            SchemaManager.Instance.RaiseSaveSchemaEntityEvent();
        }

        /// <summary>
        /// 対象フィールドを取得する
        /// </summary>
        /// <param name="fieldName">取得対象マップフィールド</param>
        /// <returns>フィールドのディクショナリ</returns>
        public Dictionary<string, string> GetFields(string fieldName) => SchemaOperator.GetFileds(fieldName);

        /// <summary>
        /// 対象のフィールドから指定したKeyのValueを取得する
        /// </summary>
        /// <param name="fieldName">フィールド名</param>
        /// <param name="key">取得対象のキー</param>
        /// <returns>取得したValue</returns>
        public string GetValue(string fieldName, string key) => SchemaOperator.GetValue(fieldName, key);

        /// <summary>
        /// 対象のフィールドの指定したKeyに値をセットする
        /// </summary>
        /// <param name="fieldName">フィールド名</param>
        /// <param name="key">取得対象のキー</param>
        /// <param name="value">セットする値</param>
        public void SetValue(string fieldName, string key, string value)
        {
            SchemaOperator.SetValue(fieldName, key, value);
            SchemaManager.Instance.RaiseSaveSchemaEntityEvent();
        }
    }
}
```

### ISchemaBaseインターフェース
- Schema操作に関する基本的なプロパティやメソッド名をまとめたインターフェースです。

```C#:ISchemaBase.cs
using Autodesk.Revit.DB;
using System.Collections.Generic;

namespace RevitSchema
{
    /// <summary>
    /// 拡張ストレージのスキーマ操作クラス用のインターフェース
    /// </summary>
    public interface ISchemaBase
    {
        /// <summary>
        /// アクティブなドキュメント
        /// </summary>
        Document Document { get; }

        /// <summary>
        /// スキーマ名
        /// </summary>
        string SchemaName { get; }

        /// <summary>
        /// フィールド名リスト
        /// </summary>
        IReadOnlyList<string> FieldNameList { get; }

        /// <summary>
        /// スキーマエンティティを物件データに書き込む
        /// </summary>
        void SaveSchemaEntity();

        /// <summary>
        /// 値をセットする
        /// </summary>
        /// <param name="fieldName">フィールド名</param>
        /// <param name="key">キー名</param>
        /// <param name="value">値</param>
        void SetValue(string fieldName, string key, string value);

        /// <summary>
        /// 指定のフィールドを取得する
        /// </summary>
        /// <param name="fieldName">対象フィールド名</param>
        /// <returns></returns>
        Dictionary<string, string> GetField(string fieldName);

        /// <summary>
        /// 指定のフィールドをクリアする
        /// </summary>
        /// <param name="fieldName">対象フィールド名</param>
        void ClearField(string fieldName);

        /// <summary>
        /// 全てのフィールドをクリアする
        /// </summary>
        void ClearAllFields();

        /// <summary>
        /// 指定の値を取得する
        /// </summary>
        /// <param name="fieldName">対象フィールド名</param>
        /// <param name="key">対象のキー</param>
        /// <returns></returns>
        string GetValue(string fieldName, string key);

        /// <summary>
        /// 指定の値を削除する
        /// </summary>
        /// <param name="fieldName">対象フィールド名</param>
        /// <param name="key">対象のキー</param>
        void DeleteValue(string fieldName, string key);
    }
}
```

### ISchemaAインターフェース
- 「スキーマA」用のインターフェースです。
- `ISchemaBase`に加えて、「スキーマA」内の全データを削除する`DeleteData()`という抽象メソッドを追加しています。
```C#:ISchemaA.cs
namespace RevitSchema
{
    /// <summary>
    /// スキーマAの操作クラス用のインターフェース
    /// </summary>
    public interface ISchemaA : ISchemaBase
    {
        /// <summary>
        /// このスキーマ内の全データを削除する
        /// </summary>
        void DeleteData();
    }
}
```

### SchemaAクラス
- 「スキーマA」を操作するクラスです。

```C#
using Autodesk.Revit.DB;
using Autodesk.Revit.DB.ExtensibleStorage;
using System.Collections.Generic;

namespace RevitSchema
{
    /// <summary>
    /// スキーマA操作用クラス
    /// </summary>
    public class SchemaA : SchemaBase, ISchemaA
    {
        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="document">物件データ</param>
        /// <param name="schemaName">スキーマ名</param>
        /// <param name="fieldNames">スキーマのフィールド名のリスト</param>
        public SchemaA(Document document, string schemaName, IReadOnlyList<string> fieldNames)
            : base(document, schemaName, fieldNames)
        {
        }

        /// <summary>
        /// 全データを削除する
        /// </summary>
        public void DeleteData()
        {
            var entity = SchemaOperator.CloneSchemaEntity();

            if (Document == null || !Document.IsValidObject) return;

            // フィールド名で指定しているので、特定のフィールドは削除しないようにすることも可能
            foreach(string fieldName in base.FieldNames)
            {
                RemoveTargetEntityData(entity, fieldName);
            }

            SchemaOperator.SetSchemaEntity(entity);
            SchemaManager.Instance.RaiseSaveSchemaEntityEvent();
        }

        /// <summary>
        /// 指定した条件のスキーマエンティティのデータを削除する
        /// </summary>
        /// <param name="entity">スキーマエンティティ</param>
        /// <param name="fieldName">フィールド名</param>
        private void RemoveTargetEntityData(Entity entity, string fieldName)
        {
            var fieldValue = entity.Get<IDictionary<string, string>>(fieldName);
            foreach (string key in fieldValue.Keys)
            {
                // 削除
                // Keyを一つずつまわしているため、特定のKeyは削除しないようにすることも可能
                fieldValue.Remove(key);
            }

            entity.Set(fieldName, fieldValue);
        }
    }
}
```

### SchemaBクラス
- 「スキーマB」を操作するクラスです。
- `SchemaBase`クラスのみ継承しています。
```C# 
using Autodesk.Revit.DB;
using System.Collections.Generic;

namespace RevitSchema
{
    /// <summary>
    /// スキーマB操作用クラス
    /// </summary>
    public class SchemaB : SchemaBase
    {
        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="document">物件データ</param>
        /// <param name="schemaName">スキーマ名</param>
        /// <param name="fieldNames">スキーマのフィールド名のリスト</param>
        public SchemaB(Document document, string schemaName, IReadOnlyList<string> fieldNames)
            : base(document, schemaName, fieldNames) { }
    }
}
```

### SchemaManagerクラス<br>SaveSchemaEntityCommandクラス
#### SchemaManagerクラス
- 複数のスキーマをインターフェース型のプロパティとして持つ、スキーマ群を管理するクラスです。
- シングルトンで記載しています。
- `LoadSchema()`の処理内でスキーマ名やフィールド名を作成直前に定義していますが、本来はプロパティ等で固定値として別に定義しておく形が良いです。
#### SaveSchemaEntityCommandクラス
- 外部イベント発火用のクラスです。
- `GetName()`メソッドの値は外部イベントハンドラを識別するための識別子を表しています。例えば`SaveSchemaEntityCommand`クラスのインスタンスから`GetName()`を呼び出すことで、外部イベントハンドラの名前を知ることができます。

```C#:SchemaManager.cs
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using System;

namespace RevitSchema
{
    /// <summary>
    /// スキーマ群を管理するクラス
    /// </summary>
    public class SchemaManager
    {
        /// <summary>
        /// シングルトンのインスタンス
        /// </summary>
        public static SchemaManager Instance { get; } = new SchemaManager();

        /// <summary>
        /// スキーマA
        /// </summary>
        public ISchemaA SchemaA { get; private set; }

        /// <summary>
        /// スキーマB
        /// </summary>
        public ISchemaBase SchemaB { get; private set; }

        /// <summary>
        /// スキーマエンティティをセットする外部イベント
        /// </summary>
        private SaveSchemaEntityCommand _saveSchemaEntityCommand = new SaveSchemaEntityCommand();

        /// <summary>
        /// スキーマ情報を拡張ストレージから読み込む
        /// </summary>
        /// <param name="document">物件データ</param>
        public void LoadSchema(Document document)
        {
            // サンプルのためここでは直前にスキーマ名やフィルド名を定義
            string schemaAName = "HogeSchema";
            string schemaBName = "FugaSchema";

            var schemaAFields = new string[] { "HogeField", "FugaField" };
            var schemaBFields = new string[] { "PiyoField" };

            SchemaA = new SchemaA(document, schemaAName, schemaAFields);
            SchemaB = new SchemaB(document, schemaBName, schemaBFields);
        }

        /// <summary>
        /// スキーマエンティティを物件データのスキーマに反映する
        /// </summary>
        public void SaveSchemaEntity()
        {
            SchemaA?.SaveSchemaEntity();
            SchemaB?.SaveSchemaEntity();
        }

        /// <summary>
        /// スキーマエンティティを物件データに反映するイベントを発火させる
        /// </summary>
        public void RaiseSaveSchemaEntityEvent() => _saveSchemaEntityCommand.RaiseEvent();
    }

    /// <summary>
    /// スキーマエンティティを物件データに書き込む外部イベントハンドラ
    /// </summary>
    public class SaveSchemaEntityCommand : IExternalEventHandler
    {
        /// <summary>
        /// 既に呼び出されているか
        /// </summary>
        private bool _isRaising = false;

        private ExternalEvent _revitEvent;

        /// <summary>
        /// コンストラクタ
        /// </summary>
        public SaveSchemaEntityCommand() => _revitEvent = ExternalEvent.Create(this);

        /// <summary>
        /// 外部イベントが実際に実行される際に呼び出されるメソッド
        /// </summary>
        /// <param name="app"></param>
        public void Execute(UIApplication app)
        {
            try
            {
                SchemaManager.Instance.SaveSchemaEntity();
            }
            finally
            {
                _isRaising = false;
            }
        }

        /// <summary>
        /// コマンド実行
        /// </summary>
        public void RaiseEvent()
        {
            if (!_isRaising)
            {
                // イベントの多重呼出しを防ぐ
                _revitEvent.Raise();
                _isRaising = true;
            }
        }

        /// <summary>
        /// 外部イベントの名前
        /// </summary>
        public string GetName() => "SaveSchemaEntityCommand";
    }
}
```

# サンプルコードの使用方法

### 値の保存、復元、削除
以下のように`SchemaBase`クラスの`SetValue()`や`GetValue()`を呼び出すことで、値の保存や復元ができます。
```C#
string fieldName = "HogeField";
string key = "ABC";
string value = "あいうえお";

// スキーマAのKey「ABC」に値「あいうえお」を保存する
SchemaManager.Instance.SchemaA.SetValue(fieldName, key, value);

// スキーマAのKey「ABC」の値を復元する
// 例えば一つ上の保存後に実行した場合、Valueには"あいうえお"が入っている
string value = SchemaManager.Instance.SchemaA.GetValue(fieldName, key);
```
削除も行えます。
```C#
// スキーマAのKey「HogeField」のKey「ABC」の値を削除する
string fieldName = "HogeField";
string key = "ABC";
SchemaManager.Instance.SchemaA.DeleteValue(fieldName, key);

// スキーマAの全てのフィールドのデータを削除する
SchemaManager.Instance.SchemaA.DeleteData();
```

### スキーマの作成、読み込み方法
- 物件ファイルが開かれる際に`SchemaManager`クラスの`LoadSchema()`を呼出してスキーマを読み込んでいます。
- 安全のため、Revitの`Idling`イベントの際にスキーマを読み込んでいます。
`ViewActivated`で`Idling`イベント時にスキーマを読み込むためのフラグを立て、次回の`Idling`イベント時にスキーマを読み込んでいます。`ViewActivated`でスキーマの読み込み要否を判断することで、異なる物件データに切り替わった際も対応できます。
  - `Idling`イベントの参考：[IdlingEvent](https://thebuildingcoder.typepad.com/blog/2010/04/idling-event.html)

```C#:App.cs
using Autodesk.Revit.UI;
using Autodesk.Revit.UI.Events;
using RevitSchema;
using System;

namespace Hoge
{
    public class App : IExternalApplication
    {
        public Result OnStartup(UIControlledApplication uiApp)
        {
            // OnStartupメソッド等、処理の初期の方でViewActivatedとIdlingのイベントハンドラを接続しておく
            uiApp.ViewActivated += RevitEventHandlerMethod_ViewActivated;
            uiApp.Idling += RevitEventHandlerMethod_Idling;
        }
        
        private void RevitEventHandlerMethod_ViewActivated(object obj, ViewActivatedEventArgs args)
        {
             var currentDocument = args.Document;
             var lastDocument = SchemaManager.Instance.SchemaA?.Document;
             bool lastDocumentIsValidObject = lastDocument?.IsValidObject ?? false;
             
             if (!lastDocumentIsValidObject || !currentDocument.Equals(lastDocument))
             {
                 // アクティブな物件データが変更された場合

                 // ここでアイドルイベント時にスキーマを読み込むフラグをセット 
             }

            if (currentDocument.IsFamilyDocument)
            {
                // 少し余談になりますが、ファミリドキュメントが開かれた場合もこのイベントが走りますが、この条件で判断が可能です
            }

        }

        private void RevitEventHandlerMethod_Idling(object obj, EventArgs args)
        {
            var uiApp = obj as UIApplication;
            
            if (ViewActivatedでセットしたフラグがtrueなら)
            {
                 // 新しく開かれた物件データのスキーマを読み込む
                 SchemaManager.Instance.LoadSchema(uiApp.ActiveUIDocument.Document);
            }
        }
    }
}
```

# 補足
拡張ストレージのValueには値1つ保存するのもよいですが、例えばxml形式やJson形式などの文字列も格納できます。そのような形式で保存しておき、ストレージから復元する時はデシリアライズして使用、保存するときはシリアライズするような方針で対応をおこなフィールドの中の項目が大量に増えてしまうという懸念が少なくなります。

# 終わりに
スキーマ操作関連は少し複雑ですが、拡張ストレージを使用できるととてもアドイン開発が楽になると思われます。