Laden...

Problem mit Relation

Erstellt von DeveloperX vor 18 Jahren Letzter Beitrag vor 18 Jahren 1.578 Views
D
DeveloperX Themenstarter:in
462 Beiträge seit 2005
vor 18 Jahren
Problem mit Relation

Hallo zusammen!

Also ich frage mich gerade, ob ich folgendes Problem mit Relationen in der Datenbank oder nur im Programm lösen sollte. Die Situation ist folgende:

Ich habe Items und Folder in einem Baum organisiert. Folder programmtechnisch gesehen von Item abgeleitet und hat nur die Funktion GetChilds() hinzubekommen. Also keine zusätzlichen Daten.
Von den Daten her, unterscheiden sie sich also nicht (Anmerkung: Die Item-spezifischen Daten habe ich aufgrund der Fähigkeit meines Programmes, benutzerdefinierte Felder hinzuzufügen, datenbank-technisch nicht in der selben Tabelle wie Item bzw. Folder).

Jetzt kommt der springende Punkt:
Vater kann ja nur ein Folder sein und kein Item. Wenn ich nun eine Beziehung auf die gleiche Tabelle mache, könnte es ja unter Umständen vorkommen, dass plötzlich ein Item ein Vater wird.

Jetzt sind mir 2 Lösungswege eingefallen:

  1. Eine zusätzliche Tabelle wo nur die Folder-IDs drinnen stehen und auf diese Tabelle wird die Vater-Relation angesetzt.

  2. Die Lösung mit der Relation auf die gleiche Tabelle und darauf hoffen, dass nie der Fall eintritt, dass ein Item ein Vater ist.

Welche Lösung wäre hier zu empfehlen? Und vor allem warum? Oder gibt es andere Ansätz?

Ist zwar jetzt keine so weltbewegende Frage, aber ich möchte, dass bei meinem ersten größteren Programm alles sauber und korrekt gelöst ist!

mfg
DeveloperX

3.728 Beiträge seit 2005
vor 18 Jahren
Datenbank und OOP

Hallo,

Entweder Du modellierst Deine Datenbank und schreibst Dir anschließend Datenzugriffsfunktionen, oder Du modellierst ein Objektmodell und persistierst dessen Status (Objekt-Relationales Mapping). Du modellierst gerade beides gleichzeitig. Das wird nicht zu einer guten Lösung führen.

Von OR-Mappern bin ich nicht überzeugt (das muss nicht heißen, dass die schlecht sind!), deshalb möchte ich Dir gerne meine Architektur nahe bringen.

Beim Aufbau einer relationalen Datenbank bestimmt man zunächst die Entitäten. Eine Entität ist etwas aus der realen Welt, welches in der Datenbank "abgebildet" werden soll. In Deinem Fall erkenne ich die folgenden Entitäten:
*Folder *Item

In den meisten Fällen wird jede Entität durch eine Tabelle abgebildet (es könnten theoretisch auch mehrere sein). Da ein "Folder" etwas anderes ist als ein "Item" würde ich beide niemals in der selben Tabelle unterbringen (Auch wenn beide sehr ähnliche Eigenschaften haben).
Die Beziehung zwischen Entität Folder und Entität Item liegt klar auf der Hand. Es muss eine 1:n Beziehung sein (1 Folder enthält n Items). Damit wäre der grundlegende Entwurf der Datenbank geklärt.

Da ich nicht weiß, was das genau für "Items" und "Folders" sind, gehe ich davon aus, dass Du Dateien in einer Datenbank erfassen möchtest (sowas wie ein Dokumenten Management System vielleicht).

Datenbankzugriffe sind mit ADO.NET zwar einfach zu programmieren, benötigen aber ziemlich viel Code (Man muss eine Connection öffnen, ein Command erstellen, usw.). Deshalb solltest Du Dir eine Datenzugriffskomponente bauen, die Dir einige dieser Routinearbeiten abnimmt. Das brauchst Du nur einmal zu tun! Alle Deine künftigen Projekte können die selbe Komponente immer wieder verwenden. Deshalb sollte die Datenzugriffskomponente in einer separaten Assembly (DLL) untergebracht werden.

Für jede Entität solltest Du eine Klasse erstellen, die Methoden zum Zugriff und zur Manipulation dieser Entität bereit stellt. Diese Entitätsklassen verwenden die Datenzugriffskomponente, um einfach und schnell auf die Datenbank zuzugreifen. Hier ein Beispiel wie eine solche Klassen für die Entitäten "Folder" und "Item" aussehen könnten:


public class FolderEntity : MarshalByRefObject
{
    public DataTable GetFolder(Guid folderID)
    { ... }

    public DataTable GetFolder(string name)
    { ... }

    public DataTable GetParentFolders(Guid folderID)
    { ... }

    public DataTable GetChildFolders(Guid folderID)
    { ... }

    public DataTable GetAllFolders()
    { ... }

    public DataTable GetEmptyTable()
    { ... }

    public DataTable Save(DataTable changedRecords)
    { ... }
}

public class ItemEntity : MarshalByRefObject
{
    public DataTable GetItem(Guid itemID)
    { ... }

    public DataTable GetItem(string name)
    { ... }

    public DataTable GetAllItems()
    { ... }

    public DataTable GetItemsByFolderID(Guid folderID)
    { ... }

    public DataTable Save(DataTable changedRecords)
    { ... }
}

Bei dieser Architektur ist es wichtig, dass die Klasse einer Entität nicht auf Tabellen anderer Entitäten zugreift. Damit wären die Klassen nicht mehr unabhängig und alles würde zu einem großen schwer zu pflegendem Klumpen verkommen.

Natürlich werden auch Funktionen benötigt, die mit Daten aus beiden Entitäten gleichzeitig arbeiten. Solche Funktionen gehören in separate Klassen. Nennen wir die mal Service-Klassen. Diese Klassen sind es, die einen bestimmten Dienst anbieten. Eine Service-Klasse kann beliebig viele Entitätsklassen verwenden. Außerdem kann sie selbst LESEND auf die Datenbank zugreifen (um z.B. SELECT-Anweisungen mit JOINs auszuführen). Nur schreiben (INSERT, UPDATE, DELETE) darf eine Service-Klasse nicht selbst in die Datenbank. Dazu muss sie immer die jeweilige Entitätsklasse verwenden. Wenn Änderungen nur über die Entitätsklassen gespeichert werden können, greifen Regeln, die beim speichern implementiert werden müssen, auf jeden Fall (z.B. schreiben einer Historie).
Clients (z.B. Windows-Forms Oberflächen) kommunizieren nur mit diesen Service-Klassen, aber nicht direkt mit den Entitätsklassen. Warum? Wenn Du Dich entschließen solltest, Deine Anwendung auf verschiedene Rechner zu verteilen (z.B. mittels .NET Remoting) brauchen die Clients nur eine Schnittstelle zu kennen (nämlich die der Service-Klasse). Überhaupt sollten die Verweise zwischen den Assemblies kein Spinnennetz ergeben (Sowas ist schwer zu pflegen).

So könnte eine Service-Klasse aussehen, die einem Client ermöglicht, mit Folders und Items zu arbeiten:


public class ContentService : MarshalByRefObject
{
    public Guid CreateFolder(string folderName)
    { ... }

    public Guid CreateFolder(string folderName, Guid parentFolderID)
    { ... }

    public void DeleteFolder(Guid folderID)
    { ... }

    public void MoveFolder(Guid folderID,Guid destParentFolderID)
    { ... }

    public DataTable GetFolder(Guid folderID)
    { ... }

    public DataTable GetFolder(string name)
    { ... }

    public DataTable GetParentFolders(Guid folderID)
    { ... }

    public DataTable GetChildFolders(Guid folderID)
    { ... }

    public Guid CreateItem(string name,Guid parentFolderID)
    { ... }

    public void DeleteItem(Guid itemID)
    { ... }

    public void MoveItem(Guid itemID,Guid destParentFolderID)
    { ... }

    public DataTable GetItem(Guid itemID)
    { ... }

    public DataTable GetItem(string name)
    { ... }

    public DataTable GetItemsInFolder(Guid folderID)
    { ... }
}

Ein Client kann eine Instanz der Service-Klasse benutzen, um Folders und Items zu erstellen, löschen, verschieben und zu lesen.

Diese Architektur hat sich in vielen Projekten bewährt.

D
DeveloperX Themenstarter:in
462 Beiträge seit 2005
vor 18 Jahren

Hallo Rainbird!

Vielen Dank für die ausführlichen Informationen. Es ging mir lediglich um die Frage, ob ich Folder und Item (sie haben tatsächlich nur die Eigenschaft, dass sie einen Vater-Folder haben können, nicht mehr. Der Rest wird in zusätzlichen Tabellen gespeichert. Ich brauche diese eine bzw. ev. zwei Tabellen lediglich aus dem Grund der Baum-Struktur) aufteilen soll oder nicht.

Der Rest des Postings ist mir bekannt und bereits mit anderen Klassen, die nicht via Baum strukturiert sind, implementiert (ich verwende keine O/R-Mapper und mache fast alles so, wie in deinem Posting beschrieben, nur ohne DataTable als Rückgabe-Wert sondern Structs mit den jeweiligen Datentypen).

Aber wie ist das nun, wenn ich lediglich die Struktur ohne zusätzliche Attribute wie Knoten-Name etc. speichern will? Du hast ja deine Lösung ja damit begründet, dass sie nicht gleich sondern ähnlich sind. Sie sind jedoch gleich (zumindest auf der Abstraktion-Stufe, auf der die Baum-Strukturierung erfolgt).

Im BusinssLayer ist Item eine abstrakte Klasse, und eine Ausprägung davon ist eben Folder. Bei mir erben viele Klassen von Item, weil ich für alle die gleichen Attribute brauche (Zuletzt-geändert, erstellt-am, besitzer, itemid und itemtype der abgibt ob welche ausprägung von item es ist). Mögliche Ausprägungen sind z.B. Folder, Kontakt, Termin ...

Vielen Dank nochmals für deinen Beitrag!!

3.728 Beiträge seit 2005
vor 18 Jahren
Extra Tebelle

In dem Fall würde ich die Struktur in einer separaten Tabelle speichern (Diese Tabelle würde ParentID, ParentType und ChildID, ChildType enthalten. Das hat den Vorteil dass man jederzeit einfach weitere Typen zur Struktur hinzufügen kann.