Read the vault structure

View as Markdown

Most calls take IDs: object type IDs, class IDs, property definition IDs, value list IDs. Apart from the built-in ones, these IDs differ from vault to vault. Read the structure once, cache it, and look up IDs by alias or name instead of hard-coding them.

Calls

What Call Response field
Object types GetObjectTypes object_types: the real object types, not the value lists
One object type GetObjectType putf8szobjtypebasic
Property definitions GetPropertyDefs property_defs
Classes and class groups GetObjectClassesAndGroups classes, class_groups
ID by alias GetMetadataStructureItemIdByAlias id; -1 (-1000 for named ACLs) if no item has the alias

Code

The recipe lists the object types, counts the property definitions, and lists the document classes with the properties each requires apart from the built-in ones. Then it resolves a class alias and a property alias (defaults MF.CL.Document and MF.PD.Keywords; pass others as arguments).

Python

# Object types. GetObjectTypes returns the real object types, not the value lists.
object_types = client.stub(IRPCObjectTypes_pb2_grpc.IRPCObjectTypesStub)
types = object_types.GetObjectTypes(
    IRPCObjectTypes_pb2.GetObjectTypesRequest()).object_types
print(f"Object types: {len(types)}")
for object_type in types:
    print(f"  {object_type.id} {object_type.name_plural}")

# Property definitions and classes.
property_defs = client.stub(IRPCPropertyDefs_pb2_grpc.IRPCPropertyDefsStub)
properties = {p.id: p for p in property_defs.GetPropertyDefs(
    IRPCPropertyDefs_pb2.GetPropertyDefsRequest()).property_defs}
print(f"Property definitions: {len(properties)}")

classes = property_defs.GetObjectClassesAndGroups(
    IRPCPropertyDefs_pb2.GetObjectClassesAndGroupsRequest()).classes
document_classes = [c for c in classes if c.object_type == 0]
print(f"Document classes: {len(document_classes)}")
for object_class in document_classes:
    info = object_class.base_info.item_info
    # The properties a new object of the class must have, other than built-in ones.
    required = [properties[p.property_def].name
                for p in object_class.associated_property_defs
                if p.is_required and p.property_def in properties
                and not properties[p.property_def].is_predefined]
    print(f"  {info.obj_id.item_id.internal_id} {info.name}; "
          f"required: {', '.join(required) or '-'}")

Source: python/recipes/vault_structure.py

# Code that must work in many vaults refers to classes and properties by alias, not ID.
# The answer is -1 when no item, or more than one, has the alias.
vault = client.stub(IRPCVault_pb2_grpc.IRPCVaultStub)
class_id = vault.GetMetadataStructureItemIdByAlias(
    IRPCVault_pb2.GetMetadataStructureItemIdByAliasRequest(
        item_type=enums.METADATA_STRUCTURE_ITEM_OBJECT_CLASS, alias=class_alias)).id
property_id = vault.GetMetadataStructureItemIdByAlias(
    IRPCVault_pb2.GetMetadataStructureItemIdByAliasRequest(
        item_type=enums.METADATA_STRUCTURE_ITEM_PROPERTY_DEF, alias=property_alias)).id
print(f"Class alias {class_alias}: ID {class_id}")
print(f"Property alias {property_alias}: ID {property_id}")

Source: python/recipes/vault_structure.py

C#

// Object types. GetObjectTypes returns the real object types, not the value lists.
var objectTypes = new IRPCObjectTypes.IRPCObjectTypesClient(connection.CallInvoker);
var types = (await objectTypes.GetObjectTypesAsync(new GetObjectTypesRequest())).ObjectTypes;
Console.WriteLine($"Object types: {types.Count}");
foreach (var type in types)
    Console.WriteLine($"  {type.Id} {type.NamePlural}");

// Property definitions and classes.
var propertyDefs = new IRPCPropertyDefs.IRPCPropertyDefsClient(connection.CallInvoker);
var properties = (await propertyDefs.GetPropertyDefsAsync(new GetPropertyDefsRequest()))
    .PropertyDefs.ToDictionary(p => p.Id);
Console.WriteLine($"Property definitions: {properties.Count}");

var classes = (await propertyDefs.GetObjectClassesAndGroupsAsync(new GetObjectClassesAndGroupsRequest()))
    .Classes;
var documentClasses = classes.Where(c => c.ObjectType == 0).ToList();
Console.WriteLine($"Document classes: {documentClasses.Count}");
foreach (var objectClass in documentClasses)
{
    var info = objectClass.BaseInfo.ItemInfo;
    // The properties a new object of the class must have, other than built-in ones.
    var required = objectClass.AssociatedPropertyDefs
        .Where(p => p.IsRequired)
        .Select(p => properties.GetValueOrDefault(p.PropertyDef))
        .Where(def => def is { IsPredefined: false })
        .Select(def => def!.Name);
    Console.WriteLine($"  {info.ObjId.ItemId.InternalId} {info.Name}; " +
        $"required: {string.Join(", ", required.DefaultIfEmpty("-"))}");
}

Source: csharp/Recipes/VaultStructure.cs

// Code that must work in many vaults refers to classes and properties by alias, not ID.
// The answer is -1 when no item, or more than one, has the alias.
var vault = new IRPCVault.IRPCVaultClient(connection.CallInvoker);
var classId = await vault.GetMetadataStructureItemIdByAliasAsync(
    new GetMetadataStructureItemIdByAliasRequest
    {
        ItemType = MetadataStructureItem.ObjectClass,
        Alias = classAlias,
    });
var propertyId = await vault.GetMetadataStructureItemIdByAliasAsync(
    new GetMetadataStructureItemIdByAliasRequest
    {
        ItemType = MetadataStructureItem.PropertyDef,
        Alias = propertyAlias,
    });
Console.WriteLine($"Class alias {classAlias}: ID {classId.Id}");
Console.WriteLine($"Property alias {propertyAlias}: ID {propertyId.Id}");

Source: csharp/Recipes/VaultStructure.cs

Go

// Object types. GetObjectTypes returns the real object types, not the value lists.
types, err := mfiles.NewIRPCObjectTypesClient(client.Conn).GetObjectTypes(ctx,
	&mfiles.GetObjectTypesRequest{})
if err != nil {
	return err
}
fmt.Printf("Object types: %d\n", len(types.GetObjectTypes()))
for _, objectType := range types.GetObjectTypes() {
	fmt.Printf("  %d %s\n", objectType.GetId(), objectType.GetNamePlural())
}

// Property definitions and classes.
propertyDefs := mfiles.NewIRPCPropertyDefsClient(client.Conn)
defs, err := propertyDefs.GetPropertyDefs(ctx, &mfiles.GetPropertyDefsRequest{})
if err != nil {
	return err
}
properties := map[int32]*mfiles.PropertyDef{}
for _, def := range defs.GetPropertyDefs() {
	properties[def.GetId()] = def
}
fmt.Printf("Property definitions: %d\n", len(properties))

classes, err := propertyDefs.GetObjectClassesAndGroups(ctx,
	&mfiles.GetObjectClassesAndGroupsRequest{})
if err != nil {
	return err
}
var documentClasses []*mfiles.ObjectClass
for _, class := range classes.GetClasses() {
	if class.GetObjectType() == 0 {
		documentClasses = append(documentClasses, class)
	}
}
fmt.Printf("Document classes: %d\n", len(documentClasses))
for _, class := range documentClasses {
	info := class.GetBaseInfo().GetItemInfo()
	// The properties a new object of the class must have, other than built-in ones.
	var required []string
	for _, associated := range class.GetAssociatedPropertyDefs() {
		def, ok := properties[associated.GetPropertyDef()]
		if associated.GetIsRequired() && ok && !def.GetIsPredefined() {
			required = append(required, def.GetName())
		}
	}
	if len(required) == 0 {
		required = []string{"-"}
	}
	fmt.Printf("  %d %s; required: %s\n", info.GetObjId().GetItemId().GetInternalId(),
		info.GetName(), strings.Join(required, ", "))
}

Source: go/recipes/vault_structure/main.go

// Code that must work in many vaults refers to classes and properties by alias, not ID.
// The answer is -1 when no item, or more than one, has the alias.
vault := mfiles.NewIRPCVaultClient(client.Conn)
class, err := vault.GetMetadataStructureItemIdByAlias(ctx,
	&mfiles.GetMetadataStructureItemIdByAliasRequest{
		ItemType: mfiles.MetadataStructureItem_METADATA_STRUCTURE_ITEM_OBJECT_CLASS,
		Alias:    classAlias,
	})
if err != nil {
	return err
}
property, err := vault.GetMetadataStructureItemIdByAlias(ctx,
	&mfiles.GetMetadataStructureItemIdByAliasRequest{
		ItemType: mfiles.MetadataStructureItem_METADATA_STRUCTURE_ITEM_PROPERTY_DEF,
		Alias:    propertyAlias,
	})
if err != nil {
	return err
}
fmt.Printf("Class alias %s: ID %d\n", classAlias, class.GetId())
fmt.Printf("Property alias %s: ID %d\n", propertyAlias, property.GetId())

Source: go/recipes/vault_structure/main.go

Output for a normal user of the M-Files Sample Vault, which defines no aliases:

Object types: 11
  0 Documents
  9 Document collections
  10 Assignments
  15 
  19 Shares
  ...
  156 Employees
Property definitions: 143
Document classes: 31
  0 Document; required: -
  1 Other Document; required: -
  2 Purchase Invoice; required: Document date
  4 Contract or Agreement; required: Agreement title, Agreement type, Owner, Document date
  5 Proposal; required: Proposal title, Document date, Proposal number
  ...
Class alias MF.CL.Document: ID -1
Property alias MF.PD.Keywords: ID -1

Pitfalls

See also