Foros Club Delphi

Foros Club Delphi (https://www.clubdelphi.com/foros/index.php)
-   Varios (https://www.clubdelphi.com/foros/forumdisplay.php?f=11)
-   -   Funcion Get usando RTTI (https://www.clubdelphi.com/foros/showthread.php?t=94626)

javicho_villa 02-05-2020 04:10:55

Funcion Get usando RTTI
 
Buenos dias con todos, siempre es un gusto contactarlos, en esta oportunidad estoy tratando de hacer una función generica Get para que me devuelva un objeto de cualquier tipo a traves de una consulta con un Query.
por ejemplo:
1. Creo una clase TCliente
Código Delphi [-]
type
  TCliente = class(TObject)
    private
      Id_Cliente:Integer;
      RazonSocial:String;
      RazonSocialCorta:String;
...
...
    public
      Constructor Create;
      procedure Clear;
      property iId_Cliente:Integer           read Id_Cliente           write Id_Cliente;
      property iRazonSocial:String           read RazonSocial          write RazonSocial;
      property iRazonSocialCorta:String      read RazonSocialCorta     write RazonSocialCorta;
...
...
   End;

2. Creo una function con parametros del nombre de la Tabla y el numero del registro. por ejemplo la tabla se llama Cliente y el Id es el numero 1, estos datos los pongo cuando invoco la función:

Código Delphi [-]
var Obj :TObject;
begin

  Obj := TObject.Create;
  Obj := Dm.GetObject(1,'Cliente');

  showmessage('Razon Social: '+ (Obj as TCliente).iRazonSocial);

aquí les paso la funcion GetObject que es la quiero implementar
Código Delphi [-]

function TDM.GetObject(Id: integer; TableName: String): TObject;
var ClassName,Cad,CProp:String;
    Obj:TObject;
    MiQuery: TFDQuery;
    Campos, Propiedad : TStringList;
    lProp    : TRTTIProperty;
    lContext,C : TRTTIContext;
    lType    : TRTTIType;
    Value: TValue;
    Pos1,Pos2:integer;
begin

  Campos     := TStringList.Create;
  Propiedad  := TStringList.Create;
  Cad           := 'select * from '+TableName;
  ClassName := 'T'+TableName;

  MiQuery := TFDQuery.Create(nil);
  MiQuery.Connection := Connection;
  MiQuery.SQL.Clear;
  MiQuery.SQL.Add(Cad);
  MiQuery.Open;

  //-- Iniciando extraccion de las propiedades de las clases --//
  LContext := TRttiContext.Create;
  C := TRttiContext.Create;

  Obj := (C.FindType('Modelo_Cliente.'+ClassName) as TRttiInstanceType).MetaClassType.Create;
  lType    := lContext.GetType(Obj.ClassInfo);   

  for lProp in LType.GetProperties do  /// Aca guardo las propiedades en un StringList llamado Propiedad
  begin
    Cad  := lProp.ToString;
    Pos1 := Pos('i',Cad);
    Pos2 := Pos(':',Cad);
    CProp := Copy(Cad,Pos1,Pos2-Pos1);
    Propiedad.Add(CProp);
  end;


  if MiQuery.RecordCount>0 then
    begin
      //--- Poniendo los campos en el arreglo --///
      Connection.GetFieldNames('','',TableName,'',Campos);   //--- Aca hay que poner los campos del Query ----//

      //--- Poniendo las propiedades del Objeto ---//
      //(Obj as ClassName).Propiedad[2] := '123456';
      Value := '123456789';


      for lProp in LType.GetProperties do  //// ACA ESTA EL ERROR NO PUEDO GUARDAR LOS VALORES EN EL OBJETO
      begin                                            /// NOSE COMO USAR EL PROPERTY O CUALQUIER METODO PARA MODIFICAR LOS ATRIBUTOS DEL OBJETO.
        Cad  := lProp.ToString;
        //showmessage(Cad);
        if Cad = 'property iId_Cliente: Integer' then
          lProp.SetValue(Obj,Value);
      end;

      //Lprop.SetValue(Obj,'12345');


      //showmessage(Campos[5]);
      //showmessage(Propiedad[4]);

    end;

  result := Obj;
end;

Como comentaba en lineas arriba no se como modificar los atributos del objeto Obj para poder devolver el objeto con todos sus valores.
todo esto lo quiero hacer para cualquier tabla y cualquier tipo de objeto, para esto debo indicar que si creo una tabla Kardex también creo una clase TKardex en un .pas que se llamaría Modelo_Kardex, ese es un standard que sigo.

Les pido me den una mano para poder solucionar este problema, este metodo me serviria para extraer cualquier objeto de cualquier tabla.

Muchas gracias de antemano por leer esta consulta y gracias por su tiempo.

Saludos,

Javier Villa.

Al González 02-05-2020 18:33:38

Hola, Javier.

Sin afán de molestar, veo un poco vago tu avance de esa implementación, aunque celebro cada vez que alguien se pone a trabajar con los valiosos recursos de la RTL. Por lo visto quieres implementar algo similar al "streamming" de Delphi o más formalmente persistencia de objetos: lectura/escritura de instancias de clases en un medio de almacenamiento.

No es que sea imposible, hay varias formas de conseguirlo, pero primero debes superar barreras más elementales. Por ejemplo, al hacer esto:
Cita:

Empezado por javicho_villa (Mensaje 537008)
Código Delphi [-]
var Obj :TObject;
begin

  Obj := TObject.Create;
  Obj := Dm.GetObject(1,'Cliente');

causas que la instancia creada con TObject.Create quede en el limbo, porque inmediatamente reemplazas el valor de la variable Obj con otra instancia (la devuelta por el método GetObject). Así que ya desde ahí parten los problemas.

Quizá deberías, por un lado, estudiar y practicar un poco más la POO en Delphi. Y, por otro, compartir cómo llegaste a la conclusión de que necesitas implementar persistencia de objetos en tu aplicación. Claro, si te nace darnos a conocer el panorama general de tu idea.

Saludos. :-)

gatosoft 03-05-2020 10:38:17

Cita:

Empezado por javicho_villa (Mensaje 537008)
Buenos dias con todos, siempre es un gusto contactarlos, en esta oportunidad estoy tratando de hacer una función generica Get para que me devuelva un objeto de cualquier tipo a traves de una consulta con un Query.


Es una iniciativa interesante, en mi opinión es un inicio hacia un ORM, lo cual no es algo tan sencillo de implementar,y menos si no se trabaja con una concepción mas orientada a objetos, es decir el get es solo una parte de todo el proceso.

Me llama la atención lo que haces con el prefijo i para identificar las propiedades del objeto, o el mismo hecho de que la. En lugar de eso, te invito a que revises la documentación asociada a TCustomAttribute

es la forma como puedes etiquetar tus clases, métodos, propiedades y darles un tratamiento.

Por ejemplo


Código Delphi [-]
[TMyORMField('GEN_CLIENTES')]
TCliente = Class(TObject)
Private
Public
end;


Con esto, puedes indicarle a tu sistema que la clase definida tiene un atributo de tipo TMyORMField (creado por ti) que indica algo, en este caso tu decides que te va a representar el nombre de la tabla en la base de datos. con esto, tu clase y tu tabla pueden llamarse diferente.


De igual forma puedes agregar atributos a tus "Fields" y "properties", asi:

Código Delphi [-]
    [TMyORMField('GEN_CLIENTES')]
    TCliente = Class(TObject)
    Private

    Public

    [TMyORMField('CLI_RUT')]
  RUT: String; //este es un field, a diferencia de las properties, no tienen getters and setters
    
    [TMyORMField('CLI_ID',True,True)]  
  property Id_Cliente:Integer  read FId_Cliente           write FId_Cliente;
    
  [TMyORMField('CLI_RAZONSOCIAL')]   
  property RazonSocial:String           read FRazonSocial          write FRazonSocial;
  
  Property FechaConstitucion: TDateTime read FFechaConstitucion write FFechaConstitucion
    
  [TMyORMField('CLI_RAZONSOCIALCORTA')]   
  property RazonSocialCorta:String      read FRazonSocialCorta     write FRazonSocialCorta;
  
  end;

Mira que aqui utilizo el mismo atributo TMyORMField (podrian ser diferentes), con la diferencia que uno opera sobre la clase, indicando el nombre de la tabla y el otro indica el nombre del campo y otros datos.

Fijate tambien que los atributos pueden ir sobre properties o sobre fields.

En el caso del ID_Cliente, recibe dos parametros mas de tipo boleano (o cualquier tipo), tu decides que interpretación le puedes dar, por ejemplo, el primero podria indicar si es un campo requerido dentro de tu tabla y el segundo si es parte de la llave primaria.

Otra opcion es utilizar multi-atributos para indicar esto

Código Delphi [-]
[TMyORMField('CLI_RAZONSOCIAL')] 
[TMyORMFieldFlags('ESLLAVE')] 
Property Id_Cliente


Para llegar a estom a grosso modo debes definir una calse para el atributo:

Código Delphi [-]
TMyORMField = class(TCustomAttribute)
  private
    FIsKeyField: Boolean;
    FFieldName: String;
    FIsNotNull: Boolean;
    procedure SetFieldName(const Value: String);
    procedure SetIsKeyField(const Value: Boolean);
    procedure SetIsNotNulll(const Value: Boolean);
  public
   Property FieldName: String read FFieldName write SetFieldName;
   Property IsNotNull: Boolean read FIsNotNull write SetIsNotNulll;
   Property IsKeyField: Boolean read FIsKeyField write SetIsKeyField;
   Constructor Create(pFieldName: String;
                      pIsNotNulll: Boolean = False;
                      pIsKey: Boolean = False
                      ); reintroduce;
   Procedure Inicializar; //digamos para asignar valores por defecto
  end;//TMyORMField


Posteriomente en tu "getObjeto(pObjeto: TObject)"

debes revisar estas properties:


Código Delphi [-]
function TMyORMEngine.getAtributosObjeto: Boolean; 
//TMyORMEngine, deberia ser la clase que hace todo el trabajo sucio con los objetos
var ctx : TRttiContext;
    rt : TRttiType;
    prop : TRttiProperty;
    xfield: TRttiField;
    Attr: TCustomAttribute;
  
  
    vCampos: TCampo; //puedes guardar lo que encuentres en una lista de campos... 
                   //TList, o en un StringList (depende de lo que quieras) 
begin
 Result:= False;

 if not Assigned(Objeto) then //objeto es una variable que le asignas a tu TMyORMEngine
    exit;

 FTabla:='';
 LimpiarCampos; //la lista

 Try
   ctx := TRttiContext.Create();
   rt := ctx.GetType(Objeto.ClassType);
   
   //buscas los atributos de tus properties
   for prop in rt.GetProperties() do
     for Attr in prop.GetAttributes() do
        if Attr is TMyORMField then
           Begin
             Result:= True;
             vCampos:= TCampo.Create;       
       //le indico que esto es una property. este valor lo defiens tu con
       // TipoCampo = (ptProperty, ptField)
             vCampos.PropType:= ptProperty; 
             vCampos.Name:= prop.Name;
             vCampos.FieldName:= (Attr as TMyORMField).FieldName;
             vCampos.IsNotNull:= (Attr as TMyORMField).IsNotNull;
             vCampos.IsKeyField:= (Attr as TMyORMField).IsKeyField;
             FlstCampos.Add(vCampos);
           End; //if

    //buscas los atributos de tus Fields
    for xfield in rt.GetFields do
      for Attr in xfield.GetAttributes do
        if Attr is TMyORMField then
           Begin
             Result:= True;
             vCampos:= TgtsCampo.Create;
             vCampos.PropType:= ptField;
             vCampos.Name:= xfield.Name;
             vCampos.FieldName:= (Attr as TMyORMField).FieldName;
             vCampos.IsNotNull:= (Attr as TMyORMField).IsNotNull;
             vCampos.IsKeyField:= (Attr as TMyORMField).IsKeyField;
             FlstCampos.Add(vCampos);
           End; //if

    //buscas los atributos de la clase
    for Attr in rt.GetAttributes do
      if Attr is TMyORMField then
         begin
          Result:= True;
          FTabla:= (Attr as TMyORMField).FFieldName;
         end;
 Finally
   ctx.Free;
 End;//Try..finally
end;

la asignacion de valores , que es lo que creo que quieres la harias con


Código Delphi [-]
procedure TMyORMEngine.AsignarPropiedades(pDataSet: TDataSet);
Var i:Integer;
    vCampo: TgtsCampo;
begin

  if (not Assigned(pDataSet)) or
     (not pDataSet.Active) or
     (pDataSet.IsEmpty) then
     exit;

  if not Assigned(Objeto) then
     exit;

  pDataSet.First; //solo el primer registro... para el demo
  For i:=0 to pDataSet.FieldCount-1 do
  begin
    vCampo:= getCampoByFieldName(pDataSet.Fields[i].FieldName); //tares el campo listado
    if Assigned(vCampo) then
       begin
        if vCampo.IsKeyField then
           Continue;

        if vCampo.PropType = ptProperty then
           SetPropValue(vCampo.Name, pDataSet.Fields[i].Value);

        if vCampo.PropType = ptField then
           SetFieldValue(vCampo.Name, pDataSet.Fields[i].Value);
       end;
  end;//for i

end;

SetField y setProp:

Código Delphi [-]
procedure TMyORMEngine.SetFieldValue(pName: String; pValor: Variant);
var ctx : TRttiContext;
    rt : TRttiType;
    xfield: TRttiField;
begin
 if not Assigned(Objeto) then
    exit;
 ctx := TRttiContext.Create();
 Try
   rt := ctx.GetType(Objeto.ClassType);
   xfield := rt.GetField(pName);
   if Assigned(xfield) then
      xfield.SetValue(Objeto, TValue.From(pValor));
 Finally
   ctx.Free;
 End;

end;

procedure TMyORMEngine.SetPropValue(pName: String; pValor: Variant);
var ctx : TRttiContext;
    rt : TRttiType;
    prop : TRttiProperty;
begin
 if not Assigned(Objeto) then
    exit;
 ctx := TRttiContext.Create();
 Try
   rt := ctx.GetType(Objeto.ClassType);
   prop := rt.GetProperty(pName);
   if Assigned(prop) then
      prop.SetValue(Objeto, TValue.From(pValor));
 Finally
   ctx.Free;
 End;
end;


Lo anterior es un extracto de algunas pruebas que hice pensando tal vez en lo mismo que tu quieres, pero solo a manera de ejercicio... funcionó lo que hice, pero nunca lo implementé. Como te dije lo anterior es un "a grosso modo"

Con la clase TMyORMEngine, iban muchos, muchos metodos y propiedades, como la persistencia en la DB a partir de los datos obtenidos... o la asignacion de sentencias SQL custom (insert, update, delete),

Como te digo, GetObject es solo una tarea de todo lo que deberias organizar sobre este tema.

Neftali [Germán.Estévez] 04-05-2020 13:18:49

Tal como han dicho, estás incursionando en un ORM o un "Framework de Persistencia".
La idea es interesanta, pero a mi entender te falta completar "la pieza" importante.

Tienes:
1) La clase TCliente. No se si realmente la tienes así definida o simplemnte la has puesto así aquí para simplificar, pero un primer consejo es que todas tus clases de "persistenca" deriven de una clase base (si no lo tienes así).
2) Tienes la tabla de CLIENTE con sus campos.
3) Tienes la pieza que te "convierte" un elemento de Entidad-Relacion a un Objeto persistente.

A esta clase, a mi entender, le falta información.
Está claro que el resultado de la ejecución (en tu caso) debe ser un objeto TCliente. De alguna forma esta función debe llegar a conocer la referencia a la clase TCliente, de forma que el objeto que se cree no sea utilizando TObject sino la referencia a esa clase.
  • Una primera prueba que podrías hacer, sería pasar esa referencia como parámetro del procedimiento.
  • Una segunda, sería conseguir la referencia de la clase (GetClass) utilizando RTTI. Para ello deberás registrar las clases.
Revisa esta documentación:
http://docwiki.embarcadero.com/RADSt...ass_References

javicho_villa 27-05-2020 20:21:34

Me han dado una buena luz para continuar
 
Muchas gracias por todas las respuestas.

Quiero comentarles que defino en archivo .pas las clases que defino, por ejemplo: Modelo_Cliente.pas. Aquí defino las clase TCliente. en otro archivo .pas llamado Cx_Cliente, defino funciones que me devuelvan valores consultando la base de datos, por ejemplo:

Código Delphi [-]
{ TCx_Cliente }

class function TCx_Cliente.GetCliente(Id: integer): TCliente;
var iCliente:TCliente;
    MiQuery: TAdoQuery;
begin
  iCliente := TCliente.Create;
  MiQuery  := TAdoQuery.Create(nil);
  MiQuery.SQL.Clear;
  MiQuery.Connection := Datos.DatosDM.CabeceraADOQuery.Connection;
  MiQuery.SQL.Clear;
  MiQuery.SQL.Add('select * from Cliente where Id_Cliente='+IntToStr(id));
  MiQuery.Active := true;
  if MIQuery.RecordCount>0 then
    begin
      MiQuery.First;
      iCliente.iId_Cliente           := Id;
      iCliente.iRazonSocial          := MiQuery.FieldByName('RazonSocial').AsString;
      iCliente.iRazonSocialCorta     := MiQuery.FieldByName('RazonSocialCorta').AsString;
....
      iCliente.iFormaPago            := MiQuery.FieldByName('FormaPago').AsString;
      iCliente.iTituloDoc1           := MiQuery.FieldByName('TituloDoc1').AsString;
      iCliente.iTituloDoc2           := MiQuery.FieldByName('TituloDoc2').AsString;
      iCliente.iTituloDoc3           := MiQuery.FieldByName('TituloDoc3').AsString;
      iCliente.iId_Usuario           := MiQuery.FieldByName('Id_Usuario').AsInteger;
      //if length(MiQuery.FieldByName('FRegistro').AsString)>0 then
        //iCliente.iFRegistro     := MiQuery.FieldByName('FRegistro').AsDateTime;
.....
    end;
  MiQuery.Free;
  result := iCliente;
  iCliente := TCliente.Create;
  iCliente.Free;
end;

tambien tengo procedimientos almacenados de sql que lo invoco de esta manera:

Código Delphi [-]
class function TCx_Cliente.ClienteTerceroInsertar(Tercero: TClienteTercero): String;
var MiProcedimiento:TADOStoredProc;
    MyClass:TComponent;
    MiError:String;
begin
  MiProcedimiento := TADOStoredProc.Create(nil);
  MiProcedimiento.Connection := DatosDM.PrincipaADOConnection;
  MyClass := TComponent.Create(nil);
  MiError := '';
  try
    with MiProcedimiento do
      begin
        Close;
        Parameters.Clear;
        ProcedureName := 'ClienteTerceroInsertar';
        //Parameters.ParseSQL('ClienteTerceroInsertar',true);
        //Parameters.CreateParameter('@Id_ClienteTercero',ftInteger,pdInput,0,0);
        Parameters.CreateParameter('@Id_Cliente',ftInteger,pdInput,0,0);
        Parameters.CreateParameter('@Id_ParametroTipoTercero',ftInteger,pdInput,0,0);
        Parameters.CreateParameter('@RazonSocial',ftString,pdInput,100,0);
        Parameters.CreateParameter('@RazonSocialCorta',ftString,pdInput,50,0);
        Parameters.CreateParameter('@RUC',ftString,pdInput,20,0);
        Parameters.CreateParameter('@Direccion',ftString,pdInput,100,0);
        Parameters.CreateParameter('@DNI',ftString,pdInput,20,0);
        Parameters.CreateParameter('@Brevete',ftString,pdInput,20,0);
        Parameters.CreateParameter('@Marca',ftString,pdInput,20,0);
        Parameters.CreateParameter('@Placa',ftString,pdInput,10,0);
        Parameters.CreateParameter('@CertificadoVehicular',ftString,pdInput,20,0);
        Parameters.CreateParameter('@Error',ftString,pdOutPut,100,0);
        Parameters.ParamByName('@Id_Cliente').Value              := Tercero.iId_Cliente;
        Parameters.ParamByName('@Id_ParametroTipoTercero').Value := Tercero.iId_ParametroTipoTercero;
        Parameters.ParamByName('@RazonSocial').Value             := Tercero.iRazonSocial;
        Parameters.ParamByName('@RazonSocialCorta').Value        := Tercero.iRazonSocialCorta;
        Parameters.ParamByName('@RUC').Value                     := Tercero.iRUC;
        Parameters.ParamByName('@Direccion').Value               := Tercero.iDireccion;
        Parameters.ParamByName('@DNI').Value                     := Tercero.iDNI;
        Parameters.ParamByName('@Brevete').Value                 := Tercero.iBrevete;
        Parameters.ParamByName('@Marca').Value                   := Tercero.iMarca;
        Parameters.ParamByName('@Placa').Value                   := Tercero.iPlaca;
        Parameters.ParamByName('@CertificadoVehicular').Value    := Tercero.iCertificadoVehicular;
        Parameters.ParamByName('@Error').Value                   := '';
        ExecProc;
      end;
      MiError := MiProcedimiento.Parameters.ParamByName('@Error').Value;
    finally
    MyClass.Free;
  end;
  result := MiError;
  MiProcedimiento.Free;
end;

este procedimiento lo repito para cualquier tabla por ejemplo para las ventas tengo una clase TVenta guardado en un archivo Modelo_Venta, ademas de los procedimientos y funciones en un archivo Cx_Venta donde me conecto a la base de datos y hago consultas y modificaciones a la tabla venta.

Lo que quiero hacer es lo mismo pero un solo archivo donde pueda invocar los valores de cualquier tabla, es decir si es Cliente, Venta, etc, pero usando los objetos pertinentes. entonces hago un GetObject(Id: integer; TableName: String): TObject; y me devuelve un objecto de tipo TObject.

quiero volver agradecer por su tiempo y ojala me puedan ayudar a realizar esta operación, que me ahorraría muchas lineas de código al hacer cualquier proyecto.

Saludos,

Javier Villa.

Neftali [Germán.Estévez] 28-05-2020 10:15:21

Cita:

Empezado por javicho_villa (Mensaje 537432)
...defino en archivo .pas las clases que defino, por ejemplo: Modelo_Cliente.pas. Aquí defino las clase TCliente. en otro archivo .pas llamado Cx_Cliente, defino funciones que me devuelvan valores consultando la base de datos

...
este procedimiento lo repito para cualquier tabla por ejemplo para las ventas tengo una clase TVenta guardado en un archivo Modelo_Venta, ademas de los procedimientos y funciones en un archivo Cx_Venta donde me conecto a la base de datos y hago consultas y modificaciones a la tabla venta.
...
Lo que quiero hacer es lo mismo pero un solo archivo donde pueda invocar los valores de cualquier tabla, es decir si es Cliente, Venta, etc, pero usando los objetos pertinentes.


Lo ideal sería que tuvieras un objeto que defina cada objeto de clase, Modelo_Cliente.pas (TCliente), Modelo_Venta.pas (TVenta),... y que tal como dices, puedas tener centralizado en un único lugar el procedimiento que recupera los campos de una tabla en el objeto correspondiente.

Tal como lo tienes ahora, el problema es que, debes tener en algún sitio la correlación entre los campos de la tabla y las propiedades de cada objeto. como no la tienes en ningún sitio, implementas un procedimiento por cada una de esas parejas (Cx_Cliente, Cx_Venta,...).

Creo que puedes conseguir eso, en la propia clase TCliente, TVenta,... utilizando RTTI y atributos.

De esa forma en un único lugar (procedimiento/función) podrás cargar los campos de la tabla en el objeto relacionado.
Sería algo así:

Código Delphi [-]
type
  TBaseObject = class
    ...
  protected
    procedure FillObjectFromdataBase;
  end;

  [AttTableName('CLIENTES')]
  TCliente = class (TBaseObject)
  private
    FiFormaPago: String;
    FiRazonSocial: string;
    FiId_Cliente: integer;
  published
    [AttrFieldName('ID')]
    [AttrDescripcion('Identificador del cliente')
    property iId_Cliente:integer read FiId_Cliente;
    [AttrFieldName('RazonSocial')]
    [AttrDescripcion('Razón social del cliente')
    property iRazonSocial:string read FiRazonSocial;
    [AttrFieldName('FormaPago')]
    [AttrDescripcion('Forma de pago del cliente')
    property iFormaPago:String read FiFormaPago;
    ...
  end;

  [AttrTableName('VENTAS')]
  TVentas = class (TBaseObject)
  published
    [AttrFieldName('ID')]
    [AttrDescripcion('Identificador del cliente asiciado à la venta')
    property iId_Venta:integer read FiId_Venta;
    [AttrFieldName('ID_venta')]
    [AttrDescripcion('Identificador del cliente asiciado à la venta')
    property iId_Cliente:integer read FiId_Cliente;
    ...
  end;


El procedmiento:
Código Delphi [-]
procedure FillObjectFromdataBase;

Definido en la clase base (TBaseObject) debería poder acceder al atributo de cada clase (AttTableName) para conocer la tabla, y a las propiedades published del objeto vía RTTI.
Para cada propiedad puedes conocer su atributo (AttrFieldName) para acceder al campo.

De esta forma, podrías utilizar:

Código Delphi [-]
var
  cli:Tcliente;
  venta:TVentas;

begin
  ...
  cli.FillObjectFromdataBase;
  ...
  venta.FillObjectFromdataBase;
  ...

Bueno, más o menos esa es la idea. No se si me he explicado bien.

Tienes información sobre cómo definir atributos aquí:
http://docwiki.embarcadero.com/RADSt...tom_Attributes

Cómo acceder a ellos en runtime:
http://docwiki.embarcadero.com/RADSt...es_at_Run_Time

javicho_villa 19-07-2021 20:32:35

Uso de RTTI - Solución encaminada
 
Muchas gracias por todas sus respuestas anteriores, me han dado mucha luz por donde debo ir, y la solución que hasta va bien es usar RTTI.
El objetivo es crear funciones clasicas como ObjectInsert(Obj: TObject):String, de iguakl manera un ObjectUpdate; ObjectDelete y GetObject:TObject;

Para el caso de ObjectInsert, lo ha desarrolado un amigo y hasta funciona bien, pero lo estoy volviendo hacer para poder incorporar la opción de maestro/detalle y tambien que guarde en una tabla llamada BITACORA para poder controlar cualquier movimiento de los usuarios.

Código Delphi [-]
function TDm.ObjectInsert(Obj: TObject): String;
var
  MiProcedimiento: TFDStoredProc;
  MiError:String;
  Detalle : widestring;
  ListaProp: TStringList;
  ListaParam: TStringList;
  ListaMetaInfo : TStringList;
  NombreProc: string;
  NombreProp: string;
  NombreParam: string;
  TipoProp  : string;
  TipoDato  : string;
  TipoIO    : string;
  AnchoCampo: string;
  contexto : TRTTIContext;
  propiedad : TRTTIProperty;
  valor : TValue;
  I, J : word;

  function BuscaLong(B: string): string;
  var
    Encontrado : boolean;
    Nombre: string;
    Tipo : string;
    Ancho:string;
    J: Integer;
  begin
    Encontrado := False;
    for J := 0 to Pred(ListaMetaInfo.Count) do
    begin
       if B = Copy(ListaMetaInfo[J], 1, Length(B)) then
       begin  // := Copy(ListaMetaInfo[J], 1, Length(NombreBuscar)) then
         Encontrado := True;
         Break;
       end;
    end;
    if Encontrado then
      TLibreria.ParseMetaInfo(ListaMetaInfo[J], Nombre, Tipo, Ancho)
    else
      Ancho := '100';
    result := Ancho
  end;

  function BuscaProp(B: string): integer;
  var
    Encontrado : boolean;
    Nombre: string;
    Tipo : string;
    Ancho:string;
    J: Integer;
  begin
    Encontrado := False;
    for J:= 0 to Pred(ListaProp.Count) do
    begin
      if Copy(B, 2, Length(B)) = Copy(ListaProp[J], 2, Length(B)-1) then
      begin
        Encontrado := True;
        Break;
      end;
    end;
    if Encontrado then
      result := J
     else
       result := -1;
  end;

begin
  MiError := '';
  ListaProp  := TStringList.Create;
  ListaParam := TStringList.Create;

  if not Assigned(Obj) then
    exit;

  try
    MiProcedimiento := TFDStoredProc.Create(nil);
    MiProcedimiento.Connection := DM.EmpresaConnection;  // SislibroConnection

//  1- Carga los propiedades de la clase
    ListaProp := TLibreria.DaListaPropConTipo(Obj);

    try
      with MiProcedimiento do
      begin
        Close;
        Params.Clear;

//  2- Ubicar el store procedure de la tabla  + INSERTAR
        NombreProc := Copy(Obj.ClassName, 2, Length(Obj.ClassName)-1) + 'Insertar';
        StoredProcName := NombreProc;
        FetchOptions.Items := MiProcedimiento.FetchOptions.Items - [fiMeta]; // quitar fiMeta para insertar parametros manualmente

//  3- Conseguir los parametros del store procedure
//--   Para el caso de Sislibro se extrae del componente EmpresaConecction --//
       ListaParam := DM.DaListaParam(Dm.EmpresaConnection.Params.Database , NombreProc);

//  4- Obtiene los meta datos de la tabla
        ListaMetaInfo := TLibreria.DaListaMetaInfo(Copy(Obj.ClassName, 2, Length(Obj.ClassName)-1));

//  5- Crear los parametros en el FDStoredProc dinamico y pasa el valor a los parametros
        contexto := TRTTIContext.Create;

//        showmessage(IntToStr(ListaParam.Count));

        for I := 0 to Pred(ListaParam.Count) do
        begin
          TLibreria.ParseParam(ListaParam[i], NombreParam, TipoDato, TipoIO);  // separa nombre de tipo
          AnchoCampo := BuscaLong( Copy(NombreParam, 2, length(NombreParam)-1)); // Busca ancho de campo de la tabla
          if (TipoDato = 'varchar') And (StrToInt(AnchoCampo) = 8000) then   // si longitud de parametro = MAX
          begin                 {Wide}
            Params.CreateParam( ftWideString,  NombreParam, TLibreria.StrToParamType(TipoIO));  // se crea con widestring
            for J := 0 to Pred(ListaProp.Count) do // busca el tipo de la propiedad iDetalle
            begin
              TLibreria.ParseProp(ListaProp[J], NombreProp, TipoProp);        // separa nombre de tipo
              if TipoProp = 'TObjectList' then  // si es del tipo TObjectList
              begin  // obtiene string con datos y los pasa al paremetro
                Detalle := Dm.GetDetalleText(TObjectList(contexto.GetType(Obj.ClassType).GetProperty(NombreProp).GetValue(Obj).  AsObject));
                Params.ParamByName( NombreParam).Value :=  Detalle;
              end;
            end;
          end
          else // de lo contrario
          begin // procesa los demas campos
            Params.CreateParam( TLIbreria.StrToFieldType(TipoDato), NombreParam, TLibreria.StrToParamType(TipoIO)); // crea paramero
            if (Uppercase(NombreParam) = '@ERROR') then  // si es campo @Error
            begin // pasa datos en blanco
              Params.ParamByName( NombreParam).Value :=  '';
            end
            else   // sino
            begin  // procesa normal
              J := BuscaProp(NombreParam);  // busca parametro en lista de propiedades, obtiene la posicion
              TLibreria.ParseProp(ListaProp[J], NombreProp, TipoProp);        // separa nombre de tipo
              propiedad :=  contexto.GetType(Obj.ClassType).GetProperty(NombreProp); // tipo de la propiedad
              valor := propiedad.GetValue(Obj).ToString;            // obtiene valor
              Params.ParamByName( NombreParam).Value :=  valor.AsString;
            end;
          end;
        end;

        contexto.Free;

        if ParamCount > 0 then
        begin
//          Prepared := True;
          ExecProc;
          MiError := Params.ParamByName('@Error').AsString;
        end;
      end;

    finally
      MiProcedimiento.Free;
    end;
  finally
    result := MiError;
    ListaProp.Free;
    ListaParam.Free;
  end;
end;


hay varias funciones que las estoy depurando, cuando las tenga lista se las paso, me parece super interesante estas funciones básicas para cualquier tabla.

les reitero mi agradecimiento por el apoyo brindado.

un fuerte abrazo.
:)


La franja horaria es GMT +2. Ahora son las 23:20:15.

Powered by vBulletin® Version 3.6.8
Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
Traducción al castellano por el equipo de moderadores del Club Delphi