GoodBooks GB5 · DMS

TAttachment
Path Resolution Engine

Template-driven folder and filename generation from 18 system tokens. Two-phase resolution for pre-save uploads.

Work Stream 1 — Template Engine Work Stream 2 — Alfresco Migration Work Stream 3 — Bulk Ingestion
18
System Tokens
2
Resolution Phases
10+
Entity Types (Migration)
14
Stored Procedures

Interactive Demo

Live Token Resolver

Edit the fields below to watch the resolved folder path and filename update in real time. All 18 system tokens plus dynamic context tokens are shown.

⏳ Phase 1 ObjectId = 0 → RowGuid used as placeholder · ISPATHDEFERRED = 1
Folder
Filename
StorageKey (CMSID)
✓ Phase 2 After entity save — RowGuid replaced with real ObjectId · ISPATHDEFERRED = 0
Folder
Filename
StorageKey (CMSID) — final
Phase 2 trigger: After SP_RESOLVE_ATTACHMENTS_BATCH, IAttachmentPathResolutionService.ResolvePhase2Async() calls SP_ATTACHMENT_GET_DEFERRED → IStorageProvider.MoveAsync() → SP_ATTACHMENT_UPDATE_RESOLVED_PATH (TVP bulk update).

Work Stream 1

Upload Service Pipeline

Seven steps from file stream to TATTACHMENT row. MD5 is computed in BLL before the stream is consumed by storage.

🔢
01
Next SlNo + Version
SP_ATTACHMENT_GET_NEXT_SLNO_VERSION scoped by ObjectId or RowGuid
→
🧩
02
Load Template
SP_ATTACHMENT_LOAD_TEMPLATE → joins DocumentSet + Module
→
⚙️
03
Resolve Paths
TemplateResolutionEngine → folder + filename + display name
→
🔐
04
MD5 + Size
Buffer to MemoryStream → MD5.HashData() via stackalloc
→
💾
05
Save to Storage
IStorageProvider.SaveAsync(folder + filename, mimeType)
→
🗄️
06–07
Insert TATTACHMENT
SP_ATTACHMENT_INSERT → SCOPE_IDENTITY() → AttachmentId
// AttachmentOptionType from IStorageProvider.ProviderType
private static byte ProviderToOption(StorageProviderType p) => p switch {
    StorageProviderType.S3      => 2,   // AmazonS3
    StorageProviderType.Network => 3,   // FileBased
    _                           => 3
};

// MD5 computed before storage consumes the stream
using var ms = new MemoryStream();
await fileStream.CopyToAsync(ms, ct).ConfigureAwait(false);
Span<byte> hash = stackalloc byte[16];
MD5.HashData(ms.GetBuffer().AsSpan(0, (int)ms.Length), hash);
var contentHash = Convert.ToHexString(hash).ToLowerInvariant();

Deferred Path Resolution

Phase 1 → Phase 2 Flow

Files uploaded before entity save use a RowGuid as the ObjectId placeholder. Phase 2 atomically moves the files and updates TATTACHMENT within the entity save transaction.

Phase 1 — Upload before save
ACME/PURCHASEORDER/2026/07/
a3f91b2c.../VQ_001_1.pdf
ISPATHDEFERRED = 1 · ObjectId = 0
→
Phase 2 — After entity save
ACME/PURCHASEORDER/2026/07/
842/VQ_001_1.pdf
ISPATHDEFERRED = 0 · ObjectId = 842
Trigger
SP_RESOLVE_ATTACHMENTS_BATCH
→ AttachmentResolutionService
Move
IStorageProvider.MoveAsync()
per deferred file
Bulk update
SP_ATTACHMENT_UPDATE_RESOLVED_PATH
TVP — single round-trip

Work Stream 2

Alfresco Migration Flow

Legacy Alfresco attachments have no TATTACHMENT rows. Migration discovers them via CMIS API, downloads, saves to new storage, and inserts rows — one ObjectTypeId at a time.

🗄️
MENTITY
DBOBJECT
DBOBJECTFIELDS
→
📋
CMIS Query
/cmis/atom/query
→
📄
Atom XML
Parse nodeIds
→
⬇️
Download
/nodes/{id}/content
→
🔐
MD5 + Size
MemoryStream
→
💾
IStorageProvider
SaveAsync
→
✅
SP_ATTACHMENT
_INSERT
Archival path format
{EntityCode}/ALF_MIGRATED/{ObjectId}/{nodeId}{ext}
e.g. PURCHASEORDER/ALF_MIGRATED/1201/a3f91b2c.pdf
Log status codes
0 Pending
1 InProgress
2 Done
3 Failed — continues to next file
4 Skipped — DryRun=true, no writes

Work Stream 3

Bulk Ingestion Modes

Two modes delegate to the same IAttachmentUploadService — all template resolution, MD5, and TATTACHMENT insertion follow the identical path as a single upload.

Manifest CSV
Detected when manifest.csv found at source folder root or path explicitly provided.
FilePath,ObjectTypeId,ObjectId,DocSetDetailId,...
/import/po_1201.pdf,101,1201,4,Vendor Quote
/import/po_1201_inv.pdf,101,1201,5,Invoice
Folder Convention
Used when no manifest found. EntityCode resolved to ObjectTypeId via MENTITY.ENTITYCODE.
/attachments/
  PURCHASEORDER/    ← EntityCode
    1201/           ← ObjectId
      quote.pdf
      invoice.pdf
Job counters are flushed every 10 files to TBULK_INGESTION_JOB. Final status = Done (2) if any files succeeded; Failed (3) only if all files failed.

Reference

All 18 System Tokens

Resolved by SystemTokenResolver. Context tokens (dot-notation) resolved by DictionaryTokenResolver from the request's ContextDictionary.

TokenSourceExampleNotes
_tenantCodeMCLIENT.CLIENTCODEACMESanitised
_tenantNameMCLIENT.CLIENTNAMEAcme CorpSanitised
_yearUploadedOn.Year2026
_monthUploadedOn.Month072-digit padded
_yyyymmUploadedOn202607
_yyyymmddUploadedOn20260704
_moduleCodeTemplate.ModuleCodeMMFrom SP_ATTACHMENT_LOAD_TEMPLATE
_moduleNameTemplate.ModuleNameMaterials
_entityCodeMENTITY.ENTITYCODEPURCHASEORDER
_entityNameMENTITY.ENTITYNAMEPurchase Order
_docTypeCodeTemplate.DocumentTypeCodeVQ
_docTypeNameTemplate.DocumentTypeNameVendor Quote
_docSetCodeTemplate.DocumentSetCodeDS_PO_001
_slNoContext.SlNo0013-digit padded
_versionContext.Version1
_extFileExtensionpdfNo dot, lowercased
_objectIdContext.ObjectId (or RowGuid in Phase 1)12345 or a3f91b2c...Phase 1: RowGuid placeholder
_rowGuidContext.RowGuida3f91b2c-d4e5...FE-generated UUID
{custom.key}ContextDictionaryPO-2026-0842Dot-notation; e.g. vendor.vendorCode