Nach dem der ORMapper aus der Datenbank lesen und in die Datenbank schreiben kann, soll der Mapper die Datenbankstruktur auch erstellen und pflegen können. Die erste Idee ist ein Script, das aus den Klassen erzeugt wird. Ein Script zeigt aber beim Update seine Schwächen, wenn zwischen CREATE TABLE und ALTER TABLE unterschieden werden muss. Bei häufigen Updates steigt schnell der Aufwand für die Erhaltung der Daten. Die Lösung: Die Wartung wird von .net selbst erledigt. Die Tabellen werden mit den Klassen verglichen und bei Bedarf geändert. Der Vergleich und die Änderungen geschehen Spaltenweise, damit die Daten in den anderen Spalten unberührt bleiben. Für eine bessere Performanz werden nicht alle Klassen verglichen. Jedes Klasse hat eine Versionsnummer, die in der Datenbank abgelegt wird. Um ein Vergleich und damit ein Update anzustoßen muss die Versionsnummer in der Klasse erhöht werden. Um alle Tabellen zu vergleichen, kann man auch die Versionstabelle oder ihren Inhalt löschen.
Wenn Sie eine neue Datenbank erzeugen wollen, legen Sie einfach eine neue Datenbank an und führen das Update aus. Der Rest geht von alleine. Zunächst brauchen wir neue Attribute: Strings brauchen eine Länge, und das DataTableAttribut wird um die Versionsnummer erweitert. Klassen, die nicht verwaltet werden sollen, bekommen die Version 0.
Wie findet man alle Klassen in allen dll, selbst wenn diese noch nicht angezogen werden? Mit Assembly.GetEntryAssembly() erhält man die exe und mit
string loc = Assembly.GetEntryAssembly() |
Location den Pfad. In diesem kann man nach dlls suchen
FileInfo[] fis = di.GetFiles("*.dll"); foreach (FileInfo fi in fis) Assembly.LoadFile(fi.FullName) |
Dieses muß eventuell angepasst werden, z.B. für den Assemblycach. Klassen werden mit GetType aus den Assamblys gelesen und auf das DataTableAttribute überprüft. Die Tabellennamen und die Versionen können dann mit der Datenbank abgeglichen werden. Existiert die Versionstabelle nicht, wird sie angelegt. Ist ein Tabellenname nicht in der Versionstabelle, wird die Tabelle auf jeden Fall überprüft. Für alle Tabellen, die angepasst werden müssen, folgt nun eine Falluntescheidung: Mit
"SELECT COUNT(*) FROM dbo.sysobjects WHERE NAME LIKE '" + Name + "'" |
prüft man, ob die Tabelle schon existiert. Wenn nicht stellt man aus den Attributen der Klasse das CreateTable her. Wenn die Tabelle schon existiert löscht man erst den Primarykey mit
"ALTER TABLE [" + TableName + "] DROP CONSTRAINT [PK_" + TableName + "]" |
damit es bei den Änderungen nicht zu Komplikationen kommt. Um zu überprüfen, was an der Tabelle geändert werden muß, braucht man Informationen über den aktuellen Status der Tabelle. Das kann man aus den Systemtabellen lesen oder viel einfacher, aus dem Datareader: Einfach ein Select auf die Tabelle und die Informationen stehen zur Verfügung.
IDbCommand Command = Connection.CreateCommand(); Command.CommandText = "SELECT * FROM [" + TableName + "] WHERE 0=1"; reader = Command.ExecuteReader(); reader.Read(); DataTable dtSchemaTable = reader.GetSchemaTable(); |
Das sollte auch auf allen anderen Datenbanken funktionieren.
DataRow[] rows = dtSchemaTable.Select("ColumnName LIKE '" + ColumnName + "'"); |
Existiert die Spalte, findet man in den Feldern DataType und ColumnSize die Informationen, um die Spalte zu überprüfen und eventuell mit
ALTER TABLE [" + TableName + "] ALTER COLUMN [" + ColumnName + "] " + GetColumnDef(Info); |
zu ändern. Findet man die Spalte nicht, muß sie mit
ALTER TABLE [" + TableName + "] ADD [" + ColumnName + "] " + GetColumnDef(Info); |
angelegt werden. In beiden Fällen ist ein Datenbank abhängiges Mappen der Membertypen zu den Spaltentypen nötig.
int |
INT |
bool |
BIT |
DateTime |
DATETIME |
double |
FLOAT |
string |
VARCHAR |
decimal |
MONEY |
Guid |
UNIQUEIDENTIFIER |
Bei VARCHAR benötigt man noch die Länge und der Primarykey wird am besten mit NOT NULL definiert. Bei der neuen oder geänderten Tabelle wird am Schluss der Primarykey wieder hinzugefügt
"ALTER TABLE [" + TableName + "] ADD CONSTRAINT [PK_" + TableName + "] PRIMARY KEY (" + Columns.ToString() + ")" |
und die neue Versionsnummer in die Versionstabelle geschrieben.
Der Einsatz ist ganz einfach: Erweitern Sie allen Klassen, die Sie verwalten möchten, um eine Versionsnummer und alle String Membervariablen um ein ColumnLengthAttribute.
Erzeugen Sie eine Instanz von DBManagerSqlServer und überprüfen sie mit GetChangedObjects ob sich was geändert hat. Rufen Sie gegebenenfalls Update auf und die Datenbank ist auf den neusten Stand. Sie können das beim Start Ihrer Anwendung durchführen oder eine separate Anwendung dafür schreiben. Bei einer produktiven Datenbank sollten Sie die Daten auf jeden Fall vorher sichern.
Den ganzen ORMapper und ein Beispiel finden Sie wie immer unter http://www.KlaasWedemeyer.de.
Klaas Wedemeyer