Entwickler-Ecke

C# - Die Sprache - OR-Mapper


maxx - Di 20.04.10 10:04
Titel: OR-Mapper
hallo,

ich habe einen OR-Mapper entworfen. Mich würde interessieren, ob der OK ist. Mir kommt der nämlich unglaublich kompliziert vor. Ich bin sicher, den kann man noch sehr vereinfachen. Ich weiß aber nicht, wie.?.?

Für jede Datenbank-Tabelle gibt es 2 Klassen. Gibt es in der Datenbank eine Tabelle A, gibt es im Programm die Klassen A und DbA. In A stehen die allgemeinen Eigenschaften, in DbA kommen noch Datenbank-spezifische Eigenschaften hinzu (z. B. eine Datenbank-ID).

Bei meinem Testbeispiel nehme ich an, dass ich in der Datenbank 2 Tabellen habe: A und B. A hat einerseits eine 1:n-Beziehung zu B (in C# als BindingList dargestellt), andererseits eine 1:1-Beziehung zu B. Ist zwar ziemlich unsinnig, aber das dient nur dazu, das Programm zu testen.


C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
125:
126:
127:
128:
129:
130:
131:
132:
133:
134:
135:
136:
137:
138:
139:
140:
141:
142:
143:
144:
145:
146:
147:
148:
149:
150:
151:
152:
153:
154:
155:
156:
157:
158:
using System;
using System.ComponentModel;
using System.Data;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
   public partial class Form1 : Form
   {
      public Form1()
      {
         InitializeComponent();

         // Hier im Hauptprogramm gibt es nur 3 Testfälle.

         A a = new A();
         a.s = "a1";

         // Test 1 (B-Collection in A):

         /*B b0 = new B();
         b0.s = "b0";
         B b1 = new B();
         b1.s = "b1";
         BindingList<B> bB = new BindingList<B>();
         bB.Add(b0);
         bB.Add(b1);
         a.cB = bB;

         B b = new B();
         b.s = "b";
         a.b = b;*/


         // Test 2 (DbB-Collection in A):

         /*DbB dbB0 = new DbB();
         dbB0.s = "b0";
         DbB dbB1 = new DbB();
         dbB1.s = "b1";
         BindingList<DbB> cDbB = new BindingList<DbB>();
         cDbB.Add(dbB0);
         cDbB.Add(dbB1);
         a.cB = cDbB;

         DbB dbB = new DbB();
         dbB.s = "b";
         a.b = dbB;*/


         // Anzeigen für Test 1 und 2:

         /*foreach (B _b in a.cB)
            MessageBox.Show(_b.s);
         MessageBox.Show(a.b.s);

         DbA _dbA = new DbA(a);
         foreach (DbB _dbB in _dbA.cB)
            MessageBox.Show(_dbB.s);
         MessageBox.Show(_dbA.b.s);

         A _a = (A)dbA;
         foreach (B _b in _a.cB)
            MessageBox.Show(_b.s);
         MessageBox.Show(_a.b.s);*/


         // Test 3 (B-Collection in DbA):

         DbA dbA = new DbA();
         dbA.s = "a1";

         B b0 = new B();
         b0.s = "b0";
         B b1 = new B();
         b1.s = "b1";
         BindingList<B> cB = new BindingList<B>();
         cB.Add(b0);
         cB.Add(b1);
         dbA.cB = cB;

         B b = new B();
         b.s = "b";
         dbA.b = b;

         // Anzeigen für Test 3:

         foreach (B _b in dbA.cB)
            MessageBox.Show(_b.s);
         MessageBox.Show(dbA.b.s);

         A _a = (A)dbA; // von DbA nach A casten
         foreach (B _b in _a.cB)
            MessageBox.Show(_b.s);
         MessageBox.Show(_a.b.s);

         DbA _dbA = new DbA(_a); // von A nach DbA casten
         foreach (DbB _dbB in _dbA.cB)
            MessageBox.Show(_dbB.s);
         MessageBox.Show(_dbA.b.s);
      }
   }
   // ==============================================================================
   // Für Datenbank-Tabelle A:
   public interface IA
   {
      string s { get; set; } // String
      IBindingList cB { get; set; } // Collection (in Datenbank 1:n-Beziehung)
      IB b { get; set; } // Objekt (in Datenbank 1:1-Beziehung)
   }
   public class A : IA
   {
      public string s { get; set; }
      public IBindingList cB { get; set; }
      public IB b { get; set; }
      public A()
      {
         this.cB = new BindingList<B>();
      }
   }
   public class DbA : A // die Datenbank-Klasse für A
   {
      public int i { get; set; } // entspricht einer Datenbank-ID
      public DbA()
      {
      }
      public DbA(A a)
         : this()
      {
         this.s = a.s;
         foreach (IB b in a.cB)
            this.cB.Add(new DbB(b));
         this.b = a.b;
      }
      // Hier noch Methoden, um Daten in Datenbank zu schreiben oder aus ihr zu lesen.
   }
   // ==============================================================================
   // Für Datenbank-Tabelle B:
   public interface IB
   {
      string s { get; set; }
   }
   public class B : IB
   {
      public string s { get; set; }
      public B()
      {
      }
   }
   public class DbB : B // die Datenbank-Klasse für B
   {
      public int i { get; set; } // entspricht einer Datenbank-ID
      public DbB()
      {
      }
      public DbB(IB b):this()
      {
         this.s=b.s;
      }
      // Hier noch Methoden, um Daten in Datenbank zu schreiben oder aus ihr zu lesen.
   }
}


Was mich an dem Programm irgendwie ganz besonders stört ist, dass ich viel mit Interfaces arbeite. Dadurch geht mir die Typsicherheit verloren.


Kha - Di 20.04.10 10:21

user profile iconmaxx hat folgendes geschrieben Zum zitierten Posting springen:
ich habe einen OR-Mapper entworfen.
Wohl eher einen Data Access Layer [http://en.wikipedia.org/wiki/Data_access_layer] für deine zwei Tabellen ;) . Für einen O/RM müsstest du das auf beliebige Entities und Tabellen verallgemeinern.

user profile iconmaxx hat folgendes geschrieben Zum zitierten Posting springen:
Was mich an dem Programm irgendwie ganz besonders stört ist, dass ich viel mit Interfaces arbeite. Dadurch geht mir die Typsicherheit verloren.
Warum trennst du dann überhaupt jede Entity in Interface-Klasse-Klasse :nixweiss: ? Hört sich nach Overengineering an, mach jeweils eine Klasse daraus.


maxx - Di 20.04.10 10:36

Zitat:
Warum trennst du dann überhaupt jede Entity in Interface-Klasse-Klasse. ? Hört sich nach Overengineering an, mach jeweils eine Klasse daraus.

Genau das will ich nicht. Es soll eine allgemeine Klasse geben, die universell einsetzbar ist. Diese Klasse soll man dann in unterschiedlichste Formen casten können. Z. B. in eine entsprechende Datenbank-Klasse.

Hier gehts mir auch um eine saubere Trennung. Zuerst überlegt man sich mal, welche Klassen es in einem Programm gibt (in meinem Beispiel die Klassen A und B). Danach kümmert man sich um die Datenbankanbindung und entwirft noch die zugehörigen Klassen DbA und DbB.

Man könnte die Klassen A und B dann auch in beliebige andere Formen casten, sofern man das dann irgendwann mal brauchen sollte. Vielleicht will ich irgendwann mal mit den Klassen A und B ein UFO steuern ;). Dann kann ich mir hierzu noch die Klassen UfoA und UfoB erzeugen und dann - wenn ich das will - z. B. von A nach UfoA casten.


Kha - Di 20.04.10 15:53

Sag ich doch, eindeutig Overengineering :mrgreen: .
Im Ernst, mir ist so etwas noch nicht begegnet und ich sehe bisher auch noch keinen praktischen Nutzen darin.


maxx - Di 20.04.10 20:35

Das ist kein overengineering. Ich kenne einen OR-Mapper, der das auch so trennt. Nämlich "Propel".


Kha - Di 20.04.10 22:04

Meinst du die om/FooTableMap-Klassen? Das ist doch nur die zwischengespeicherte XML-Datei und leitet vor allem nicht von FooBase ab.
Ich kenne eigentlich nur die folgenden zwei Ansätze: