Skip to content

Instantly share code, notes, and snippets.

@bcapetillo
Last active January 5, 2026 16:05
Show Gist options
  • Select an option

  • Save bcapetillo/9835924289f419a52f58ecd26c3e7f0e to your computer and use it in GitHub Desktop.

Select an option

Save bcapetillo/9835924289f419a52f58ecd26c3e7f0e to your computer and use it in GitHub Desktop.

📘 Guía Simplificada: Oracle UDT en C#

🎯 ¿Qué son los tipos UDT de Oracle?

Los UDT (User Defined Types) son tipos personalizados en Oracle:

  • Objetos (SZTYOTIOL_OBJ) - Similar a una clase
  • Arreglos (SZTYOTIOL_ARR) - Colección de objetos
-- Tipo Objeto
CREATE TYPE SZTYOTIOL_OBJ AS OBJECT (
    SZTYOTIOL_TERM_CODE VARCHAR2(6),
    SZTYOTIOL_PIDM NUMBER(8)
);

-- Tipo Arreglo
CREATE TYPE SZTYOTIOL_ARR AS TABLE OF SZTYOTIOL_OBJ;

🚀 3 Formas de Implementar Oracle UDT

📌 Opción 1: Mapeo Automático con OracleUdtBase (⭐ RECOMENDADO)

✅ Cuándo usar: Mayoría de casos, tipos simples con muchas propiedades

// ActualizarTiraAutomatizadaRequestDto.cs

public class ActualizarTiraAutomatizadaRequestDto
{
    // ✅ Hereda de OracleUdtBase = Mapeo automático por reflexión
    [OracleCustomTypeMapping("SZTYOTIOL_OBJ")]
    public class TiraOracleObj : OracleUdtBase
    {
        [OracleObjectMapping("SZTYOTIOL_TERM_CODE")]
        public string? TermCode { get; set; }
        
        [OracleObjectMapping("SZTYOTIOL_PIDM")]
        public int? Pidm { get; set; }
        
        // ✨ No necesitas escribir FromCustomObject/ToCustomObject
        // ✨ La clase base lo hace automáticamente con reflexión
        
        public static TiraOracleObj Null => CreateNull<TiraOracleObj>();
    }
    
    public string TermCode { get; set; }
    public TiraOracleObj? Tira { get; set; }
    public TiraOracleObj[]? TirasActivas { get; set; }
}

// Factories genéricas (al final del archivo) - Solo 2 líneas!
[OracleCustomTypeMapping("SZTYOTIOL_OBJ")]
public class TiraOracleObjFactory : OracleUdtFactory<ActualizarTiraAutomatizadaRequestDto.TiraOracleObj> { }

[OracleCustomTypeMapping("SZTYOTIOL_ARR")]
public class TiraOracleArrayFactory : OracleUdtArrayFactory<ActualizarTiraAutomatizadaRequestDto.TiraOracleObj> { }

💡 Ventajas:

  • ✅ Código ultra compacto (solo declaras propiedades)
  • ✅ Perfecto para 10+ propiedades
  • ✅ Reflexión automática lee [OracleObjectMapping]
  • ✅ No repites código de serialización
  • ✅ Factories genéricas reutilizables (2 líneas vs 15)

📌 Opción 2: Mapeo Manual Completo

✅ Cuándo usar: Control total, transformaciones complejas, performance crítico

public class ActualizarTiraAutomatizadaRequestDto
{
    // ✅ Implementa IOracleCustomType directamente = Control manual
    [OracleCustomTypeMapping("SZTYOTIOL_OBJ")]
    public class TiraOracleObj : IOracleCustomType, INullable
    {
        private bool _isNull;
        
        [OracleObjectMapping("SZTYOTIOL_TERM_CODE")]
        public string? TermCode { get; set; }
        
        [OracleObjectMapping("SZTYOTIOL_PIDM")]
        public int? Pidm { get; set; }
        
        public bool IsNull => _isNull;
        public static TiraOracleObj Null => new TiraOracleObj { _isNull = true };
        
        // ✅ Control manual: escribes toda la lógica
        public void FromCustomObject(OracleConnection con, object pUdt)
        {
            if (!IsNull)
            {
                // Transformación personalizada
                OracleUdt.SetValue(con, pUdt, "SZTYOTIOL_TERM_CODE", 
                    TermCode?.ToUpper() ?? "");
                
                OracleUdt.SetValue(con, pUdt, "SZTYOTIOL_PIDM", Pidm ?? 0);
            }
        }
        
        public void ToCustomObject(OracleConnection con, object pUdt)
        {
            TermCode = (string)OracleUdt.GetValue(con, pUdt, "SZTYOTIOL_TERM_CODE");
            Pidm = Convert.ToInt32(OracleUdt.GetValue(con, pUdt, "SZTYOTIOL_PIDM"));
        }
    }
}

// Factories (igual que Opción 1)

💡 Ventajas:

  • ✅ Control total sobre serialización
  • ✅ Puedes hacer validaciones custom
  • ✅ Transformaciones complejas (mayúsculas, formatos, etc.)
  • ✅ Sin overhead de reflexión (marginal)

⚠️ Desventajas:

  • ❌ Más código para mantener
  • ❌ 40 propiedades = 80 líneas manuales

📌 Opción 3: Híbrido (Base + Override)

✅ Cuándo usar: Mayoría de propiedades automáticas, algunas manuales

public class ActualizarTiraAutomatizadaRequestDto
{
    // ✅ Hereda de base + sobrescribe métodos específicos
    [OracleCustomTypeMapping("SZTYOTIOL_OBJ")]
    public class TiraOracleObj : OracleUdtBase
    {
        [OracleObjectMapping("SZTYOTIOL_TERM_CODE")]
        public string? TermCode { get; set; }
        
        [OracleObjectMapping("SZTYOTIOL_PIDM")]
        public int? Pidm { get; set; }
        
        // Propiedad compleja que necesita lógica especial
        public DateTime? FechaCreacion { get; set; }
        
        // ✅ Sobrescribe solo FromCustomObject para lógica custom
        public override void FromCustomObject(OracleConnection con, object pUdt)
        {
            if (!IsNull)
            {
                // Llama a la base para propiedades automáticas
                base.FromCustomObject(con, pUdt);
                
                // Lógica manual para campos especiales
                if (FechaCreacion.HasValue)
                {
                    OracleUdt.SetValue(con, pUdt, "SZTYOTIOL_FECHA", 
                        FechaCreacion.Value.ToString("yyyy-MM-dd"));
                }
            }
        }
        
        public static TiraOracleObj Null => CreateNull<TiraOracleObj>();
    }
}

💡 Ventajas:

  • ✅ Lo mejor de ambos mundos
  • ✅ Automático para 95% de propiedades
  • ✅ Manual solo donde lo necesitas
  • ✅ Código limpio y flexible

✅ Solución Simple: TODO en un Archivo

📁 Estructura Recomendada

// ActualizarTiraAutomatizadaRequestDto.cs

public class ActualizarTiraAutomatizadaRequestDto
{
    // ✨ Clase anidada que sirve para AMBOS propósitos:
    // 1. DTO para recibir JSON en el API
    // 2. UDT para pasar a Oracle
    [OracleCustomTypeMapping("SZTYOTIOL_OBJ")]
    public class TiraOracleObj : IOracleCustomType, INullable
    {
        [OracleObjectMapping("SZTYOTIOL_TERM_CODE")]
        public string? TermCode { get; set; }
        
        [OracleObjectMapping("SZTYOTIOL_PIDM")]
        public int? Pidm { get; set; }
        
        // === Oracle UDT Implementation ===
        private bool _isNull;
        public bool IsNull => _isNull;
        
        public void FromCustomObject(OracleConnection con, object pUdt)
        {
            if (!IsNull)
            {
                OracleUdt.SetValue(con, pUdt, "SZTYOTIOL_TERM_CODE", TermCode ?? "");
                OracleUdt.SetValue(con, pUdt, "SZTYOTIOL_PIDM", Pidm ?? 0);
            }
        }
        
        public void ToCustomObject(OracleConnection con, object pUdt) { }
    }
    
    // === Propiedades del Request ===
    public string TermCode { get; set; }
    public bool DesmarcarTodos { get; set; }
    public TiraOracleObj? Tira { get; set; }           // ✅ Objeto
    public TiraOracleObj[]? TirasActivas { get; set; }  // ✅ Array
}

// === Factories genéricas (al final del mismo archivo) ===
[OracleCustomTypeMapping("SZTYOTIOL_OBJ")]
public class TiraOracleObjFactory : OracleUdtFactory<ActualizarTiraAutomatizadaRequestDto.TiraOracleObj> { }

[OracleCustomTypeMapping("SZTYOTIOL_ARR")]
public class TiraOracleArrayFactory : OracleUdtArrayFactory<ActualizarTiraAutomatizadaRequestDto.TiraOracleObj> { }

� Estructura de Archivos

Archivo 1: OracleUdtBase.cs (Core/Models/Banner/)

using Oracle.ManagedDataAccess.Client;
using Oracle.ManagedDataAccess.Types;

namespace Lince.SERVACAD.Core.Models.Banner;

/// <summary>
/// Clase base para Oracle UDT con serialización automática por reflexión
/// </summary>
public abstract class OracleUdtBase : IOracleCustomType, INullable
{
    private bool _isNull;
    public bool IsNull => _isNull;

    // ✅ Mapeo automático de todas las propiedades con [OracleObjectMapping]
    public virtual void FromCustomObject(OracleConnection con, object pUdt)
    {
        if (!IsNull)
        {
            foreach (var prop in GetType().GetProperties())
            {
                var attrData = prop.GetCustomAttributesData()
                    .FirstOrDefault(a => a.AttributeType == typeof(OracleObjectMappingAttribute));
                
                if (attrData?.ConstructorArguments.Count > 0)
                {
                    string oracleName = attrData.ConstructorArguments[0].Value?.ToString() ?? "";
                    var value = prop.GetValue(this) ?? GetDefaultValue(prop.PropertyType);
                    OracleUdt.SetValue(con, pUdt, oracleName, value);
                }
            }
        }
    }

    public virtual void ToCustomObject(OracleConnection con, object pUdt)
    {
        foreach (var prop in GetType().GetProperties())
        {
            var attrData = prop.GetCustomAttributesData()
                .FirstOrDefault(a => a.AttributeType == typeof(OracleObjectMappingAttribute));
            
            if (attrData?.ConstructorArguments.Count > 0)
            {
                string oracleName = attrData.ConstructorArguments[0].Value?.ToString() ?? "";
                var value = OracleUdt.GetValue(con, pUdt, oracleName);
                prop.SetValue(this, value);
            }
        }
    }

    protected virtual object GetDefaultValue(Type propertyType)
    {
        return propertyType == typeof(string) ? string.Empty :
               propertyType == typeof(int?) ? 0 :
               propertyType == typeof(decimal?) ? 0m :
               propertyType == typeof(DateTime?) ? DateTime.MinValue : 0;
    }

    protected static T CreateNull<T>() where T : OracleUdtBase, new()
    {
        return new T { _isNull = true };
    }
}

/// <summary>
/// Factory genérica para crear instancias de Oracle UDT
/// </summary>
public class OracleUdtFactory<T> : IOracleCustomTypeFactory where T : IOracleCustomType, new()
{
    public IOracleCustomType CreateObject() => new T();
}

/// <summary>
/// Factory genérica para crear arreglos de Oracle UDT
/// </summary>
public class OracleUdtArrayFactory<T> : IOracleArrayTypeFactory where T : IOracleCustomType, new()
{
    public Array CreateArray(int numElems) => new T[numElems];
    public Array? CreateStatusArray(int numElems) => null;
}

Archivo 2: Tu Request DTO (ejemplo con Opción 1)

using Lince.SERVACAD.Core.Models.Banner;

public class ActualizarTiraAutomatizadaRequestDto
{
    [OracleCustomTypeMapping("SZTYOTIOL_OBJ")]
    public class TiraOracleObj : OracleUdtBase
    {
        [OracleObjectMapping("SZTYOTIOL_TERM_CODE")]
        public string? TermCode { get; set; }
        
        [OracleObjectMapping("SZTYOTIOL_PIDM")]
        public int? Pidm { get; set; }
        
        public static TiraOracleObj Null => CreateNull<TiraOracleObj>();
    }
    
    public string TermCode { get; set; }
    public TiraOracleObj? Tira { get; set; }
    public TiraOracleObj[]? TirasActivas { get; set; }
}

// Factories genéricas (heredan de OracleUdtBase)
[OracleCustomTypeMapping("SZTYOTIOL_OBJ")]
public class TiraOracleObjFactory : OracleUdtFactory<ActualizarTiraAutomatizadaRequestDto.TiraOracleObj> { }

[OracleCustomTypeMapping("SZTYOTIOL_ARR")]
public class TiraOracleArrayFactory : OracleUdtArrayFactory<ActualizarTiraAutomatizadaRequestDto.TiraOracleObj> { }

🔧 Cómo Usarlo en el Service (Igual para las 3 opciones)

Service - Pasar Directamente a Oracle

public ActualizarTiraAutomatizadaResponseDto ActualizarTirasAutomatizadas(
    string termCode, 
    int? pidm, 
    string allInd, 
    ActualizarTiraAutomatizadaRequestDto.TiraOracleObj? tira,      // ✅ DTO
    ActualizarTiraAutomatizadaRequestDto.TiraOracleObj[]? tirasActivas)  // ✅ DTO
{
    DataToProcedureBanner procedure = new()
    {
        PackageName = "SZPKEPTMS.PR_ACTUALIZAR_TIRA_AUTO",
        Parameters = new OracleParameter[]
        {
            new("PIC_TERM_CODE", OracleDbType.Varchar2, termCode, ParameterDirection.Input) { Size = 6 },
            new("PIN_PIDM", OracleDbType.Int32, pidm ?? (object)DBNull.Value, ParameterDirection.Input),
            new("PIC_ALL_IND", OracleDbType.Varchar2, allInd, ParameterDirection.Input) { Size = 1 },
            
            // ✅ Pasar directo - TiraOracleObj implementa IOracleCustomType
            new() { 
                ParameterName = "PIA_TIRA", 
                Direction = ParameterDirection.Input, 
                OracleDbType = OracleDbType.Object,     // ← Objeto individual
                UdtTypeName = "SZTYOTIOL_OBJ",          // ← Case-sensitive
                Value = tira ?? (object)DBNull.Value 
            },
            new() { 
                ParameterName = "PIA_TIRAS_ACT", 
                Direction = ParameterDirection.Input, 
                OracleDbType = OracleDbType.Array,      // ← Array
                UdtTypeName = "SZTYOTIOL_ARR",          // ← Case-sensitive
                Value = tirasActivas ?? (object)DBNull.Value 
            },
            
            new("PON_ERROR_CODE", OracleDbType.Int32, ParameterDirection.Output) { Size = 100 },
            new("POC_ERROR_DESC", OracleDbType.Varchar2, ParameterDirection.Output) { Size = 3500 }
        }
    };
    
    return _bannerClient.Execute<ActualizarTiraAutomatizadaResponseDto>(procedure);
}

Manager - Sin Conversiones

public ActualizarTiraAutomatizadaResponseDto ActualizarTirasAutomatizadas(
    ActualizarTiraAutomatizadaRequestDto request)
{
    string allInd = request.DesmarcarTodos ? "S" : "N";
    
    // ✅ Pasar directo - Sin conversiones
    return _service.ActualizarTirasAutomatizadas(
        request.TermCode,
        request.Pidm,
        allInd,
        request.Tira,           // ✅ Directo
        request.TirasActivas    // ✅ Directo
    );
}

🎯 Ejemplo de Uso del API

Request JSON

POST /api/ConfigTiraAutomatizada/ActualizarTirasAutomatizadas
{
  "termCode": "202510",
  "pidm": 123456,
  "desmarcarTodos": false,
  "tira": {
    "termCode": "202510",
    "pidm": 123456
  },
  "tirasActivas": [
    { "termCode": "202510", "pidm": 111111 },
    { "termCode": "202510", "pidm": 222222 }
  ]
}

Flujo Completo (Sin Conversiones)

1. API recibe JSON
   ↓
2. Se deserializa a ActualizarTiraAutomatizadaRequestDto
   ↓
3. Controller → Manager → Service (sin conversiones)
   ↓
4. Service crea OracleParameter con Value = tira
   ↓
5. Oracle llama automáticamente a tira.FromCustomObject()
   ↓
6. Datos serializados a Oracle PL/SQL

⚙️ Puntos Críticos (IMPORTANTE)

1. Nombres Case-Sensitive

// ✅ CORRECTO
[OracleCustomTypeMapping("SZTYOTIOL_OBJ")]      // Exactamente como en Oracle
[OracleObjectMapping("SZTYOTIOL_TERM_CODE")]    // Exactamente como en Oracle

// ❌ INCORRECTO
[OracleCustomTypeMapping("sztyotiol_obj")]      // Oracle no lo encontrará

2. OracleDbType Correcto

// Objeto individual
OracleDbType = OracleDbType.Object,
UdtTypeName = "SZTYOTIOL_OBJ"

// Array de objetos
OracleDbType = OracleDbType.Array,
UdtTypeName = "SZTYOTIOL_ARR"

3. Factories Requeridas

// ✅ Usa factories genéricas reutilizables de OracleUdtBase
[OracleCustomTypeMapping("SZTYOTIOL_OBJ")]
public class TiraOracleObjFactory : OracleUdtFactory<TiraOracleObj> { }

[OracleCustomTypeMapping("SZTYOTIOL_ARR")]
public class TiraOracleArrayFactory : OracleUdtArrayFactory<TiraOracleObj> { }

// ✅ Solo 2 líneas por tipo! (84% menos código que implementar interfaces)

🔍 Verificar Tipos en Oracle

-- Ver tipos UDT
SELECT type_name, typecode 
FROM user_types 
WHERE typecode IN ('OBJECT', 'COLLECTION')
ORDER BY type_name;

-- Ver estructura de un tipo
DESC SZTYOTIOL_OBJ;

-- Ver atributos exactos (case-sensitive)
SELECT attr_name, attr_type_name 
FROM user_type_attrs 
WHERE type_name = 'SZTYOTIOL_OBJ'
ORDER BY attr_no;

🐛 Problemas Comunes

Error Causa Solución
ORA-50028: Invalid parameter binding UdtTypeName incorrecto Verificar mayúsculas exactas
Type mismatch OracleDbType.Object/Array equivocado Object para objetos, Array para arrays
Schema mismatch Atributos no coinciden Verificar nombres con user_type_attrs
PLS-00306: número o tipos de argumentos erróneos OracleDbType incorrecto (Int32 vs Decimal) NUMBER → OracleDbType.Int32 o Decimal

✅ Ventajas de Este Patrón

Aspecto Beneficio
Un solo archivo Todo junto, fácil de mantener
Cero conversiones DTO = UDT, pasa directo a Oracle
Menos clases Solo TiraOracleObj (no necesitas TiraOracleUdt)
Código limpio Manager y Service súper simples
Type-safe IntelliSense completo en todo el flujo

🏭 Factories Genéricas Reutilizables

¿Qué son?

Las factories genéricas están definidas en OracleUdtBase.cs y eliminan la necesidad de escribir código repetitivo.

Antes (Opción Manual - 15 líneas):

[OracleCustomTypeMapping("SZTYOTIOL_OBJ")]
public class TiraOracleObjFactory : IOracleCustomTypeFactory
{
    public IOracleCustomType CreateObject()
    {
        return new ActualizarTiraAutomatizadaRequestDto.TiraOracleObj();
    }
}

[OracleCustomTypeMapping("SZTYOTIOL_ARR")]
public class TiraOracleArrayFactory : IOracleArrayTypeFactory
{
    public Array CreateArray(int numElems)
    {
        return new ActualizarTiraAutomatizadaRequestDto.TiraOracleObj[numElems];
    }
    public Array? CreateStatusArray(int numElems) => null;
}

Ahora (Con Genéricas - 4 líneas):

[OracleCustomTypeMapping("SZTYOTIOL_OBJ")]
public class TiraOracleObjFactory : OracleUdtFactory<ActualizarTiraAutomatizadaRequestDto.TiraOracleObj> { }

[OracleCustomTypeMapping("SZTYOTIOL_ARR")]
public class TiraOracleArrayFactory : OracleUdtArrayFactory<ActualizarTiraAutomatizadaRequestDto.TiraOracleObj> { }

Ventajas

Aspecto Manual Genéricas
Líneas de código 15 4
Código repetitivo Alto Cero
Reutilizable No
Mantenimiento Difícil Fácil
Type-safe

Implementación en OracleUdtBase.cs

public class OracleUdtFactory<T> : IOracleCustomTypeFactory 
    where T : IOracleCustomType, new()
{
    public IOracleCustomType CreateObject() => new T();
}

public class OracleUdtArrayFactory<T> : IOracleArrayTypeFactory 
    where T : IOracleCustomType, new()
{
    public Array CreateArray(int numElems) => new T[numElems];
    public Array? CreateStatusArray(int numElems) => null;
}

Para Nuevos UDTs

Simplemente hereda de las factories genéricas:

// Para cualquier nuevo UDT, solo necesitas 2 líneas:
[OracleCustomTypeMapping("MI_NUEVO_TIPO_OBJ")]
public class MiNuevoTipoFactory : OracleUdtFactory<MiClaseUdt> { }

[OracleCustomTypeMapping("MI_NUEVO_TIPO_ARR")]
public class MiNuevoTipoArrayFactory : OracleUdtArrayFactory<MiClaseUdt> { }

🎯 Resultado: Creas 10 UDTs diferentes = Solo 40 líneas de factories (vs 150 líneas manual)


📋 Checklist para Nuevos UDT

Cuando necesites agregar un nuevo tipo UDT:

  • Crear clase anidada con [OracleCustomTypeMapping("TIPO_OBJ")]
  • Heredar de OracleUdtBase (Opción 1) o implementar IOracleCustomType (Opción 2)
  • Agregar [OracleObjectMapping] a cada propiedad
  • Si es manual: Implementar FromCustomObject() y ToCustomObject()
  • Crear factories genéricas heredando de OracleUdtFactory<T> (2 líneas)
  • Crear factory de array heredando de OracleUdtArrayFactory<T> (2 líneas)
  • Verificar nombres case-sensitive en Oracle (DESC TIPO_OBJ)
  • Usar OracleDbType.Object para objetos, .Array para arrays
  • Probar con datos reales

📊 Comparativa: ¿Cuál Opción Elegir?

Criterio Opción 1: Base Auto Opción 2: Manual Opción 3: Híbrido
Líneas de código 🟢 Mínimo (10-15) 🔴 Máximo (40-80) 🟡 Medio (20-30)
Propiedades 🟢 40+ (reflexión) 🔴 Hasta 10 🟢 Cualquier cantidad
Control manual 🔴 No 🟢 Total 🟢 Parcial
Performance 🟡 Reflexión (~1ms) 🟢 Directo 🟡 Reflexión (~1ms)
Mantenimiento 🟢 Fácil 🔴 Difícil 🟢 Fácil
Transformaciones 🔴 No 🟢 Sí 🟢 Sí
Validaciones custom 🔴 No 🟢 Sí 🟢 Sí
Recomendado para Tipos simples Control total Casos especiales

🎯 Guía de Decisión

┌─ ¿Tienes 10+ propiedades?
│   └─ SÍ → Opción 1 (Base Auto)
│   └─ NO ↓
│
├─ ¿Necesitas transformaciones/validaciones?
│   └─ SÍ → ¿Solo en algunas propiedades?
│       ├─ SÍ → Opción 3 (Híbrido)
│       └─ NO → Opción 2 (Manual)
│   └─ NO → Opción 1 (Base Auto)
│
└─ ¿Performance es crítico? (microsegundos)
    └─ SÍ → Opción 2 (Manual)
    └─ NO → Opción 1 (Base Auto)

💡 Recomendación General: Empieza con Opción 1 (Base Auto), cambia a Opción 3 si necesitas lógica especial en algunos campos.


🎓 Resumen

Opción 1: Base Automático (⭐ Recomendado)

[OracleCustomTypeMapping("SZTYOTIOL_OBJ")]
public class TiraOracleObj : OracleUdtBase  // ← Hereda
{
    [OracleObjectMapping("SZTYOTIOL_TERM_CODE")]
    public string? TermCode { get; set; }
    
    // ✅ Listo! No necesitas FromCustomObject/ToCustomObject
}

Opción 2: Manual Completo

[OracleCustomTypeMapping("SZTYOTIOL_OBJ")]
public class TiraOracleObj : IOracleCustomType, INullable  // ← Implementa
{
    public void FromCustomObject(OracleConnection con, object pUdt)
    {
        OracleUdt.SetValue(con, pUdt, "SZTYOTIOL_TERM_CODE", TermCode);
        // ✅ Control total línea por línea
    }
}

Opción 3: Híbrido

[OracleCustomTypeMapping("SZTYOTIOL_OBJ")]
public class TiraOracleObj : OracleUdtBase  // ← Hereda
{
    public override void FromCustomObject(OracleConnection con, object pUdt)
    {
        base.FromCustomObject(con, pUdt);  // ← Automático
        // Tu lógica manual adicional aquí
    }
}

🎉 ¡Elige la que mejor se adapte a tu caso!

using Oracle.ManagedDataAccess.Client;
using Oracle.ManagedDataAccess.Types;
namespace Lince.SERVACAD.Core.Models.Banner;
/// <summary>
/// Clase base para Oracle UDT con serialización automática por reflexión
/// Hereda de esta clase para mapeo automático, o sobrescribe FromCustomObject/ToCustomObject para control manual
/// </summary>
public abstract class OracleUdtBase : IOracleCustomType, INullable
{
private bool _isNull;
public bool IsNull => _isNull;
/// <summary>
/// Serialización automática de C# a Oracle usando reflexión
/// Sobrescribe este método si necesitas control manual
/// </summary>
public virtual void FromCustomObject(OracleConnection con, object pUdt)
{
if (!IsNull)
{
// ✅ Reflexión automática - Lee los atributos [OracleObjectMapping]
foreach (var prop in GetType().GetProperties())
{
var attrData = prop.GetCustomAttributesData()
.FirstOrDefault(a => a.AttributeType == typeof(OracleObjectMappingAttribute));
if (attrData?.ConstructorArguments.Count > 0)
{
string oracleName = attrData.ConstructorArguments[0].Value?.ToString() ?? "";
var value = prop.GetValue(this);
// Convertir null a valores por defecto según el tipo
if (value == null)
{
value = GetDefaultValue(prop.PropertyType);
}
OracleUdt.SetValue(con, pUdt, oracleName, value);
}
}
}
}
/// <summary>
/// Deserialización automática de Oracle a C# usando reflexión
/// Sobrescribe este método si necesitas control manual
/// </summary>
public virtual void ToCustomObject(OracleConnection con, object pUdt)
{
// ✅ Reflexión automática para deserialización
foreach (var prop in GetType().GetProperties())
{
var attrData = prop.GetCustomAttributesData()
.FirstOrDefault(a => a.AttributeType == typeof(OracleObjectMappingAttribute));
if (attrData?.ConstructorArguments.Count > 0)
{
string oracleName = attrData.ConstructorArguments[0].Value?.ToString() ?? "";
var value = OracleUdt.GetValue(con, pUdt, oracleName);
prop.SetValue(this, value);
}
}
}
/// <summary>
/// Obtiene el valor por defecto según el tipo de dato
/// Extiende este método si necesitas más tipos
/// </summary>
protected virtual object GetDefaultValue(Type propertyType)
{
return propertyType == typeof(string) ? string.Empty :
propertyType == typeof(int?) ? 0 :
propertyType == typeof(decimal?) ? 0m :
propertyType == typeof(DateTime?) ? DateTime.MinValue :
propertyType == typeof(bool?) ? false :
propertyType == typeof(double?) ? 0.0 :
propertyType == typeof(float?) ? 0f :
0;
}
/// <summary>
/// Crea una instancia NULL del UDT
/// </summary>
protected static T CreateNull<T>() where T : OracleUdtBase, new()
{
return new T { _isNull = true };
}
}
/// <summary>
/// Factory genérica para crear instancias de Oracle UDT
/// Úsala heredando: [OracleCustomTypeMapping("TIPO_OBJ")] public class MiFactory : OracleUdtFactory<MiClase> { }
/// </summary>
/// <typeparam name="T">Tipo de UDT que implementa IOracleCustomType</typeparam>
public class OracleUdtFactory<T> : IOracleCustomTypeFactory where T : IOracleCustomType, new()
{
public IOracleCustomType CreateObject() => new T();
}
/// <summary>
/// Factory genérica para crear arreglos de Oracle UDT
/// Úsala heredando: [OracleCustomTypeMapping("TIPO_ARR")] public class MiArrayFactory : OracleUdtArrayFactory<MiClase> { }
/// </summary>
/// <typeparam name="T">Tipo de UDT que implementa IOracleCustomType</typeparam>
public class OracleUdtArrayFactory<T> : IOracleArrayTypeFactory where T : IOracleCustomType, new()
{
public Array CreateArray(int numElems) => new T[numElems];
public Array? CreateStatusArray(int numElems) => null;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment