Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Set of Enumeration Type in Datenbank-Parameter zuweisen (https://www.delphipraxis.net/180700-set-enumeration-type-datenbank-parameter-zuweisen.html)

DelphiBandit 10. Jun 2014 13:14

Delphi-Version: XE5

Set of Enumeration Type in Datenbank-Parameter zuweisen
 
Hallo zusammen,

ich habe eine vermutlich ganz triviale Frage. Der Laufzeitfehler bezüglich Typkonvertierung beschäftigt mich allerdings schon eine Weile, ohne dass ich bisher eine Lösung gefunden hätte.

Delphi-Quellcode:
type
  TBankFlag = (fbfEinzahlung, fbfAuszahlung);
type
  TBankFlags = set of TBankFlag;

// Diese in im Objekt als Property vorhanden
type
  TFoo = type(TObject)
    FFlags: TBankFlags;
  published
    Flags: TBankFlags read FFlags write FFlags;
  end;
Den Wert bestimme ich per RTTI und bekomme einen Wert vom Typ TValue zurück. Jetzt möchte ich diesen in der Datenbank als Integer speichern. Dabei kommt aber immer ein Laufzeitfehler "Ungültige Typumwandlung", obwohl ich in value.AsInteger den korrekten Wert im Debugger sehen kann. Das Feld Flags ist in der Datenbank als Integer angelegt.

foo.Flags := [fbfEinzahlung, fbfAuszahlung] -> value.AsInteger = 3
foo.Flags := [fbfEinzahlung] -> value.AsInteger = 1

Delphi-Quellcode:
..
var value: TValue;
begin
  // ...value-Bestimmung...
 
  qry.ParamByName('FLAGS').AsInteger := value.AsInteger;
end;
Egal welchen Typ ich mir von TValue mit .AsXXX geben lasse, es knallt beim Zuweisen dieses Parameters. Noch eine Anmerkung, Flags kann je nach Objekt verschiedene ENum-Bedeutungen haben. Also ist AsType<T> für mich nicht so zielführend.

Geht das überhaupt, was ich da vorhabe?

himitsu 10. Jun 2014 13:19

AW: Set of Enumeration Type in Datenbank-Parameter zuweisen
 
Früher hat man das so gemacht:
Delphi-Quellcode:
  qry.ParamByName('FLAGS').AsInteger := Byte(value);

Ein Set ist nunmal kein "ordinaler" Typ.

Oder du gehst über die RTTI und wandelst das in einen String um.

Neutral General 10. Jun 2014 13:32

AW: Set of Enumeration Type in Datenbank-Parameter zuweisen
 
Hallo,

Hab grad rumprobiert und hatte auch so meine Probleme, aber so gehts:

Delphi-Quellcode:
var b: Byte;
begin
  // ...
  value.ExtractRawData(@b);
  qry.ParamByName('FLAGS').AsInteger := b;
  // ODER
  qry.ParamByName('FLAGS').AsInteger := PByte(value.GetReferenceToRawData)^;
end;

Der schöne Günther 10. Jun 2014 13:38

AW: Set of Enumeration Type in Datenbank-Parameter zuweisen
 
Ich würde es als String in die Datenbank speichern. Wenn da später jemand draufguckt, könnte man noch eine ungefähre Vorstellung dafür bekommen, was das ist.

Abgesehen davon- Kannst du deinem Enum nicht eine Helfer-Methode verpassen?

Delphi-Quellcode:
type
   TMeinEnum = (Hund, Katze, Maus);

   TMeinEnumHelper = record helper for TMeinEnum
      function ToString(): String;
      function ToInteger(): Integer;
      // function ToField(): TField // oder so ähnlich?
   end;
   
implementation uses System.TypInfo;

function TMeinEnumHelper.ToString(): String;
begin
   Result := GetEnumName(
      TypeInfo(TMeinEnum), // Den Namen hier nochmal manuell zu nennen muss wohl...
      Ord(self)
   );
end;

function TMeinEnumHelper.ToInteger(): Integer;
begin
   Result := Ord(self);
end;

DelphiBandit 10. Jun 2014 14:02

AW: Set of Enumeration Type in Datenbank-Parameter zuweisen
 
Danke für die superschnelle Hilfe, ich werde für den Moment mal mit Michaels Ansatz weiterarbeiten.

Das mit der Helper-Klasse hätte ich wohl schon gemacht, wenn im rechten Teil des Helpers auch ein generischer Typ angegeben werden könnte :) Ich habe bisher drei unterschiedliche Typen von "FLAGS", je nachdem in welchem Objekt. Und jedem Set sein eigenes Helper-Objekt an die Seite zu stellen mit dreimal dem gleichen Quellcode bis auf den Typ :(

Die Überlegung das Ganze in Strings in die Datenbank zu speichern ist gut, erschwert aber nachher die Suche. Mit dem Integer brauche ich nur mit logischem AND eine Bitposition in FLAGS abfragen. So war zumindest meine Grundüberlegung.

Aber wenn ich so drüber nachdenke, könnte ich das ganze Set auch in eigenes Objekt verschieben und mit einzelnen boolschen Property-Werten arbeiten. Macht den Quellcode lesbarer, aber die nachfolgende Speicherung und Suche auch nicht gerade übersichtlicher.

Dejan Vu 10. Jun 2014 16:48

AW: Set of Enumeration Type in Datenbank-Parameter zuweisen
 
Eine Alternative und von den Restriktionen des ENUM-SET gänzlich unabhängige Variante wäre die, die einzelnen Ausprägungen des Enums in eine Tabelle zu packen und eine n:m-Beziehung zwischen dem Objekt und der Enum-Tabelle zu erstellen.

Das ist eine allgemeingültige Lösung, die vollständig im DB-Schema dokumentiert ist und die es erlaubt, sehr einfach Queries zu erstellen, ohne im Code zu schauen, wie den das SET nun kodiert wurde.

Einfach ausgedrückt:
Tabelle 'Enum'
IdName
1Rot
2Grün
3Blau
Tabelle 'Daten'
IdName
10Meyer
11Müller
12Schulz
Tabelle 'DatenEnums'
IdEnumIdDaten
110
211
311
Meyer ist rot und Müller ist grün und blau.

smallie 12. Jun 2014 22:35

AW: Set of Enumeration Type in Datenbank-Parameter zuweisen
 
Zitat:

Zitat von DelphiBandit (Beitrag 1261831)
Das mit der Helper-Klasse hätte ich wohl schon gemacht, wenn im rechten Teil des Helpers auch ein generischer Typ angegeben werden könnte :) Ich habe bisher drei unterschiedliche Typen von "FLAGS", je nachdem in welchem Objekt. Und jedem Set sein eigenes Helper-Objekt an die Seite zu stellen mit dreimal dem gleichen Quellcode bis auf den Typ :(

Ich mach' das so:

Delphi-Quellcode:
uses
  TypInfo;

type
  TEnumHelper = class
  private
    FEnumInfo: PTypeInfo;
  public
    Constructor Create(AEnumInfo: PTypeInfo);
    function ToString(const EnumValue): string; //typloser Parameter
    function ToEnum(const Name: string): integer;
  end;


implementation

constructor TEnumHelper.Create(AEnumInfo: PTypeInfo);
begin
  FEnumInfo := AEnumInfo;
end;

function TEnumHelper.ToString(const EnumValue): string;
begin
  Result := GetEnumName(FEnumInfo, ord(TOrdType(EnumValue))); //typloser Parameter wird gecastet
end;


function TEnumHelper.ToEnum(const Name: string): integer;
begin
  Result := GetEnumValue(FEnumInfo, Name);
end;
So läßt sich damit arbeiten:

Delphi-Quellcode:
//Enumeration:
TTasteBuds = (tbSour, tbSweet, tbSalty, tbSpicey, tbUmami)

//Erzeugung und Verwendung
TasteBuds   := TEnumHelper.Create(TypeInfo(TTasteBuds));
showMessage(TasteBuds.ToString(tbSour)); //"tbSour"

//oder andere Richtung
var SomeTasteBud: TTasteBuds;

SomeTasteBud := TTasteBuds(TasteBuds.ToEnum(tbSour)); //hier muß zurückgecastet werden. :(
Auf eine Lösung, wie ich diesen letzten Cast einsparen kann, bin ich noch nicht gekommen. Wenn nur eine Zahl herauskommen soll, passt es.

himitsu 12. Jun 2014 23:24

AW: Set of Enumeration Type in Datenbank-Parameter zuweisen
 
Nja, es geht auch andersrum.


Delphi-Quellcode:
Byte(SomeTasteBud) := TasteBuds.ToEnum(tbSour);


oder
Delphi-Quellcode:
var
  SomeTasteBudByte: Byte absolute SomeTasteBud;

SomeTasteBud := TasteBuds.ToEnum(tbSour);

Oder du definierst das TEnumHelper.ToEnum als Prozedur und gibst das Result ebenfalls als typlosen VAR/OUT-Parameter raus, genauso wie beim CONST.



Wenn du dir in den letzten paar 5 Jahren mal ein neueres Delph zugelegt hättest, dann ginge auch sowas.

Delphi-Quellcode:
RTTIServices = class
  class function SetToStr<TSet>(const Value: TSet; Brackets: Boolean=True): string; static;
  class function StrToSet<TSet>(const Value: string): TSet; static;

  class function EnumToStr<TEnum>(const Value: TEnum): string; static;
  class function StrToEnum<TEnum>(const Value: string): TEnum; static;
end;



var
  S: string;
  E: TTasteBuds;

S := RTTIServices.EnumToStr<TTasteBuds>(tbSour);
E := RTTIServices.StrToEnum<TTasteBuds>('tbSour');
Das entspricht in etwa deinem Code, wenn man den Typ direkt mit übergibt
Delphi-Quellcode:
ShowMessage(TasteBuds.ToString(TypeInfo(TTasteBuds), Ord(tbSour))); // 'tbSour'

SomeTasteBud := TTasteBuds(TasteBuds.ToEnum(TypeInfo(TTasteBuds), 'tbSour')); // tbSour


Es liese sich im Aufruf nochmal etwas kürzen, wenn man den generischen Typ nicht an die Methoden, sondern an die Klasse bindet.
Delphi-Quellcode:
type
  EnumService<TEnum> = class
    class function EnumToStr(const Value: TEnum): string; static;
    class function StrToEnum(const Value: string): TEnum; static;
  end;

type
  X = EnumService<TTasteBuds>;

var
  S: string;
  E: TTasteBuds;

S := EnumService<TTasteBuds>.EnumToStr(tbSour);
E := EnumService<TTasteBuds>.StrToEnum('tbSour');

S := X.EnumToStr(tbSour);
E := X.StrToEnum('tbSour');



Und nein, TOrdType ist nicht immer richtig.
Eigentlich msstest du aus dem TypeInfo die Enum-Größe auslesen und dann entweder nach Byte, Word oder Integer casten,
denn wenn dein ENUM mehr als 256 Werte enthält, dann hast du ein Problem.


Alle Zeitangaben in WEZ +1. Es ist jetzt 02:08 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz