Generic Cells

There are situations where we want to retrieve the data from a field given its name alone, without knowing the type of the cell. For example, when we have thousands of cell types and we want to write a common routine to retrieve the value of a given field, it will be too complicated to implement due to the complex schema.

To address this issue, we can use the ICell or ICellAccessor interface. ICell is an interface implemented by all the strongly-typed cell types specified by TSL, and so is ICellAccessor for the strongly-typed accessor counterparts. They provide generic manipulation methods on cells. Operations performed through such methods will be routed to the corresponding data fields of the strongly-typed cell/accessor object. We can access a cell's fields through a few unified Get/Set/Append interfaces, or select the fields tagged with certain attributes with SelectFields/EnumerateValues.

Basic usage #

The data access interfaces of generic cells/accessors are similar to those of strongly-typed cells. These interfaces are provided by Trinity.Core. For local data accessing, Trinity.Storage.LocalMemoryStorage exposes LoadGenericCell, SaveGenericCell, UseGenericCell, and NewGenericCell; for remote data accessing, Trinity.Storage.MemoryCloud exposes LoadGenericCell and SaveGenericCell. For generic cell enumeration, Trinity.Storage.LocalMemoryStorage provides GenericCell_Selector and its accessor counterpart GenericCellAccessor_Selector.

We can call Global.LocalStorage.LoadGenericCell with a cell id to load an ICell. Alternatively, we can allocate a new one by calling Global.LocalStorage.NewGenericCell. Note that in NewGenericCell, we must specify the type of the underlying strongly-typed cell by providing a CellType string. Incorrect cell type will make the method throw an exception. With a generic cell obtained, we can use ICell.GetField<T>() to retrieve a field (where T is the desired output type) or ICell.SetField() to push a field back into the cell. ICell.AppendToField() treats a field as a container and try to append the given value to its end. The three interfaces manipulate a data field by name. It means that we have to know the exact name of the field.

These interfaces handle data type conversions automatically --- it tries to find the most suitable type if the desired data type is not exactly the same as the type of the field. All the data types can be converted into strings. For simple fields like int, bool, they will be converted using the .NET built-in ToString() interface with the default format. For complex data types, such as lists, arrays, substructures, and cells, they will be serialized to JSON strings. Arrays and lists will be regarded as native JSON arrays. Following JSON's specification, strings will be escaped. Cells and substructures are regarded as JSON objects and each cell object has an additional member CellID.

All the generated cell and struct classes have a TryParse interface, which deserializes JSON strings back to objects. This is especially useful when we are importing data or having communications over RESTful APIs. The generic cell does not have TryParse as it is an interface rather than a class. To parse a generic cell, we can call the overloaded Global.LocalStorage.NewGenericCell with the JSON string representation of the cell content. An interesting usage is to declare a string field for a cell. It can then be used as a general JSON store, where objects can be deserialized from it.

Accessing the metadata #

Without prior knowledge about the cells, sometimes it is desirable to inspect the structures of the cells as well as the attributes associated with them. The interface ICellDescriptor provides metadata about the cells and their fields. With it we can get the names, attributes, and descriptors of the fields. ICellDescriptor implements IAttributeCollection, so that we can get a collection of attributes associated with a cell. There are two ways to obtain an ICellDescriptor.

  • Both ICell and ICellAccessor implement ICellDescriptor and we can invoke the metadata accessing methods on them.

  • Another way is to use the generated Schema class, which contains static metadata.

Note that, if ICellDescriptor is obtained via the Schema class, it contains only static metadata. We need to use the runtime objects if we need the information about the cells they are associated with. Calling ICell.GetFieldNames will return the names of all available fields in a particular cell, while the static one obtained from the Schema class returns all the fields defined in TSL.

Generic programming #

We can leverage the attributes to do generic programming. Attributes can be used to specify the purpose of the fields. For example, we can mark a List<CellId> storing the neighbors of a graph node with an attribute [GraphEdge], so that a graph traversal library can recognize the field and enumerate the neighbors without knowing the field name.

To select the fields tagged with a given attribute, call ICell.SelectFields<T>(). The result is a list of name-value pairs indicating the name and value of each field. Automatic type conversion is performed on each of the fields. The method throws an exception when a field cannot be automatically converted. It is desirable that the data provider and the computation module agree on the data type of the field tagged by a specific attribute and the semantics of the attribute. For example, [GraphEdge] in our example implies that the field should be convertible to a container of CellId.

Furthermore, to simplify the handling of different containers, we can use ICell.EnumerateValues<T>(). The semantics of this method is to select the fields according to the specified attribute and optionally with the attribute value, treat each of the resulting fields as a container of T, and enumerate its elements. If a cell has two fields with type List<CellId> and CellId respectively, both tagged with [GraphEdge], where the former is a list of neighbors and the latter is a single edge, EnumerateValues<long>("GraphEdge") will enumerate cell ids from both fields, like a generic "flatten" operation.