Además de los campos tradicionales de texto y números, Dataverse permite almacenar archivos directamente en registros. Ya sea a través de Notas (annotations), campos de tipo File, o campos de tipo Image, entender cómo trabajar con archivos desde plugins te da flexibilidad para automatizar procesos documentales.
Objetivos de aprendizaje
- Entender las diferentes formas de almacenar archivos en Dataverse
- Crear y leer notas con archivos adjuntos
- Trabajar con campos de tipo File e Image
- Conocer las limitaciones y alternativas para archivos grandes
Opciones de almacenamiento de archivos
Dataverse ofrece varias formas de almacenar archivos, cada una con sus características:
Notas (Annotations): La forma tradicional. Se crea un registro en la entidad "annotation" asociado a cualquier registro del sistema. Soporta archivos hasta aproximadamente 128 MB, aunque los archivos grandes consumen rápidamente tu cuota de almacenamiento.
Campos File: Campos específicos de tipo archivo que se agregan directamente a una entidad. Cada campo puede configurarse hasta 128 MB. Es más moderno que las notas y permite tener múltiples campos de archivo en la misma entidad.
Campos Image: Similares a los campos File pero específicos para imágenes. Soportan algunas optimizaciones como miniaturas automáticas. Límite de 30 MB.
Azure Blob Storage: Para archivos muy grandes o volúmenes altos, la mejor opción es almacenar en Azure Blob Storage y guardar solo la URL o referencia en Dataverse. Esto no consume cuota de Dataverse y no tiene límites prácticos de tamaño.
Trabajando con Notas (Annotations)
Las notas son la forma más flexible de adjuntar archivos porque funcionan con cualquier entidad del sistema sin modificar el esquema.
Crear una nota con archivo adjunto
public Guid CrearNotaConArchivo(
IOrganizationService service,
EntityReference registroRelacionado,
string nombreArchivo,
byte[] contenidoArchivo,
string tipoMime)
{
Entity nota = new Entity("annotation");
nota["subject"] = "Documento adjunto";
nota["notetext"] = "Archivo generado automáticamente por el sistema";
nota["objectid"] = registroRelacionado;
nota["filename"] = nombreArchivo;
nota["mimetype"] = tipoMime;
nota["documentbody"] = Convert.ToBase64String(contenidoArchivo);
return service.Create(nota);
}
// Uso:
byte[] pdfBytes = GenerarPdf(datos);
Guid notaId = CrearNotaConArchivo(
service,
new EntityReference("account", cuentaId),
"Informe.pdf",
pdfBytes,
"application/pdf"
);
El campo documentbody espera el contenido en Base64. Esto aumenta el tamaño aproximadamente un 33%, así que un archivo de 10 MB ocupará cerca de 13 MB en almacenamiento.
Leer una nota con archivo
public byte[] ObtenerArchivoDeNota(IOrganizationService service, Guid notaId)
{
Entity nota = service.Retrieve("annotation", notaId,
new ColumnSet("filename", "documentbody", "mimetype"));
string base64 = nota.GetAttributeValue<string>("documentbody");
if (string.IsNullOrEmpty(base64))
return null;
return Convert.FromBase64String(base64);
}
Campos de tipo File
Los campos File usan un API diferente basado en bloques para manejar archivos grandes de forma eficiente.
Subir un archivo
public void SubirArchivo(
IOrganizationService service,
string nombreEntidad,
Guid registroId,
string nombreCampo,
string nombreArchivo,
byte[] contenido)
{
// Paso 1: Inicializar la subida
InitializeFileBlocksUploadRequest initRequest = new InitializeFileBlocksUploadRequest
{
Target = new EntityReference(nombreEntidad, registroId),
FileAttributeName = nombreCampo,
FileName = nombreArchivo
};
InitializeFileBlocksUploadResponse initResponse =
(InitializeFileBlocksUploadResponse)service.Execute(initRequest);
string token = initResponse.FileContinuationToken;
// Paso 2: Subir en bloques de 4 MB
int blockSize = 4 * 1024 * 1024;
int blockNumber = 0;
List<string> blockIds = new List<string>();
for (int offset = 0; offset < contenido.Length; offset += blockSize)
{
int length = Math.Min(blockSize, contenido.Length - offset);
byte[] bloque = new byte[length];
Array.Copy(contenido, offset, bloque, 0, length);
string blockId = Convert.ToBase64String(
BitConverter.GetBytes(blockNumber++));
UploadBlockRequest uploadRequest = new UploadBlockRequest
{
FileContinuationToken = token,
BlockId = blockId,
BlockData = bloque
};
service.Execute(uploadRequest);
blockIds.Add(blockId);
}
// Paso 3: Confirmar la subida
CommitFileBlocksUploadRequest commitRequest = new CommitFileBlocksUploadRequest
{
FileContinuationToken = token,
FileName = nombreArchivo,
MimeType = ObtenerMimeType(nombreArchivo),
BlockList = blockIds.ToArray()
};
service.Execute(commitRequest);
}
Descargar un archivo
public byte[] DescargarArchivo(
IOrganizationService service,
string nombreEntidad,
Guid registroId,
string nombreCampo)
{
InitializeFileBlocksDownloadRequest initRequest =
new InitializeFileBlocksDownloadRequest
{
Target = new EntityReference(nombreEntidad, registroId),
FileAttributeName = nombreCampo
};
InitializeFileBlocksDownloadResponse initResponse =
(InitializeFileBlocksDownloadResponse)service.Execute(initRequest);
string token = initResponse.FileContinuationToken;
long fileSize = initResponse.FileSizeInBytes;
// Descargar en bloques
List<byte> todosLosBytes = new List<byte>();
long offset = 0;
int blockSize = 4 * 1024 * 1024;
while (offset < fileSize)
{
DownloadBlockRequest downloadRequest = new DownloadBlockRequest
{
FileContinuationToken = token,
BlockLength = blockSize,
Offset = offset
};
DownloadBlockResponse downloadResponse =
(DownloadBlockResponse)service.Execute(downloadRequest);
todosLosBytes.AddRange(downloadResponse.Data);
offset += downloadResponse.Data.Length;
}
return todosLosBytes.ToArray();
}
Consideraciones de rendimiento
Las operaciones de archivos son costosas en términos de tiempo y recursos. Algunas recomendaciones:
Evita archivos grandes en plugins síncronos. Si necesitas procesar archivos pesados, hazlo en un plugin asíncrono o en una Azure Function.
Para volúmenes altos, considera Azure Blob Storage. En lugar de guardar el archivo en Dataverse, súbelo a Blob Storage y guarda solo la URL. Esto reduce costos de almacenamiento y mejora rendimiento.
Las notas son más simples pero menos eficientes. Para archivos pequeños (menos de 5 MB), las notas funcionan bien. Para archivos más grandes, los campos File con su API de bloques son más eficientes.
Puntos clave
- Notas (annotation) para archivos asociados a cualquier registro, hasta ~128 MB
- Campos File para archivos directos en entidades, hasta 128 MB configurable
- documentbody usa Base64, lo que aumenta el tamaño ~33%
- La API de bloques es más eficiente para archivos grandes
- Para volúmenes altos o archivos muy grandes, considera Azure Blob Storage