Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to update a field in a record knowing the field name and value

Tags:

Given a Record:

MyRecord = record
    Company: string;
    Address: string;
    NumberOfEmplyees: integer;

can you write a function call like

function UpdateField(var FieldName: string; FieldValue: variant): bool;

so that:

UpdateField('Company', 'ABC Co');

would update MyRecord.Company to 'ABC Co'?

I looked for an example but everything I found is for a database. Any help pointing me in the right direction is appreciated.

Thanks, Charles

like image 235
Charles Avatar asked Jun 22 '11 20:06

Charles


People also ask

How do I update a field value in Salesforce?

From Setup, enter Field Updates in the Update box, and select Field Updates. Then use these settings to configure your field update. Before you begin, check the type of the field you want to update. Read-only fields like formula or auto-number fields are not available for field updates.

How to update field in flows?

Identify Salesforce records to update, and set the values to change in those records. To do so, use the IDs and field values stored in a record variable or record collection variable, or use specify conditions to identify the records and set the field values individually.

How do you update toggle fields in Word?

To update a single field, select it (or click anywhere in it) and press F9, or right-click the field and choose Update Field. If you have toggled on the display of the field code string, updating the field toggles it back to displaying the results.


2 Answers

What Delphi 7 RTTI knows, and can be retrieved from TypeInfo(aRecordType), is:

  • The record type name;
  • The record global size;
  • The offset and type of each reference-counted variable within the record (string/variant/widestring/dynamic array/other nested record containing reference-counted variables).

The latest information is necessary to free the memory used by each reference-counted variables inside the record, or copy the record content, at run-time. The initialization of the record is also performed either in compiler-generated code (if the record is created on the stack), either via _InitializeRecord() method, either with a global fill to 0 when a class or a dynamic array is instanciated.

It's the same for both record and object types, in all version of Delphi.

You can note that there is a bug in modern version of Delphi (including Delphi 2009 and 2010 at least), which sometimes don't create the code for initializing objects on stack. You'll have to use record instead, but it will break compatibility with previous version of Delphi. :(

Here are the structure used for storing this RTTI data:

type
  TFieldInfo = packed record
    TypeInfo: ^PDynArrayTypeInfo; // information of the reference-counted type
    Offset: Cardinal; // offset of the reference-counted type in the record
  end;
  TFieldTable = packed record
    Kind: byte;
    Name: string[0]; // you should use Name[0] to retrieve offset of Size field
    Size: cardinal;  // global size of the record = sizeof(aRecord)
    Count: integer;  // number of reference-counted field info
    Fields: array[0..0] of TFieldInfo; // array of reference-counted field info
  end;
  PFieldTable = ^TFieldTable;

Using this data, here is for instance what you can do:

  • Some optimized low-level asm version of the Delphi 7 System.pas, corresponding to the records (including a faster CopyRecord function);
  • A way of accessing, saving and loading record content (also inside dynamic arrays, providing TList-like methods - and more - for a dynamic array of records).

For instance, here is how two records of the same type can be compared, using this RTTI:

/// check equality of two records by content
// - will handle packed records, with binaries (byte, word, integer...) and
// string types properties
// - will use binary-level comparison: it could fail to match two floating-point
// values because of rounding issues (Currency won't have this problem)
function RecordEquals(const RecA, RecB; TypeInfo: pointer): boolean;
var FieldTable: PFieldTable absolute TypeInfo;
    F: integer;
    Field: ^TFieldInfo;
    Diff: cardinal;
    A, B: PAnsiChar;
begin
  A := @RecA;
  B := @RecB;
  if A=B then begin // both nil or same pointer
    result := true;
    exit;
  end;
  result := false;
  if FieldTable^.Kind<>tkRecord then
    exit; // raise Exception.CreateFmt('%s is not a record',[Typ^.Name]);
  inc(PtrUInt(FieldTable),ord(FieldTable^.Name[0]));
  Field := @FieldTable^.Fields[0];
  Diff := 0;
  for F := 1 to FieldTable^.Count do begin
    Diff := Field^.Offset-Diff;
    if Diff<>0 then begin
      if not CompareMem(A,B,Diff) then
        exit; // binary block not equal
      inc(A,Diff);
      inc(B,Diff);
    end;
    case Field^.TypeInfo^^.Kind of
      tkLString:
        if PAnsiString(A)^<>PAnsiString(B)^ then
          exit;
      tkWString:
        if PWideString(A)^<>PWideString(B)^ then
          exit;
      {$ifdef UNICODE}
      tkUString:
        if PUnicodeString(A)^<>PUnicodeString(B)^ then
          exit;
      {$endif}
      else exit; // kind of field not handled
    end;
    Diff := sizeof(PtrUInt); // size of tkLString+tkWString+tkUString in record
    inc(A,Diff);
    inc(B,Diff);
    inc(Diff,Field^.Offset);
    inc(Field);
  end;
  if CompareMem(A,B,FieldTable.Size-Diff) then
    result := true;
end;

So for your purpose, what Delphi 7 RTTI could let you know at runtime, is the position of every string within a record. Using the code above, you could easily create a function using the field index:

     procedure UpdateStringField(StringFieldIndex: integer; const FieldValue: string);

But you simply don't have the needed information to implement your request:

  • The field names are not stored within the RTTI (only the global record type name, and even not always AFAIK);
  • Only reference-counted fields have an offset, not other fields of simple type (like integer/double...).

If you really need this feature, the only solution under Delphi 7 is to use not records, but classes.

On Delphi 7, if you create a class with published fields, you'll have all needed information for all published fields. Then you can update such published field content. This is what the VCL runtime does when unserializing the .dfm content into class instances, or with an ORM approach.

like image 109
Arnaud Bouchez Avatar answered Jan 03 '23 04:01

Arnaud Bouchez


You need modern versions of Delphi to do what you ask for without resorting to manually coding the lookups, e.g. via a table.

The updated RTTI introduced in Delphi 2010 can support what you are looking for, but there's nothing in Delphi 7 that will do this for records.

like image 24
David Heffernan Avatar answered Jan 03 '23 04:01

David Heffernan