Simple objectification of data sets with Delphi RTTI

xiaoxiao2021-03-06  37

Simple objectification of data sets with Delphi RTTI

[Mental Studio] Raptor [blog]

In the article "Powerful Delphi RTTI - I need to understand a variety of development languages", I said that I realized the simple objectification of the data set with the RTTI of Delphi. This article will introduce my implementation method.

First, from a simple example: Assume that there is an adodataset control, connect the Ross Wen Database, SQL is:

SELECT * from Employee

Now you have to display EmployeeiD, FirstName, LastName, Birthdate four fields in the list in ListView. The traditional code is as follows:

With adodataset1 do

Begin

Open;

While not Eof do

Begin

With listview1.add do

Begin

CAPTION: = INTOSTR (FieldByName) .asinteger;

Subitems.add (FieldByName ('firstname') .sstring);

Subitems.Add (FieldByName ('lastname') .sstring);

Subitems.Add (Formator (FieldByname) .aSDateTime);

END;

NEXT;

END;

CLOSE;

END;

There are mainly several aspects here:

1. The first is that there are many code very lengthy. For example, FieldByname and Asxxx, etc., especially asxxx, you must always remember what type of each field is very easy to make mistakes. And some incompatible types can be found when they cannot be automatically converted.

2, you need to handle the current record in the loop. As mentioned above, otherwise, once you forget, you will have a dead cycle, although this problem is easy to discover and deal with, but programmers should not be entangled in such small details.

3, the most important thing is that the field name passes through the string parameter. If you write, you will find it when you run, add a potential bug possibility, especially if the test does not completely override all FieldByname, it is likely to make this The problem will appear on the other side. This case is very easy to happen, especially when the program uses multiple tables, it is easy to mix the field names of different tables.

In this era of OO, when you encounter an operation related to a data set, we have to often fall into the details of these relational databases mentioned above. Of course, there is also a way to get rid of them, that is, o / r mapping, but o / r mapping is too big to have a traditional development method, especially for some small applications, there is no need to exaggerate, in this case We need only a simple data set objectification program.

Under the inspiration of Java and other dynamic languages, I thought of this simple dataset objectification scheme with Delphi powerful RTTI. Below is a data set objectification application code that implements the same functionality with traditional code:

Type

TDSPEMPLOYEE = Class (TMDataSetProxy)

Published

Property EmployeeiD: Integer Index 0 Readinteger;

Property Firstname: String Index 1 Readstring Write Setstring;

Property Lastname: String Index 2 Read GetString Write SetString; Property Birthdate: Variant Index 3 Read Variant Write setvariant;

END;

Procedure TFORM1.LISTCLICK (Sender: TOBJECT);

VAR

EMP: TDSPEMPLOYEE;

Begin

EMP: = TDSPEMPLOYE.CREATE (AdoDataSet1);

Try

While (Emp.Foreach) DO

With listview1.items.add do

Begin

CAPTION: = INTOSTR (EMP.EMPLOYEEID);

Subitems.add (Emp.Firstname);

Subitems.Add (Emp.lastname);

Subitems.Add (Formator-mm-dd ', tdatetime (emp.birthdate)));

END;

Finally

Emp.free;

END;

END;

The usage is very simple. The most important thing is to define an agent class, in which all fields are defined in the Published attribute, including its type, and then operate the data set in the way. This proxy class is derived from TMDataSetProxy, where the RTTI implements the mapping from the property to the field operation, as long as the corresponding unit is simply Uses. The implementation unit of this class will be described in detail below.

On the surface, there is more proxy classes that define the data set, which seems to have some code, but this is a one-time thing, especially when the program needs multiple data sets, it will make the code. The amount is greatly reduced. What's more, this proxy class is very simple, just define a series of properties based on the field name and field type, do not use any implementation code. The attribute access function of which is used in it is implemented in the base class TMDataSetProxy.

Now let's see the loop corresponding to the original code:

1, FieldByname and Asxxx do not need, becoming the properties of the proxy class, and the type of attribute corresponding to each field is already defined in front, no need to use it again to consider what type it is? of. If you use a wrong type, you will report an error in compile.

2, use a Foreach to record traversal, don't worry about forgetting the death cycle caused by next.

3, the biggest benefit is that the field name has become a property, so you can enjoy the benefits of the compile time field name check, unless you define the agency class, you can write the field name, otherwise you can find it at compile.

TMDataSetProxy is now discussed. The code achieved is as follows:

(********************************************************* ******************

Data sets implemented with RTTI can simply use data set objects.

Copyright (c) 2005 by Mental Studio.

Author: Raptor

Date: jan.28-05

*********************************************************** ****************)

Unit mdspcomm;

Interface

Uses

Classes, DB, Typinfo;

Type

TMPropList = Class (TOBJECT)

Private

FPROPCOUNT: INTEGER;

FProplist: pproplist;

protected

Function getPropname (aindex: integer): shortstring; function getprop (aindex: integer): PPropInfo;

public

Constructor Create (AOBJ: TPERSIStent);

DESTRUCTOR DESTROY; OVERRIDE;

Property PropCount: Integer Read FPROPCOUNT;

Property PropNames [Aindex: Integer]: shortstring read getpropname;

Property Props [Aindex: Integer]: PPropInfo ReadProp;

END;

TMDataSetProxy = Class (TPERSIStent)

Private

FDataSet: TDataSet;

FProplist: tmproplist;

FLOOPING: BOOLEAN;

protected

Procedure beginedit;

PROCEDURE Endedit;

Function GetInteger (Aindex: Integer): Integer; Virtual;

Function getfloat (aindex: integer): Double; Virtual;

Function GetString (Aindex: Integer): String; Virtual;

Function getvariant (aindex: integer): Variant; Virtual;

Procedure setInteger (Aindex: Integer; Avalue: Integer); Virtual;

Procedure setfloat (aindex: integer; avalue: double); virtual;

Procedure setstring (aindex: integer; avalue: string); virtual;

Procedure setvariant (aindex: integer; avalue: variant); virtual;

public

Constructor Create (Adataset: TDataSet);

DESTRUCTOR DESTROY; OVERRIDE;

Procedure afterconstruction; Override;

Function foreach: boolean;

Property DataSet: TDataSet Read FDataSet;

END;

IMPLEMentation

{TMPropList}

Constructor TMProplist.create (AOBJ: TPERSIStent);

Begin

FPROPCOUNT: = GettypedTata (AOBJ.CLASSIINFO) ^. PropCount;

FProplist: = nil;

IF fpropcount> 0 THEN

Begin

GetMem (FProplist, FPropCount * Sizeof (Pointer);

GetPropinfos (Aobj.classInfo, FProplist);

END;

END;

Destructor TMProplist.destroy;

Begin

IF assigned (fproplist) THEN

FreeMem (FProplist);

inherited;

END;

Function TMProplist.getProp (Aindex: Integer): PPropInfo; Begin

Result: = NIL;

IF (assigned (fproplist))

Result: = fproplist [aindex];

END;

Function TMProplist.getPropname (aindex: integer): shortstring;

Begin

Result: = getProp (aindex) ^. Name;

END;

{TMREFDataSet}

Constructor TMDataSetProxy.create (Adataset: TDataSet);

Begin

Inherited Create;

FDataSet: = adataset;

FDataSet.Open;

FLOOPING: = FALSE;

END;

Destructor TMDataSetProxy.destroy;

Begin

FProplist.free;

IF assigned (fdatan) THEN

FDataSet.Close;

inherited;

END;

Procedure tmdatasetproxy.AFTERCONSTRUCTION;

Begin

inherited;

FProplist: = tmproplist.create (Self);

END;

Procedure tmdatasetproxy.beginedit;

Begin

IF (fdataSet.State <> dsedit) and (fdataSet.State <> DSInsert) THEN

FDataSet.edit;

END;

Procedure tmdatasetproxy.endedit;

Begin

IF (fdataSet.State = dsedit) or (fdataset.state = dsinsert) THEN

FDataSet.post;

END;

Function TMDataSetProxy.GetInteger (Aindex: Integer): Integer;

Begin

Result: = fdataSet.fieldByname (fProplist.propnames [aindex]) .asinteger;

END;

Function TMDataSetProxy.Getfloat (aindex: integer): Double;

Begin

Result: = fdatanet.fieldbyName (fProplist.propnames [aindex]) .asfloat;

END;

Function TMDataSetProxy.getstring (aindex: integer): String;

Begin

Result: = fdatanet.fieldbyName (fProplist.propnames [aindex]) .sstring;

END;

Function TMDataSetProxy.Getvariant (aindex: integer): Variant;

Begin

Result: = fdataSet.fieldbyName (fProplist.propNames [aindex]) .value;

END;

Procedure TMDataSetProxy.setInteger (Aindex, Avalue: Integer);

Begin

Beginedit;

FDataSet.fieldByname (fProplist.propnames [aindex]) .asinteger: = avalue;

END;

Procedure TmdataSetProxy.setFloat (Aindex: Integer; Avalue: double);

Begin

Beginedit;

FDataSet.fieldbyName (fProplist.propnames [aindex]) .asfloat: = Avalue;

END;

Procedure tmdatasetproxy.setstring (aindex: integer; avalue: string);

Begin

Beginedit;

FDataSet.fieldbyName (fProplist.propnames [aindex]) .sstring: = avalue;

END;

Procedure TMDataSetProxy.Setvariant (Aindex: Integer; Avalue: Variant);

Begin

Beginedit;

FDataSet.fieldByname (fProplist.propnames [aindex]) .value: = avalue;

END;

Function TMDataSetProxy.Foreach: Boolean;

Begin

Result: = NOT fdatanet.eof;

IF flooping then

Begin

Endedit;

FDataSet.next;

Result: = NOT fdatanet.eof;

IF NOT RESULT THEN

Begin

FDataSet.first;

FLOOPING: = FALSE;

END;

End

Else if result

FLOOPING: = True;

END;

End.

Where the TMProplist class is a package for the operating part of the RTTI attribute. It features some of the RTTI functions that Delphi defined in the Typinfo unit, implements a TPERSIStent's derived class to maintain its publish's property list information. The proxy class acquires the attribute name through this property list and eventually operates with the corresponding fields in the data set.

TMDataSetProxy is the base class of the dataset agent class. Its most important part is to create a list of properties in the AfterConstruction.

The operation of the property only implements the four data types of Integer, Double / Float, String, and Variant. If needed, you can derive your own agent base class to implement other data types, and the properties of these have been implemented is defined as virtual functions, or you can use yourself in the derived base class. Implementation replaces it. However, for types that are not very common, it is recommended to define the actual proxy class. For example, in the previous example, suppose TDATETIME is not a common type, you can do this:

TDSPEMPLOYEE = Class (TMDataSetProxy)

protected

Function getDatetime (const index: integer): tdatetime;

Procedure setDateTime (const index: integer; const value: tdatetime);

Published

Property EmployeeiD: Integer Index 0 Readinteger;

Property Firstname: String Index 1 Readstring Write Setstring;

Property Lastname: String Index 2 Readstring Write Setstring;

Property Birthdate: TDATETIME INDEX 3 ReadDateTime; END;

{TDSPEMPLOYEE}

Function TDSPEMPLOYE.GETDATETIME (Const Index: Integer): TDATETIME

Begin

Result: = TDATETIME (GetVariant (Index));

END;

Procedure tdspemployee.setdatetime (const index: integer;

Const value: TDATETIME);

Begin

Setvariant (Index, Value);

END;

In this way, BirthDate can be used as a TDATETIME type directly.

In addition, this is used to provide a unified operation for some custom special data types.

In addition, you call BeGineDit before all setxxx to avoid the runtime error caused by DataSet.edit.

Foreach is achieved, which can be reused, and after each Foreach is completely traversed, the current record is recorded on the first record of the next time. In addition, the endedit and automatic submission of the Endedit are called before NEXT.

This data set objectification scheme is a very simple solution. The biggest problem now is that the attribute's index parameter must be strictly followed by the order when defined, otherwise the error field will be taken. This is because Delphi is still a native development language. The only way to distinguish between the same type of different attributes when calling getxxx / setxxx is through Index, and this index parameter is determined to pass the function when compiling, and there is not a dynamic The table is recorded, so it can only be used in this way.

[Mental Studio] Raptor JAN.28-05

转载请注明原文地址:https://www.9cbs.com/read-62351.html

New Post(0)