![]() |
Best practice: Benutzerverwaltung mit Datenbankanbindung
Moin liebe DPler! :-)
Vorweg solltet ihr folgendes wissen: Ich arbeite in letzter Zeit viel mit C#, WPF und dem Entity Framework. Da läuft es ja so, dass man Datenobjekte direkt an Listen-Controls binden und über Templates die Datenobjektproperties an die Oberfläche durchreichen kann. Es gibt also keine eigenen "ListItems" mehr, sondern jedes Item ist ein eigenes Objekt meiner Datenklasse (z.B. "Benutzer", "Gruppe" usw.) Ändere ich in einem anderen Fenster den Namen des Objektes, wird dieser auch sofort in meinem List-Control geändert und dargestellt. Das ist also richtiges Binding, so wie es Embarcadero wohl mit den LiveBindings versuchen wollte... Allerdings gibt es so einige Sachen, die mir an WPF nicht so gefallen. Der größte Kritikpunkt ist für mich die Performance, die bei großen Fenstern mit vielen Controls auf schwachen Rechnern schnell mal in die Knie geht. Das ist in der VCL (Delphi), Thunderforms (VB6), MFC (C++), WinForms (.NET),... wesentlich performanter. Und in unserer Anwendung wird es viele solcher "Controlling-Fenster" geben. Jetzt zum eigentlichen Thema: Ich möchte meine Delphi-Kenntnisse mal auf den aktuellen Stand bringen. Erst mal möchte ich eine Benutzer / Gruppen / Mandantenverwaltung nach dem aktuellen Stand der Technik programmieren. Als Vorbild sehe ich die Computerverwaltung von Windows, bei der man die Benutzer und Gruppen in einer Art ListView sieht. Jeder Benutzer kann mehreren Gruppen zugeordnet sein, jede Gruppe kann beliebig viele Benutzer aufnehmen... Wie würden die Profis hier sowas umsetzen? 1) Klassen für Benutzer, Gruppen, Mandanten, Rechte usw. entwerfen Daten von der Datenbank abfragen und die Objekte daraus generieren lassen (Select-Statement) Items für das ListView-Control anlegen und im Data-Property einen Zeiger auf die Objekte legen Beim Bearbeiten eines Benutzers z.B. ein neues Fenster öffnen und dem das Benutzerobjekt übergeben Beim Speichern / Anlegen die Daten aus dem Objekt in die Datenbank schreiben (Update-Statement / Insert-Statement) Beim Speichern den Benutzernamen usw. im ListView aktualisieren Keine Datensenstiven Controls verwenden, sondern die entsprechenden Controls per Code füllen / lesen 2) So ähnlich wie 1 nur mit LiveBindings für die Präsentation der Daten (kommt dem Prinzip von WPF wohl am nähesten) 3) klassische Programmierung mit Recordsets und Datensensitiven Controls 4) ganz anders? Erst mal geht es mir nur um direkte Kommunikation zwischen Windows-Programm und Datenbank. Eine Multi-Tier-Anwendung ist bei uns wohl nicht unbedingt notwendig. (zumindest hat in den letzten 20 Jahren kaum jemand danach gefragt) Wenn man also so wie in Beispiel 1 vorgeht, wie werden dann die Objekte befüllt? Benutzt man hier besser ein ORM oder macht alles per Hand? Welches ORM bietet sich dabei an? So wie ich es verstanden habe, zieht man bei mORMot ja wieder eine Zwischenschicht in die Anwendung. Vom EntityFramework (Database First) kenne ich es so, dass ich mir die Datenbank designe und anschließend mit dem EntityFramework-Assistenten meine Klassen automatisch erzeuge. Über LINQ z.B. kann ich die Daten dann direkt von der Datenbank über die ADO.NET-Provider abfragen. Gibt es solche ORMs in Delphi auch? Oder anders gefragt: Hat diese Zwischenschicht in mORMot irgendwelche Nachteile? Ich würde dabei jetzt mit Performanceverlust rechnen, wenn man viele Daten schnell abfragen muss. Ich denke das sind Grundlagen, die ich in Delphi noch nicht ganz verstanden habe, bzw. früher immer anders gemacht habe (Datensenstive Controls), bitte seid also gnädig mit mir :-) |
AW: Best practice: Benutzerverwaltung mit Datenbankanbindung
Zitat:
Was heute noch wie ein Märchen klingt, kann morgen Wirklichkeit sein. :) |
AW: Best practice: Benutzerverwaltung mit Datenbankanbindung
Klar ginge das... Aber genauso könnte meine eigene Anwendung dieses UDP-Broadcast senden ;-)
Wie gesagt, eigentlich möchte ich auf eine Zwischenschicht verzichten, da ich Angst um die Performance habe. Im Prinzip besteht unsere Anwendung großteils aus Eingaben / Abrufung von Daten. Es ist also sehr Datenbankorientiert. (sehr viele Selectes und Inserts / Updates) Ich kenne die Vorteile von Multi-Tier-Anwendungen, halte sie für unsere Anforderungen aber nicht in allen Bereichen für sinnvoll. |
AW: Best practice: Benutzerverwaltung mit Datenbankanbindung
Zitat:
Für die maximale Performance kann die direkte Datenbankanbindung natürlich essentiell sein. Applicationserver erfordern ja auch einigen Aufwand, selbst wenn sie nur zustandslose Web Services zuverlässig (und zugleich einfach änderbar) bereitstellen sollen... |
AW: Best practice: Benutzerverwaltung mit Datenbankanbindung
Also es gibt User, Groups und Permissions (Rechte).
Die Permissions kann man den Usern aber auch den Gruppen zuteilen. Es besteht als einen N:M Beziehung zwischen User und Permissions als auch zwischen Groups und Permissions. Wenn ein User sich anmeldet, dann sammelt man alle seine Permissions in einer Liste. Der User kann Mitglied in mehreren Gruppen sein. Also erhält der User weitere Permissions aus diesen Gruppen. Eine Permission könnte z.B. sein:
Code:
Dann gibt es lokal in jedem Formular / Datenmodul eine Zuordnung von Permissions zu bestimmten Properties von Komponenten.
Permission_Edit_Global_Config (Benutzer darf globale Konfiguration ändern)
Das könnte z.B. so aussehen:
Delphi-Quellcode:
Es gibt hauptsächlich 3 Properties, die von Interesse sind: Enabled, Visible und ReadOnly.
PanelConfig.Enabled := SecurityManager.HasPermission('Permission_Edit_Global_Config');
QueryPreise.ReadOnly := not SecurityManager.HasPermission('Permission_Change_Prices'); MenuItemDebug.Visible := SecurityManager.HasPermission('Permission_Debug'); Mit Hilfe von RTTI kann man die Zuordnung zwischen Komponenten-Properties und Permissions zur Laufzeit herstellen. Du kannst es dir aber auch einfach machen und die ![]() |
AW: Best practice: Benutzerverwaltung mit Datenbankanbindung
Danke für deine Antwort.
Wie die Logik dahinter funktioniert war mir natürlich klar, das ist wie gesagt in C# umgesetzt. Mir ging es eher um den technischen Aspekt (DataLayer, Binding) und wie man es in Delphi richtig macht. Ich habe wie gesagt bisher immer Recordsetorientiert in Delphi programmiert. In .NET programmiere ich allerdings vollkommen Objektorientiert. Da hier aber oft von ORMs für Delphi gesprochen wird, wollte ich wissen, ob das wirklich praxistauglich ist, oder man doch lieber bei der klassischen Recordsetprogrammierung bleiben sollte... Wenn man seine Datenbankobjekte dann in Delphi-Klassen kapseln würde, wie bindet man diese dann sauber an die Oberfläche (z.B. an Listen oder Grids)? Bzw. wie trennt man hier die Oberfläche sauber von den Daten? OT: Es geht mir oft so, dass ich eine Frage stelle, aber leider nicht richtig verstanden werde... Wenn ich meine Fragen also etwas unverständlich schreibe, dann bitte ich das zu entschuldigen ;-) |
AW: Best practice: Benutzerverwaltung mit Datenbankanbindung
Hi Morphie,
dann schau Dir doch mal das D-ORM an: ![]() Scheinbar schon produktiv durch den Author selbst in einigen großen Projekten verwendet. Ich bin gerade selber am überlegen, ob ich es mal wagen soll das einzusetzen. Ansonsten versuche ich immer die DB-Struktur in Objekten abzubilden, habe mir ein kleines Tool geschrieben was anhand der DB-Struktur Quellcode generiert(Objekte + Listen(Objectlist/Generic-TObjectList)). Außerdem verwende ich die DevExpress-Komponenten, wo zur Darstellung meistens wunderschön ein cxGrid verwendet werden kann. Für dieses kann je nach Daten eine eigene Abteiltung von TcxCustomDataSource erstellt werden, die dann in der Gui wie jeder anderer DataSource dem Grid zugewiesen werden kann. Hoffe das hilft Dir weiter, Greetz Data |
AW: Best practice: Benutzerverwaltung mit Datenbankanbindung
Hi :-) Na das geht doch schon mal in die richtige Richtung ;-)
Ja, an sowas habe ich auch schon gedacht... Also einen Codegenerator zu schreiben, der mir meine Datenbankobjekte in Delphi-Objekte abbildet. Viel anders macht es das EntityFramework bei DatabaseFirst ja auch nicht... Es gibt dann noch einen DataContext, der das Abrufen der Daten übernimmt (z.B.
Delphi-Quellcode:
) und Änderungen verfolgt und diese bei
function GetCustomersByName(AName: String): List<TCustomer>;
Delphi-Quellcode:
wieder zurück in die Datenbank schreibt...
procedure SaveChanges;
Komplizierter wird es dann bei Relationen (Master / Details)... Ich kenne mich mit den DevExpress-Controls nicht so gut aus... Wie sieht so eine Implementierung einer TcxCustomDataSource-Ableitung aus? |
AW: Best practice: Benutzerverwaltung mit Datenbankanbindung
Hi Morphie,
eigentlich sieht so ein abgeleiteter DataSource recht simpel aus:
Delphi-Quellcode:
Unterm Strich musst Du "nur" definieren welche Spalten/Fields Dein CustomDataset bereitstellt und dafür dann GetValue & SetValue schreiben.
unit uMitarbeiterDS;
{$I cxVer.inc} interface uses Variants, Classes, cxCustomData, cxGridCustomTableView, uMitarbeiter; const IndexOfID = 0; IndexOfPersNr = 1; IndexOfNachName = 2; IndexOfVorname = 3; IndexOfUpdated_at = 4; IndexOfCreated_at = 5; type TDemoMitarbeiterDataSource = class(TcxCustomDataSource) private FMaList: TMitarbeiterList; FModified: boolean; protected function GetRecordCount: Integer; override; function GetValue(ARecordHandle: TcxDataRecordHandle; AItemHandle: TcxDataItemHandle): Variant; override; function AppendRecord: TcxDataRecordHandle; override; procedure DeleteRecord(ARecordHandle: TcxDataRecordHandle); override; function InsertRecord(ARecordHandle: TcxDataRecordHandle): TcxDataRecordHandle; override; procedure SetValue(ARecordHandle: TcxDataRecordHandle; AItemHandle: TcxDataItemHandle; const AValue: Variant); override; public constructor Create(AMaList: TMitarbeiterList); property Modified: boolean read FModified; property MitarbeiterList : TMitarbeiterList read FMaList; function GetDataObjectByHandle(ARecordHandle: TcxDataRecordHandle): TMitarbeiter; end; implementation { TCustomDataSource } constructor TDemoMitarbeiterDataSource.Create(AMaList: TMitarbeiterList); begin inherited Create; FMaList := AMaList; end; function TDemoMitarbeiterDataSource.AppendRecord: TcxDataRecordHandle; var aMa: TMitarbeiter; begin aMa := TMitarbeiter.Create(FMaList.Connection); Result := TcxDataRecordHandle(FMaList.Add(aMa)); DataChanged; if not Modified then FModified := True; end; procedure TDemoMitarbeiterDataSource.DeleteRecord(ARecordHandle: TcxDataRecordHandle); begin FMaList.Delete(Integer(ARecordHandle)); DataChanged; if not Modified then FModified := True; end; function TDemoMitarbeiterDataSource.GetDataObjectByHandle( ARecordHandle: TcxDataRecordHandle): TMitarbeiter; begin try Result := FMaList[Integer(ARecordHandle)]; except result := nil; end; end; function TDemoMitarbeiterDataSource.GetRecordCount: Integer; begin Result := FMaList.Count; end; function TDemoMitarbeiterDataSource.GetValue(ARecordHandle: TcxDataRecordHandle; AItemHandle: TcxDataItemHandle): Variant; var AColumnId: Integer; aMa: TMitarbeiter; begin aMa := FMaList[Integer(ARecordHandle)]; AColumnId := GetDefaultItemID(Integer(AItemHandle)); case AColumnId of IndexOfID : Result := aMa.ID; IndexOfPersNr : Result := aMa.PersNr; IndexOfNachName : Result := aMa.Nachname; IndexOfVorname : Result := aMa.Vorname; IndexOfUpdated_at : Result := aMa.Updated_at; IndexOfCreated_at : Result := aMa.Created_at; end; end; function TDemoMitarbeiterDataSource.InsertRecord( ARecordHandle: TcxDataRecordHandle): TcxDataRecordHandle; var aMa: TMitarbeiter; begin aMa := TMitarbeiter.Create; FMaList.Insert(Integer(ARecordHandle), aMa); Result := TcxDataRecordHandle(ARecordHandle); DataChanged; if not Modified then FModified := True; end; procedure TDemoMitarbeiterDataSource.SetValue(ARecordHandle: TcxDataRecordHandle; AItemHandle: TcxDataItemHandle; const AValue: Variant); var aMa: TMitarbeiter; AColumnId: Integer; begin AColumnId := GetDefaultItemID(Integer(AItemHandle)); aMa := FMaList[Integer(ARecordHandle)]; case AColumnId of IndexOfID : begin if VarIsNull(AValue) then aMa.ID := 0 else aMa.ID := AValue; end; IndexOfPersNr : aMa.PersNr := VarToStr(AValue) ; IndexOfNachName : aMa.Nachname := VarToStr(AValue) ; IndexOfVorname : aMa.Vorname := VarToStr(AValue) ; IndexOfUpdated_at : begin // Read-Only (* if VarIsNull(AValue) then aMa.Updated_at := 0 else aMa.Updated_at := VarToDateTime(AValue); *) end; IndexOfCreated_at : begin // Read-Only end; end; if not Modified then FModified := True; end; end. Greetz Data |
AW: Best practice: Benutzerverwaltung mit Datenbankanbindung
Zitat:
Neben dem Anbinden von Rechten an GUI Elemente wünsche ich mir ein Delphi eine Möglichkeit sie über Attribute an Methoden zu binden, so wie es das Apache Shiro Framework kann. Dann könnte man in beliebigen Anwendungsarten (Kommandozeile, Service, Webanwendung) unmittelbar auf Ebene der Geschäftslogik die Rechte prüfen, anstatt in der Visualisierungsschicht.
Delphi-Quellcode:
//Will throw an AuthorizationException if none
//of the caller’s roles imply the Account //'create' permission [RequiresPermissions(“account:create”)‏] procedure openAccount(acct: Account); begin //create the account end; |
Alle Zeitangaben in WEZ +1. Es ist jetzt 17:21 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz