Implement A Dynamic Schema

Table of Contents

Example

Complete source is available DynamicSchema.ts from Parametric Connector Framework

Synch the Dynamic Schema

Create the schema, compare it with existing version schema if it exists, register the schema if its either new or changed from previous version

export async function syncDynamicSchema(
  db: IModelDb,
  domainSchemaNames: string[],
  props: DynamicSchemaProps,
): Promise<pcf.ItemState> {

  const { schemaName } = props;
  const existingSchema = await tryGetSchema(db, schemaName);

  const version = getSchemaVersion(db, schemaName);
  const latestSchema = await createDynamicSchema(db, version, domainSchemaNames, props);

  let schemaState: pcf.ItemState = pcf.ItemState.New;
  let dynamicSchema = latestSchema;

  if (existingSchema) {
    const reporter = new DynamicSchemaCompareReporter();
    const comparer = new SchemaComparer(reporter);
    await comparer.compareSchemas(latestSchema, existingSchema);
    const schemaIsChanged = reporter.diagnostics.length > 0;

    if (schemaIsChanged) {
      schemaState = pcf.ItemState.Changed;
      version.minorVersion = existingSchema.minorVersion + 1;
      dynamicSchema = await createDynamicSchema(db, version, domainSchemaNames, props);
    } else {
      schemaState = pcf.ItemState.Unchanged;
      dynamicSchema = existingSchema;
    }
  }

  if (schemaState !== pcf.ItemState.Unchanged) {
    const schemaString = await schemaToXmlString(dynamicSchema);
    await db.importSchemaStrings([schemaString]);
    registerDynamicSchema(props);
  }

  return schemaState;
}

Register the Dynamic Schema

First unregister if already registered in case of upgrading schema, then register current version of schema

function registerDynamicSchema(props: DynamicSchemaProps) {

  const entitiesModule: any = {};
  for (const entity of props.dynamicEntityMap.entities) {
    if (entity.registeredClass) {
      entitiesModule[entity.registeredClass.className] = entity.registeredClass;
    }
  }

  const relationshipsModule: any = {};
  for (const rel of props.dynamicEntityMap.relationships) {
    if (rel.registeredClass) {
      relationshipsModule[rel.registeredClass.className] = rel.registeredClass;
    }
  }

  const dynamicSchemaClass = class BackendDynamicSchema extends Schema {
    public static override get schemaName(): string {
      return props.schemaName;
    }
    public static registerSchema() {
      if (this !== Schemas.getRegisteredSchema(this.schemaName)) {
        Schemas.unregisterSchema(this.schemaName);
        Schemas.registerSchema(this);
        ClassRegistry.registerModule(entitiesModule, this);
        ClassRegistry.registerModule(relationshipsModule, this);
      }
    }
  };
  dynamicSchemaClass.registerSchema();
}

Create the Dynamic Schema

Iterate through classes and properties and add them to the schema

/ Generates an in-memory [Dynamic EC Schema](https://www.itwinjs.org/bis/intro/schema-customization/) from user-defined DMO.
sync function createDynamicSchema(
 db: IModelDb,
 version: SchemaVersion,
 domainSchemaNames: string[],
 props: DynamicSchemaProps,
: Promise<MetaSchema> {

 const map = props.dynamicEntityMap;
 const context = new SchemaContext();
 const editor = new SchemaContextEditor(context);

 const createEntityClass = async (schema: MetaSchema) => {
   for (const entity of map.entities) {
     const entityResult = await editor.entities.createFromProps(schema.schemaKey, entity.props);
     if (!entityResult.itemKey)
       throw new Error(`Failed to create EC Entity Class - ${entityResult.errorMessage}`);

     if (entity.props.properties) {
       for (const prop of entity.props.properties) {
         const propResult = await editor.entities.createPrimitiveProperty(entityResult.itemKey, prop.name, prop.type as any);
         if (!propResult.itemKey)
           throw new Error(`Failed to create EC Property - ${propResult.errorMessage}`);
       }
     }
   }
 };

 const createRelClasses = async (schema: MetaSchema) => {
   for (const rel of map.relationships) {
     const result = await editor.relationships.createFromProps(schema.schemaKey, rel.props);
     if (!result.itemKey)
       throw new Error(`Failed to create EC Relationship Class - ${result.errorMessage}`);
   }
 };

 const { schemaName, schemaAlias } = props;
 const newSchema = new MetaSchema(context, schemaName, schemaAlias, version.readVersion, version.writeVersion, version.minorVersion);

 const loader = new SchemaLoader(db as unknown as SchemaPropsGetter);
 const bisSchema = loader.getSchema("BisCore");
 await context.addSchema(newSchema);
 await context.addSchema(bisSchema);
 await (newSchema as MutableSchema).addReference(bisSchema); // TODO remove this hack later

 for (const currSchemaName of domainSchemaNames) {
   const schema = loader.getSchema(currSchemaName);
   await context.addSchema(schema);
   await (newSchema as MutableSchema).addReference(schema);
 }

 await createEntityClass(newSchema);
 await createRelClasses(newSchema);

 return newSchema;

Last Updated: 01 May, 2024