Extensible Storage

Create your own class-like Schema data structures and attach instances of them to any Element in a Revit model.

Schema-based data is saved with the Revit model and allows for higher-level, metadata-enhanced, object-oriented data structures. Schema data can be configured to be readable and/or writable to all users, just a specific application vendor, or just a specific application from a vendor.

The following steps are necessary to store data with Elements in Revit:

  1. Create and name a new schema
  2. Set the read/write access for the schema
  3. Define one or more fields of data for the schema
  4. Create an entity based on the schema
  5. Assign values to the fields for the entity
  6. Associate the entity with a Revit element

Schemas and SchemaBuilder

The first step to creating extensible storage is to define the schema. A schema is similar to a class in an object-oriented programming language. Use the SchemaBuilder class constructor to create a new schema. SchemaBuilder is a helper class used to create schemas. Once a schema is finalized using SchemaBuilder, the Schema class is used to access properties of the schema. At that stage, the schema is no longer editable.

Although the SchemaBuilder constructor takes a GUID which is used to identify the schema, a schema name is also required. After creating the schema, call SchemaBuilder.SetSchemaName() to assign a user-friendly identifier for the schema. The schema name is useful to identify a schema in an error message.

The read and write access levels of entities associated with the schema can be set independently. The options are Public, Vendor, or Application. If either the read or write access level is set to Vendor, the VendorId of the third-party vendor that may access entities of the schema must be specified. If either access level is set to Application, the GUID of the application or add-in that may access entities of the schema must be supplied.

Note: Schemas are stored with the document and any Revit API add-in may read the available schemas in the document, as well as some data of the schema. However, access to the fields of a schema is restricted based on the read access defined in the schema and the actual data in the entities stored with specific elements is restricted based on the read and write access levels set in the schema when it is defined.

Fields and FieldBuilder

Once the schema has been created, fields may be defined. A field is similar to a property of a class. It contains a name, documentation, value type and unit type. Fields can be a simple type, an array, or a map. The following simple data types are allowed:

Type

Default Value

int

0

short

0

byte

0

double

0.0

float

0.0

bool

false

string

Empty string ("")

GUID

Guid.Empty {00000000-0000-0000-0000-000000000000}

ElementId

ElementId.InvalidElementId

Autodesk.Revit.DB.XYZ

(0.0,0.0,0.0)

Autodesk.Revit.DB.UV

(0.0,0.0)

Additionally, a field may be of type Autodesk.Revit.DB.ExtensibleStorage.Entity. In other words, an instance of another Schema, also known as a SubSchema or SubEntity. The default value for a field of this type is Entity with null schema, and guid of Guid.Empty.

A simple field can be created using the SchemaBuilder.AddSimpleField() method to specify a name and type for the field. AddSimpleField() returns a FieldBuilder, which is a helper class for defining Fields. If the type of the field was specified as Entity, use FieldBuilder.SetSubSchemaGUID() to specify the GUID of the schema of the Entities that are to be stored in this field.

Use the SchemaBuilder.AddArrayField() method to create a field containing an array of values in the Schema, with a given name and type of contained values. Array fields can have all the same types as simple fields.

Use the SchemaBuilder.AddMapField() method to create a field containing an ordered key-value map in the Schema, with given name, type of key and type of contained values. Supported types for values are the same as for simple fields. Supported types for keys are limited to int, short, byte, string, bool, ElementId and GUID.

Once the schema is finalized using SchemaBuilder, fields can no longer be edited using FieldBuilder. At that stage, the Schema class provides methods to get a Field by name, or a list of all Fields defined in the Schema.

Entities

After all fields have been defined for the schema, SchemaBuilder.Finish() will return the finished Schema. A new Entity can be created using that schema. For each Field in the Schema, the value can be stored using Entity.Set(), which takes a Field and a value (whose type is dependent on the field type). Once all applicable fields have been set for the entity, it can be assigned to an element using the Element.SetEntity() method.

To retrieve the data later, call Element.GetEntity() passing in the corresponding Schema. If no entity based on that schema was saved with the Element, an invalid Entity will be returned. To check that a valid Entity was returned, call the Entity.IsValid() method. Field values from the entity can be obtained using the Entity.Get() method.

To remove an extensible storage entity from an Element, call Element.DeleteEntity() passing in the Schema that was used to create it.

To determine Entities stored with an element, use the Element.GetEntitySchemaGuids() method, which returns the Schema guids of any Entities for the Element. The Schema guids can be used with the static method Schema.Lookup() to retrieve the corresponding Schemas.

The following is an example of defining an extensible storage Schema, creating an Entity, setting its values, assigning it to an Element, and retrieving the data.

Code Region 22-9: Extensible Storage

// Create a data structure, attach it to a wall, populate it with data, and retrieve the data back from the wall
void StoreDataInWall(Wall wall, XYZ dataToStore)
{
        Transaction createSchemaAndStoreData = new Transaction(wall.Document, "tCreateAndStore");
        createSchemaAndStoreData.Start();
        SchemaBuilder schemaBuilder = 
                new SchemaBuilder(new Guid("720080CB-DA99-40DC-9415-E53F280AA1F0"));
        schemaBuilder.SetReadAccessLevel(AccessLevel.Public); // allow anyone to read the object
        schemaBuilder.SetWriteAccessLevel(AccessLevel.Vendor); // restrict writing to this vendor only
        schemaBuilder.SetVendorId("ADSK"); // required because of restricted write-access
        schemaBuilder.SetSchemaName("WireSpliceLocation");
        // create a field to store an XYZ
        FieldBuilder fieldBuilder = 
                schemaBuilder.AddSimpleField("WireSpliceLocation", typeof(XYZ)); 
        fieldBuilder.SetUnitType(UnitType.UT_Length);
        fieldBuilder.SetDocumentation("A stored location value representing a wiring splice in a wall.");

        Schema schema = schemaBuilder.Finish(); // register the Schema object
        Entity entity = new Entity(schema); // create an entity (object) for this schema (class)
        // get the field from the schema
        Field fieldSpliceLocation = schema.GetField("WireSpliceLocation"); 
        // set the value for this entity
        entity.Set<XYZ>(fieldSpliceLocation, dataToStore, DisplayUnitType.DUT_METERS); 
        wall.SetEntity(entity); // store the entity in the element

        // get the data back from the wall
        Entity retrievedEntity = wall.GetEntity(schema);
        XYZ retrievedData = 
                retrievedEntity.Get<XYZ>(schema.GetField("WireSpliceLocation"),
                DisplayUnitType.DUT_METERS);
        createSchemaAndStoreData.Commit();  
}

Extensible Storage Advantages

Self Documenting and Self-Defining

Creating a schema by adding fields, units, sub-entities, and description strings is not only a means for storing data. It is also implicit documentation for other users and a way for others to create entities of the same schema later with an easy adoption path.

Takes Advantage of Locality

Because schema entities are stored on a per-element basis, there is no need to necessarily read all extensible storage data in a document (e.g. all data from all beam family instances) when an application might only need data for the currently selected beam. This allows the potential for more specifically targeted data access code and better data access performance overall.