ice.NET Key Concepts: Files and Vaults

Files and File Content

The built-in object type Core.File (display name: File) provides the functionality of storing unstructured content. All instances of Core.File or of one of its subtypes manage a ordered sequence of file versions. Each Version points to exactly one FileContent. The FileContent record in the database represents a chunk of binary data that is identified by a UUID and that is stored in a place determined by the type and configuration of its containing vault.

The structure and cardinalities are as follows:

The diagram indicates that multiple versions of the same or different instances of Core.File can share the same FileContent. This is especially useful for copying an object instance without increasing the vault space required to store the binary content.

Vaults

Vaults are places where unstructured file content is stored. Each vault has a unique name in the scope of an ice.NET system and each file content stored in the vault has a universally unique identification (UUID). Vaults can be created (and modified and deleted) using the standard administration tools (ice.NET Studio SmartClient, Web UI) and the Vault API represented by the CLR interface PDTec.IceNet.Core.Database.IVault.

There are several types of vaults, mainly separated in two categories:

  • Database vaults
  • File vaults

Database Vaults

Database vaults store their file contents – as the name suggests – in the same database that also contains the structured data (objects, relationships and attributes).

Advantages:

  • No configuration necessary (and possible).
  • Vault access takes place within database transaction boundaries, always 100% consistent.
  • Backup of the vault content is automatically included in every database backup.

Disadvantages:

  • Vault content increases database size.
  • Requests to vault content always have to go through the application server, vault has no separate interface.

File Vaults

The category of file vaults represents all other types, i.e. all types of non-database vaults. The Version and FileContent records are still stored in the database for consistency reasons but the actual chunk of binary data is stored elsewhere.

Advantages:

  • The vault content does not add to the database size.
  • The storage and access methods can be separated from the application server and delegated to a specific vault service in order to reduce the network load on the application server due to file transfer.

Disadvantages:

  • Filesystem vaults require some configuration.
  • Vault access is separated from database transactions. This can lead to inconsistencies between vault content and database content due to access failures and transactions that were rolled back. The ice.NET implementation ensures that in case of failure no checked-in file is lost but it cannot technically avoid that unreferenced binary content (“dead data”) remains stored under certain circumstances. A vault admin command (“tidy”) helps to detect and remove dead data from the vault periodically.
  • Database backup is not sufficient to save the state of an ice.NET system. The vault directories must be backed-up as well.

The term “filesystem” vault is for historical reasons (and the fact that ultimately the binary data is store somewhere on a file system). However, technically “filesystem” means that the binary data is stored outside the database and accessed through a vault protocol. The methods of the ice.NET vault protocol are defined in the CLR interface PDTec.IceNet.Core.Database.IVaultProtocol. Any valid implementation of this interface can serve as a vault and can be plugged into an ice.NET system by configuration.

Example 1: Working with Files

This example explains how to use the PDTec.IceNet.Core.Model.File.IFile interface to read and write file content.

The following code shows how to create a file object and to store binary content into the built-in vault System.Database:

IObjType pFileObjType = m_pRepository.GetObjTypeByName("Core.File");
byte[] aContent = Encoding.ASCII.GetBytes("FILECONTENT");

Repository.ExecuteTransaction(delegate()
{
    // Create a file object...
    IFile pFile = pFileObjType.Create<IFile>(m_pRepository.RootFolder, "F1", "");
    
    // Store content (implicitly creates a first content version)......
    pFile.WriteContent(true, "", "System.Database", "f1.txt", aContent);
});

The following code shows how read binary content from a file:

IFile pFile = Repository.Get<IFile>(objectId);
byte[] aContent;

// Reads file content from the current (most recent) file version into buffer...
pFile.ReadContent(out aContent);

// Gets the file size of the current version...
long size = pFile.GetContentSize();

// Reads file content with offset and partial size parameters...
pFile.ReadContentPart(0, (int)size, out aContent);

The following code shows how to use the AppendContent function to subsequently add content to a file:

IObjType pFileObjType = m_pRepository.GetObjTypeByName("Core.File");

byte[] aContent0 = Encoding.ASCII.GetBytes("F");
byte[] aContent1 = Encoding.ASCII.GetBytes("1");

Repository.ExecuteTransaction(delegate()
{
    // Create a file object...
    IFile pFile = pFileObjType.Create<IFile>(m_pRepository.RootFolder, "F1", "");
    
    // Store initial content (implicitly creates a first content version)...
    pFile.WriteContent(true, "description", "System.Database", "f1.txt", aContent0);
                        
    // Append content to the current version... 
    pFile.AppendContent(aContent1);
});

Example 2: Working with Vaults and FileContent

This example explains how to work with vaults and file content directly. This can be useful to implement a transactional safe Web uploading process that enables to upload large files in parts, where the whole uploading process cannot be encapsulated in a single database transaction for HTTP request memory size and timeout reasons:

  • Create a FileContent record in the database and retrieve its UUID.
  • Upload the file in several steps by appending to the FileContent.
  • Create the File object and connect the FileContent.

This procedure ensures that if the upload process fails at one point, the file object does not appear in the database at all. This avoids broken file objects. Unconnected FileContents can be identified and removed easily by a vault utility.

The following code shows how to create a file in several steps and how the individual transactions can be separated from each other. If the vault is not a database vault, the transaction scopes around the IVaultProtocol calls are not necessary.

byte[] aContent = Encoding.ASCII.GetBytes("1234567890");

IVault pVault = Repository.GetVaultByName("System.Database");

string contentUuid = null;

//
// First step: create a FileContent record in the database...
//

string hash = FileUtils.CalculateHash(aContent);

Repository.ExecuteTransaction(delegate
{
    contentUuid = pVault.CreateFileContent(aContent.Length, hash).Uuid;
});

//
// Second step: Use the IVaultProtocol of the vault to upload the content...
//

IVaultProtocol pProtocol = pVault.GetProtocol();

Repository.ExecuteTransaction(delegate()
{
    pProtocol.CreateFileContent(contentUuid, aContent.Take(5).ToArray());
});

Repository.ExecuteTransaction(delegate()
{
    pProtocol.AppendFileContent(contentUuid, aContent.Skip(5).ToArray());
});

//
// Final step: Create the File object and connect the FileContent as a new content version...
//

IFile pFile = null;

Repository.ExecuteTransaction(delegate
{
    pFile = Repository.GetObjTypeByName("Core.File").Create<IFile>(Repository.RootFolder, "F1", "");

    pFile.ConnectContent(true, "", "test.txt", contentUuid);
});


Using Files in the application model

The Core.File object type provides the basic content management functionality. It is recommended to define application-specific document object types to reflect the actual usage of this content management functionality. The following image suggests two ways to implement domain-specific document types:

In the MyApp model the Core.File type is used as a supertype of a domain-specific type MyApp.Attachment. The MyApp.Attachment inherits the content management functionality and can additionaly define attributes and BO methods to extend the Core.File functionality in a domain-specific way.

In the YourApp model a more complex document type is created that is not a subtype of Core.File but instead owns 1:n files. A reason for this model could be that one logical document contains several binary representations in different formats. The business logic of YourApp.Document is now responsible for managing the collection of files attached to the document object.