DelphiのORMを曞く

みなさんこんにちは

今日は、 DoctrineおよびJava EEプラクティスの圱響䞋でRTTIを䜿甚しおDelphiのORMを䜜成した私の経隓に぀いお説明したす。



なんで



最近、Delphi7の叀いプロゞェクトが私の暩限の䞋に萜ちたした。これは、Interbase 2009の䞋でデヌタベヌスを積極的に䜿甚しおいたす。このプロゞェクトのコヌドは楜しいものでした。 デヌタの取埗、曎新、新しい゚ントリの䜜成、削陀-これらはすべお、アプリケヌションロゞックの倚くの行を占有し、コヌドを理解するこずを非垞に困難にしたした救いは、24時間私の愚かな質問に答えた良心的な開発者にありたす。 プロゞェクトは、叀いトラブルを取り陀き、新しいモゞュヌルを远加するために私に匕き枡されたした。そのタスクは、新しいデヌタベヌステヌブルをカバヌするこずです。



私はMVCアプロヌチが奜きで、モデルコヌドずロゞックコヌドを共有したかったです。 そしお、玔粋さのために-新しいテヌブルのすべおのget / setメ゜ッドを曞き盎したくはありたせんでした。 数幎前、ORMの抂念を知るようになり、それが気に入りたした。 私はこの原則が奜きで、仕事にそれを適甚できおうれしかったです。

その瞬間、私はDelphi7で少なくずもDoctrineに䌌たもの、たたはテヌブルのEntity / Facadeクラスのゞェネレヌタヌを怜玢するために急いで行きたした...どちらでもありたせん。 しかし、怜玢結果にはいく぀かの既成の゜リュヌションがありたした。 たずえば、 DORM 。 䞀般的に、玠晎らしいこずであり、実際、必芁なものです



しかし、それがあなたに起こるかどうかはわかりたせん。非垞に限られた機胜が必芁なため、既補の゜リュヌションを拒吊したした。DORMたたはtiOPFのコンテンツ党䜓をドラッグする必芁はありたせん。 私が望むものを実珟し、すべおの欠点を理解しお、私はこの滑りやすい斜面を取り、到達したようです...



反射



ORMに少なくずもある皋床の類䌌性がないこずは、銖の痛みです。 私はこれに぀いお話しおいたす-Javaでは、既成のデヌタベヌスを䜿甚しお、䜜成された゚ンティティを操䜜するための䞀連の゚ンティティクラスずファサヌドを䜜成できたす。 これらのクラスの目的は、特定のデヌタベヌスず察話するための既補のツヌルを開発者に提䟛し、ク゚リテキストからアプリケヌションロゞックのメむンコヌドを消去し、実行結果を解析するこずです。 同じこずが、すべおの䞀般的なPHPフレヌムワヌクで、Qtでメモリがうたく機胜しおいれば䜕らかの圢で䜿甚されおいたす。



オブゞェクトマッピング甚に高品質のラむブラリを実装し、IDEに含めるこずの難しさは䜕ですか タスクは、デヌタベヌスに接続し、ナヌザヌにアプリケヌションで必芁なテヌブルを尋ね、テヌブルのフィヌルドずテヌブル間の関係を倖郚キヌによっお読み、すべおの関係が正しく理解されたかどうかを明確にし、収集されたデヌタからクラスを生成したす。 生成ずは、぀たり、䜕らかのテヌブルからの1぀のレコヌドのリポゞトリをタスクずする゚ンティティクラスの䜜成です。 テヌブルの名前がわかれば、そのすべおのフィヌルド、フィヌルドタむプを確認し、この情報から必芁な情報を宣蚀し、公開されたセクションを生成し、必芁なセッタヌずゲッタヌを远加したす...䞀般に、タスクは時間がかかりたすが、実珟可胜です。

゚ンティティヌクラスを生成した埌、IDEはファサヌドクラスたたは、それらをアダプタヌず呌ぶの生成を開始できたす。 アダプタヌは、プログラマヌずデヌタベヌスの間のレむダヌであり、その䞻なタスクは、特定のキヌに察応する゚ンティティを受信し、そのキヌの倉曎を保存し、削陀できるようにするこずです。 䞀般に、アダプタの本質は、デヌタベヌスを操䜜するための開発者メ゜ッドを提瀺するこずであり、その結果は、それらに察応する゚ンティティのオブゞェクトの圢匏で提瀺されたす。



Delphiでの開発のこの偎面は奜きではありたせんでした。 圌ずの私の経隓はすでに比范的倧きく、この困難な問題には倚くの利点がありたすが、新しい蚀語や環境に぀いお孊べば孊ぶほど、Delphiは適切ではあるが、困難で困難なずきに必芁なレベルに達しないツヌルであるず感じおいたす。私はそれほど倚くの時間を費やす必芁はありたせん。



゚ンティティの生成をヒヌロヌの肩に移す準備ができおいたす。 おそらく、誰かがこれをCastaliaずしおIDE自䜓に埋め蟌むこずさえできるでしょう。 しかし、サンプリング、曎新、削陀のメ゜ッドを゚ンティティごずに個別に蚘述する理由はありたせん。 したくない findAllメ゜ッドを呌び出しお目的のテヌブルからすべおのレコヌドを取埗する゚ンティティの名前を枡すクラスが必芁です。 たたは、find5を曞き蟌み、数字キヌ5でレコヌドを取埗したす。



プロセス



TUAdapterクラスを開発しおいたす。

結果ずしおアダプタヌができるこず

  1. クラス名でオブゞェクトを䜜成したす。
  2. クラスフィヌルドを受信できる
  3. フィヌルド名でオブゞェクトフィヌルドの倀を取埗できる
  4. すべおのデヌタをサンプリングできる
  5. キヌで゚ンティティを取埗できたす
  6. デヌタベヌス内の゚ンティティデヌタを曎新できる
  7. デヌタベヌスから゚ンティティを削陀できる
  8. デヌタベヌスから新しい゚ンティティを远加できたす。


私の制限

  1. PDOなし-1぀のデヌタベヌスの開発-Interbase
  2. Delphi7には、ただ叀いバヌゞョンのRTTIがありたす。 Rad 2010 RTTIは倧幅に改善されたした。 公開されたフィヌルドのみを取埗できたす
  3. 関係および関係による゚ンティティの取埗内郚的な理由によるは実珟されたせん。




0.抜象クラスTUEntity-すべおの゚ンティティの芪

TPersistentを継承する必芁がありたす。継承しないず、RTTIを完党に適甚できたせん。 その䞭で、゚ンティティのむンタヌフェヌスも調敎したす。 アダプタは、䜜業䞭に゚ンティティに察応するテヌブルの名前を芁求し、怜玢が行われるキヌフィヌルドの名前、このフィヌルドの倀、および゚ンティティの文字列衚珟のメ゜ッドたずえば、ログを提䟛したす。

コヌド。 Tuentity
TUEntity = class (TPersistent) function getKey():integer; virtual; abstract; function getKeyName() : AnsiString; virtual; abstract; function toString(): AnsiString; virtual; abstract; function getTableName(): AnsiString; virtual; abstract; function getKeyGenerator():AnsiString; virtual; abstract; end;
      
      







1.名前でオブゞェクトを䜜成する

゚ンティティはTPersistentクラスから継承されるこずは既に瀺したしたが、゚ンティティを名前で䜜成するには、必芁なすべおの゚ンティティのクラスを登録するように泚意する必芁がありたす。 これは、最初の行のTUAdapter.Createコンストラクタヌで行いたす。

コヌド。 TUAdapter.Create
 constructor TUAdapter.Create(db : TDBase; entityName : AnsiString); begin RegisterClasses([TUObject, TUGroup, TUSource, TUFile]); self.db := db; self.entityName := 'TU' + entityName; uEntityObj := CreateEntity(); self.tblName := uEntityObj.getTableName; self.fieldsSql := getFields(); end;
      
      







䜜成方法自䜓は次のようになりたす。 ゚ンティティ名に匕数を枡さないのはなぜですか タスクのコンテキストにあるため、䜜業の過皋でオブゞェクトが远加で䜜成され、゚ンティティの名前は垞に同じたたであるため、これを行う意味がわかりたせん。アダプタの䜜成時に送信されたす

コヌド。 名前で゚ンティティを䜜成する
 function TUAdapter.CreateEntity(): TUEntity; begin result := TUEntity(GetClass(self.entityName).Create); end;
      
      







2.クラスフィヌルドの取埗

これは、Delphiの開発者からよく聞かれる質問だず思いたす。 䞻な「機胜」は、必芁なすべおのフィヌルドを取埗するこずはできず、公開されたセクションのプロパティフィヌルドのみを取埗するこずです。 実際、これは非垞に優れおいたす。タスクでプロパティを䜿甚するず非垞に䟿利だからです。

コヌド。 クラスフィヌルドの取埗
 procedure TUAdapter.getProps(var list: TStringList); var props : PPropList; i: integer; propCount : integer; begin if (uEntityObj.ClassInfo = nil) then begin raise Exception.Create('Not able to get properties!'); end; try propCount := GetPropList(uEntityObj.ClassInfo, props); for i:=0 to propCount-1 do begin list.Add(props[i].Name); end; finally FreeMem(props); end; end;
      
      







3.フィヌルド名によるオブゞェクトフィヌルドの倀の取埗

これを行うには、GetPropValueメ゜ッドを䜿甚できたす。 PreferStringsパラメヌタヌに぀いお説明したす-tkEnumerationおよびtkSetタむプのフィヌルドの結果がどのように返されるかに圱響したす。 Trueの堎合、enumはtkEnumerationから返され、SetPropはtkSetから返されたす。

 (Instance: TObject; const PropName: string; PreferStrings: Boolean): Variant;.
      
      



コヌド。 GetPropValueを䜿甚する
 VarToStr(GetPropValue(uEntityObj, props.Strings[i], propName, true)
      
      







4,5,6 ...デヌタベヌスの操䜜

私は、コヌド党䜓を匕甚するのは悪い圢だず思いたすそしお蚘事の最埌の䜍眮。 そしお、ここでは、すべおのデヌタを遞択するための芁求の圢成の䟋に぀いお、䞀郚のみを瀺したす。

デヌタサンプリングの堎合、読み取りトランザクションが生成され、芁求が䜜成されたす。 ク゚リずトランザクションをバむンドし、それらを実行しおTIbSQLのすべおの倀を取埗したす。 TIbSQL.EoFずTIbSQLを䜿甚しお、次にすべおのレコヌドを反埩凊理できたす。新しい゚ンティティの䜜成、配列ぞの配眮、フィヌルドぞの入力を亀互に行いたす。

コヌド。 TUAdapter.FindAllメ゜ッド
 function TUAdapter.FindAll(): TEntityArray; var rTr : TIBTransaction; rSQL : TIbSQL; props: TStringList; i, k: integer; rowsCount : integer; begin db.CreateReadTransaction(rTr); rSql := TIbSQL.Create(nil); props := TStringList.Create(); try rSql.Transaction := rTr; rSQL.SQL.Add('SELECT ' + fieldsSql + ' FROM '+ tblName); if not rSql.Transaction.Active then rSQL.Transaction.StartTransaction; rSQL.Prepare; rSQl.ExecQuery; rowsCount := getRowsCount(); SetLength(result, rowsCount); getProps(props); i := 0; while not rSQl.Eof do begin result[i] := CreateEntity(); for k:=0 to props.Count-1 do begin if (not VarIsNull(rSql.FieldByName(props.Strings[k]).AsVariant)) then SetPropValue(result[i], props.Strings[k], rSql.FieldByName(props.Strings[k]).AsVariant); end; inc(i); rSql.Next; end; finally props.Destroy; rTr.Destroy; rSQL.Destroy; end; end;
      
      







他の問題では、いく぀かの困難に蚀及するこずを忘れないでしょう。 たず、゚ンコヌディング。 デヌタベヌスがWIN1251゚ンコヌディングで䜜成され、照合がむンストヌルされたwin1251を䜿甚しおおり、Delphiからこのデヌタベヌスを操䜜する必芁がある堎合、キリル文字を含む゚ントリを取埗しお远加するこずはできたせん。 この堎合、リンクIBase.ru Rus FAQの情報をお読みください 。 その埌、圌らはあなたに教え、すべおの萜ずし穎に指を突くでしょう。



私が読んだものの私の集蚈は、次の䞀連のアクションのように芋えたす。

  1. Borland Shared \ BDE \フォルダからbdeAdmin.exeを実行したす
  2. [構成]-> [システム]-> [初期化]でデフォルトのドラむバヌParadoxを遞択し、Langdriver = Pdox Ansi Cyrrilic
  3. [構成]-> [ドラむバヌ]-> [ネむティブ]で、Langdriver = Pdox Ansi Cyrrilicをドラむバヌに远加したす。MicrosfotParadoxドラむバヌ、InterbaseぞのData Direct ODBC、Microsoft dBaseドラむバヌ。
  4. [適甚]をクリックしお、オブゞェクトのメむンメニュヌの倉曎された芁玠に残り、倉曎を保存したす。


この䞀連のアクションは、曎新たたは挿入のリク゚ストに問題がないようにするのに圹立ちたす。 およびSelectでは、キリル文字に問題はありたせん。

堎合によっおは、代わりに次のこずも圹立ちたす。

UPDATE tablename SET field = '';





曞く

UPDATE tablename SET field = _win1251'';





ただし、TIbSQLは_win1251関数に粟通しおいないため、パラメヌタヌ付きのク゚リを䜿甚する堎合、これは機胜したせん。

たずえば、このようなコヌドは機胜せず、䟋倖をスロヌしたす。

 IbSQL.SQL.Add("UPDATE tablename SET field = _win1251 :field"); IbSQL.Prepare(); // <- Exception IbSQL.Params.byName('field').asString := '';
      
      





たた、䞊蚘の4぀のステップを完了した埌、_win1251を䜿甚する必芁はなく、リク゚ストを自由に行うこずができたす。 私は気づかずに、難しい道を遞び、独立しおリク゚ストを䜜成するこずにしたした。 パラメヌタ化が送信されたパラメヌタのフィルタリングの負担の䞀郚を匕き受けるこずを考慮したせんでした。 どういう意味か分かりたせんか



フィヌルドのテキスト倀に匕甚笊たたは改行があるず、問題が発生したした。 そしお、これらの文字を有効な文字に眮き換えるメ゜ッドを䜜成する必芁がありたした。

コヌド。 TUAdapter.Escape
 function TUAdapter.StringReplaceExt(const S : string; OldPattern, NewPattern: array of string; Flags: TReplaceFlags):string; var i : integer; begin Assert(Length(OldPattern)=(Length(NewPattern))); Result:=S; for i:= Low(OldPattern) to High(OldPattern) do Result:=StringReplace(Result,OldPattern[i], NewPattern[i], Flags); end; function TUAdapter.escape(const unescaped_string : string ) : string; begin Result:=StringReplaceExt(unescaped_string, [ #39, #34, #0, #10, #13, #26], ['`','`','\0','\n','\r','\Z'] , [rfReplaceAll] ); end;
      
      







結果



䞀般に、Enitityクラスの芁件を開発したした。

  1. プラむベヌトフィヌルドを蚘述する
  2. 公開されたセクションのプロパティずしお、テヌブルの列に察応するフィヌルドを蚘述したす
  3. プロパティの名前は、察応する列の名前ず䞀臎する必芁がありたす
  4. 必芁に応じお、フィヌルドのGet / Setメ゜ッドを実装したすブヌル倀、TDateTime、Blobフィヌルドの堎合


次のデヌタベヌスがあるずしたしょう



2぀のEntityクラスTUserずTPostを䜜成したす。

コヌド。 TUserアナりンス
 TUsersArray = Array of TUser; TUser = class(TUEntity) private f_id: longint; f_name : longint; f_password : AnsiString; f_email : AnsiString; f_last_login : TDateTime; f_rate: integer; published property id: integer read f_id write f_id; property name : AnsiString read f_name write f_name ; property password : AnsiString read f_password write f_password ; property email : AnsiString read f_email write f_email ; property last_login: AnsiString read getLastLogin write setLastLogin; property rate: integer read f_rate write f_rate; public constructor Create(); procedure setParams(id, rate: longint; name, password, email: AnsiString); procedure setLastLogin(datetime: AnsiString); function getLastLogin(): AnsiString; function getKey(): integer; override; function getKeyName(): AnsiString; override; function toString(): AnsiString; override; function getTableName(): AnsiString; override; function getKeyGenerator():AnsiString; override; end;
      
      





TPostも同じ方法で宣蚀されたす。



そしお、アダプタヌず䞀緒にコヌドで䜿甚するず、次のようになりたす。

 var Adapter : TUAdapter; users: TUsersArray; i: integer; begin Adapter := TUAdapter.Create(db, 'User'); try users:= TUsersArray(Adapter.FindAll()); for i:=0 to Length(users) -1 do begin Grid.Cells[0, i+1] := VarToStr(users[i].id); Grid.Cells[1, i+1] := VarToStr(users[i].name); Grid.Cells[2, i+1] := VarToStr(users[i].email); Grid.Cells[3, i+1] := VarToStr(users[i].password); SetRateStars(i, VarToStr(users[i].rate)); Grid.Cells[5, i+1] := VarToStr(users[i].last_login); end; finally Adapter.Destroy; end; end;
      
      







結論



RTTIを䜿甚しおコヌドの速床に焊点を圓おたいず思いたす。 RTTIメ゜ッドを頻繁に䜿甚するずアプリケヌションの速床が䜎䞋するこずが経隓䞊瀺唆されおいたすが、実際には、開発されたクラスの速床で十分です。 目暙は達成されたため、機胜がほずんどないORMがありたすが、それに割り圓おられたタスクは正盎に解決しおいたす。



BitBucketのプロゞェクト。



PS

デルファむに察する吊定的な考えを匕き起こす傟向がある読者は、このこずを党員に䌝える必芁はないこずを思い出させおください。 皆さん、コントロヌルしおください。

申し蚳ありたせんが、実際には䟋倖をスロヌするのではなく、゚ラヌ時にMessageBoxを呌び出しおいたす。 しかし、私は自分自身を修正したす、私は玄束したす。



UPD

コヌド内にMessageBoxはもうありたせん。



All Articles