Datenbank Projekte in Visual Studio 2005 (auch 2003) Jan A. Staecker, bei .Net Developer-Group Ulm Jan A. Staecker Studium Physik an TU München 5 Jahre bei M.A.X. GmbH in München (Microsoft Solution Provider) Seit 1998 freiberuflich mit Projekten große (Branchenlösung), mittlere (Mitarbeit) und kleine (etwa vba) ◦ .NET und SQL Server ◦ Access Frontend / Excel Ziele Synchronisierung der Entwicklung von SQL Server Datenbankobjekten. Etwa eine Development, ein Test und ein Live System. Versionsvergleich von Datenbankobjekten per Visual Source Safe. Manuelles Einstellen in unterschiedliche Systeme führt oft zu Problemen, wenn man doch mal ein Statement vergisst. DBObjekt enthält mehr als nur TSQL Was ist ein Datenbankprojekt ? Eine Menge von, in Verzeichnissen strukturierbaren Dateien, die jeweils T-SQL Code enthalten. Zuordnung zu SQL Server Datenbank, wobei eine immer aktuell verwendet wird. Was kann man damit machen? Den T-SQL Teil einer Datei gegen den aktuellen Server ausführen. Markierten Teil einer Datei ausführen Alle markierten Dateien ausführen (Reihenfolge ist alphabetisch) Vorteil: Versionierung mit Source Safe Was kann in einer Datei stehen? Meistens wohl DDL Code: SQL zerfällt ja in ◦ DQL ◦ DML ◦ DDL ◦ DCL Data Query Language – SELECT Date Manipulation Language INSERT, UPDATE, DELETE Data Definition Language CREATE, ALTER, DROP Data Control Language GRANT, DENY, REVOKE ANLEGEN EINES DATENBANKPROJEKTES IN VISUAL STUDIO Wahl eines Verweises. Hier etwa wähle ich einen Verweis aus, den ich aus dem C# oder VB Projekt her angelegt habe. Datenbank Projektmappe Name war: TSQL Idee Es gibt schon: Vorgabeverzeichnisse Datenbank Referenzen Das Schloss symbolisiert den Zustand „eingecheckt“ in Visual Source Safe. Beispiel für Arbeit mit VSS Anlegen einer Tabelle mit Tabellenskript Etwa 2 Felder, einchecken Weiter Editieren und Vergleichen Datenbankabfrage Daten aus dem Nichts, per dbo.sysobjects Erstellen mit Hilfe der Datenbankabfrage (Zu erkennen an der Endung .dtq) Ändern in eine Erstellungsabfrage Einchecken und Ändern Nachteil: Man kann diese nicht mehr vergleichen Beheben durch Skripten Wir fügen stattdessen einfach eine normale Skriptdatei (.sql) hinzu. In dieser können wir einfaches DQL als auch DML entwerfen. Wir fügen nochmal dbo.sysobjects hinzu. Und vergleichen: BEISPIEL: IDEEN DATENBANK Wir sammeln einerseits Ideen und bewerten diese. Im Kern werden unterschiedliche Rollen/Teilnehmer die Idee absegnen. Was machen wir nicht? Rollen werden von Personen und Stellvertretern repräsentiert. Man könnte dieses Beispiel als Workflow abbilden. Erstellen einer Tabelle für Ideen Wir erzeugen eine Tabelle, etwa für Ideen. Eine Idee kann erstellt, angenommen und realisiert sein. Jeder Schritt bekommt einen Zeitstempel, also ein Feld in dem Datensatz. In der Realität werden Ideen auch verworfen oder nicht realisiert oder getestet. Beispiel Anlage des Skripts Wir erstellen die Tabelle per T-SQL auswendig oder nehmen bei SQL 2000 den „Enterprise Manager“ oder bei SQL 2005 das „SQL Management Studio“ zur Hilfe. Wir erstellen ein Skript für das Neuanlegen (Create) als auch für Delete, falls wir die Definition verändern wollen. Dies ergibt schon unser erstes Skript. Erstellen 2. Tabelle „Unterschriften“ Eine Zweite Tabelle enthält alle „Unterzeichner“ in verschiedenen Rollen zu der Idee, also ggf. Projektleiter, Firmenchefs, evtl. auch Stellvertreter. Insbesondere natürlich eine Bemerkung und ein Datum, an dem die Idee unterzeichnet wurde. Liegen komplett alle Unterzeichnungen vor, so gilt die Idee als angenommen. Wir nehmen an, das es einen Prozess gibt, der vorher die Unterzeichner festlegt. Eine weitere Unterschrift der oder des Realisierer(s) kennzeichnet die Idee als realisiert. Anlage des Skripts /****** Objekt: Table [dbo].[Unterschrift] Skriptdatum: 07/16/2007 22:19:27 ******/ IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[Unterschrift]') AND type in (N'U')) DROP TABLE [dbo].[Unterschrift] GO CREATE TABLE [dbo].[Unterschrift]( [UnterschriftID] [int] IDENTITY(1,1) NOT NULL, [IdeeID] [int] NOT NULL, [Signierer] [int] NOT NULL, [Bemerkung] [nvarchar](max) COLLATE Latin1_General_CI_AS NOT NULL, [Datum] [datetime] NULL, CONSTRAINT [PK_Unterschrift] PRIMARY KEY CLUSTERED ( [UnterschriftID] ASC )WITH (IGNORE_DUP_KEY = OFF) ON [PRIMARY] ) ON [PRIMARY] Anlage der Relation Eine Relation ist ein wichtiges Element in der Datenbank Dieses gewährleistet, dass es für jede Unterschrift immer eine Idee gibt. Die 1:n Relation erlaubt mehrere Unterschriften zu einer Idee zu verwenden. Es gibt keine 2 Ideen mit dem gleichen Schlüssel. Erstellen des Skripts Entweder als neue Datei oder aber in einer der vorhandenen Dateien. Will man die Skripte in einer bestimmten Reihenfolge aufrufen, so sollte man diese auch entsprechend bezeichnen. Vorteil einer weiteren Datei liegt darin, dass man erst alle Tabellen in beliebiger Reihenfolge erstellen kann und danach die die Relationen (Dennoch muss man die Relationen ggf. wieder vorher entfernen) Nachteil einer weiteren Datei ist ein zusätzliches Element in der Projekthierarchie. Automatisches Änderungsskript Relation entfernen IF EXISTS (SELECT * FROM sys.foreign_keys WHERE object_id = OBJECT_ID(N'[dbo].[FK_Unterschrift_Idee]') AND parent_object_id = OBJECT_ID(N'[dbo].[Unterschrift]')) ◦ ALTER TABLE [dbo].[Unterschrift] DROP CONSTRAINT [FK_Unterschrift_Idee] GO Will man alles in einem Rutsch laufen lassen, muss man dieses Skript zuerst ausführen. Danach die beiden Tabellen Zum Schluss die Relation setzen. Beispiel • Aufruf aller 4 Skripte am Stück. Wir werden durch fehlerlose Ausführung belohnt. Ausführen auf Live Datenbank Keiner sagt LiveDatenbank, sondern verwendet den Namen des Produkts. Anwenden auf eine andere Datenbank. Wir haben bisher entwickelt und vielleicht auch getestet. Nun verteilen wir das Design Skript auf die Livedatenbank. Test durch Datensätze Wir fügen ein Stück SQL ein, welches uns Datensätze generiert. Dazu benötigen wir den Index der gerade angelegten Idee. Auf dem SQL Server (meist) kein Problem, da wir per @@Identity den Zugriff auf die letzte angelegte ID bekommen. Packen in eine Transaktion Läuft dies? Nein, denn wir haben vergessen, dass die Bemerkung keinen Standardwert besitzt oder Null sein darf. Wie ändern wir dies auf Dev System und Live-Server ? Etwa per neuem Skript oder Änderung unserer Design-Skripte. Weiterer Test Abzeichnen (Diesmal mit festen ID-Werten) Teil 2: Alle Zeichnen ab UPDATE SET WHERE dbo.Unterschrift Bemerkung = N'ich bin dabei', Datum = GETDATE() (UnterschriftID = @UnterschriftID) UPDATE dbo.Unterschrift SET Bemerkung = N'ich bin auch dabei', Datum = GETDATE() WHERE (UnterschriftID = 200) SELECT FROM UnterschriftID, IdeeID, Signierer, Bemerkung, Datum Unterschrift Was ist nun? Wie zeichnen wir unsere Idee ab? Wir müssen den Datensatz der Idee ändern! Kennzeichnen als Abgezeichnet Die einfachste Idee ist sicher, dies manuell zu setzen. Was machen wir aber, wenn es unterschiedliche Frontends zu diesem Prozess gibt oder auch ein administratives Frontend existiert. Vielleicht auch mal einen Datensatz von Hand setzen und dennoch alles stimmig sein soll. Eventuell werden (später in einer Prozessänderung) ja auch Unterschriften zurückgezogen, während oder nachdem wir eigentlich alles schon als Abgezeichnet erachtet haben. Lösung durch Trigger Damit die Datenbank immer einen gültigen Status besitzt schreiben wir einen Trigger, der alle unterzeichneten Datensätze durchgeht und das Datum setzt. Hierfür wäre eine Funktion nett, die uns für jeden beim Aktualisieren gesetzte Unterschrift den Status der Idee angibt. Funktion dbo.isAngenommen() Funktionen spricht man immer mit dem Namensraum an, hier also samt „dbo“. Test der Funktion Entspricht die Funktion unserer Aufgabe? Print dbo.isAngenommen(@IdeeID) UPDATE dbo.Unterschrift SET Bemerkung = N'ich bin dabei', Datum = GETDATE() WHERE (UnterschriftID = @UnterschriftID) Print dbo.isAngenommen(@IdeeID) UPDATE dbo.Unterschrift SET Bemerkung = N'ich bin auch dabei', Datum = GETDATE() WHERE (UnterschriftID = 200) Print dbo.isAngenommen(@IdeeID) Schreiben des Triggers Tipp: erst mal so tun, als ob wir eine Tabelle Unterschrift (mit Alias) hätten, die die geänderten Datensätze enthält. Zuletzt die Tabelle in „Inserted“ umbenennen. Code des Triggers CREATE Trigger dbo.UpdateIdeeAbgezeichnet ON Unterschrift FOR UPDATE as UPDATE Idee SET Angenommen = null FROM Inserted AS Unterschrift_1 INNER JOIN Idee ON Unterschrift_1.IdeeID = Idee.IdeeID WHERE (dbo.isAngenommen(Idee.IdeeID) = 0) UPDATE SET FROM WHERE Idee Angenommen = GETDATE() Inserted AS Unterschrift_1 INNER JOIN Idee ON Unterschrift_1.IdeeID = Idee.IdeeID (dbo.isAngenommen(Idee.IdeeID) <> 0) Test des Triggers SELECT FROM UnterschriftID, IdeeID, Signierer, Bemerkung, Datum Unterschrift SELECT IdeeID, Kurz, Beschreibung, Erstellt, Angenommen, Umgesetzt FROM Idee UPDATE SET NULL WHERE dbo.Unterschrift Bemerkung = N'bin wieder unschlüssig', Datum = (UnterschriftID = 200) SELECT IdeeID, Kurz, Beschreibung, Erstellt, Angenommen, Umgesetzt FROM Idee Warum nicht als berechnetes Feld? Das ist auch eine schöne Möglichkeit. Allerdings können berechnete Felder deutlich längere Laufzeiten nach sich ziehen. Berechnete Felder lassen sich nicht indizieren, somit sind Abfragen auf „Angenommen is Null“ nicht besonders performant. Beachte auch Auswirkungen in Frontends DATENBANKPROJEKT IM SQL SERVER MANAGEMENT STUDIO Dies ist sicher auch ein gangbarer Weg! Allerdings finde ich es von Visual Studio aus besser gelöst Auf den folgenden Folien werde ich zwischen VS (Visual Studio) und MS (Management Studio) Projekten unterscheiden. Verbindungen im MS-Projekt Je SQL Server und User kann man nur eine Verbindung erstellen. Also nicht per Datenbank wie im VS Projekt. Tipp: außer man nutzt die XML-Projektdatei. Dann wird aber nicht der zugewiesene Name, sondern der SQL Server angezeigt. Beide Verbindungen zeigen hier auf unterschiedliche Datenbanken Variable Ausführung der Abfragen Abfragen (TSQL Skripte) werden nun einer Verbindung fest zugeordnet. Ein Wechsel ist im UI des MS nicht möglich. Abfragen kann man nicht gruppieren, also in Unterverzeichnisse werfen. Mehrere Skriptdateien sind nicht zusammen ausführbar. Problem: Etwa 0 von 20 Skripten, die ich im VS in einem Verzeichnis unterbringe, gegen die Datenbank B statt A auf dem gleichen Server laufen zu lassen ist nicht so möglich. Vorteile im MS Abfragen lassen sich sofort analysieren und somit besser bewerten. XML Projektdatei (des MS) Mit etwas Manipulation der XML-Datei kann man die Ablage in unterschiedliche Verzeichnisse erzwingen. Deren Namen werden aber immer wieder zum Standardnamen Abfragen zurückgeändert. Resümee Wenn man kein Visual Studio beim Kunden auf dem Entwicklungsrechner hat, ist das Projekt im Management Studio sicher eine überlegenswerte Alternative. Ggf. verwendet man eben mehrere Projekte für unterschiedliche Aufgaben Bei gezielter Optimierung von Abfragen kann ich mir vorstellen, mit dem Management Studio die besseren Karten hat, da hier die Anbindung bereits vorliegt. Für komplette Datenbankprojekte hingegen, mit der Teilung in Prod/Test und Dev-System, würde ich lieber unter Visual Studio durchführen. UMSCHLIEßEN DES DB OBJEKTS Im Datenbankprojekt kann man zu jedem Datenbankobjekt weiteren Code generieren: Etwa zum Registrieren des Objektes manchmal auch um eine vorherige Version zu löschen (Bevorzugt ALTER verwenden) Weiteren Code, der zum Objekt passt. Beispiel: Fehler Protokoll Wir erzeugen je eine Tabelle für unsere Stored Procedures und eine Tabelle für die auftretenden Fehler. Da wir viele Stored Procedures nutzen wollen, sollen diese gleich automatisch registriert werden. Jede SP liefert im Fehlerfall den Fehler mit einer eigenen ID. Anlegen der Stored Procedure 1/3 ALTER PROCEDURE [dbo].[myDevide] @a int, @b int AS BEGIN DECLARE @RetVal int BEGIN TRY SET @RetVal = @a/@b END TRY Abfangen möglicher Fehler 2/3 BEGIN CATCH INSERT INTO dbo.SP_Errors (SP_UID, SPErr_Text,SPErr_Location) values ('{FD04AE68-5F64-4903-B08E-3CB626E74AF6}', ERROR_MESSAGE(), ERROR_LINE()) END CATCH END Go Registrierung und Testcode 3/3 if not exists(SELECT SP_UID FROM dbo.StoredProcedures WHERE SP_UID = '{FD04AE68-5F64-4903-B08E-3CB626E74AF6}') BEGIN INSERT INTO dbo.StoredProcedures (SP_UID, SP_Name,SP_Bemerkung) values ('{FD04AE68-5F64-4903-B08E-3CB626E74AF6}‚ ,'dbo.myDevide', 'Teilung zweier Zahlen') -- TestCode EXECUTE dbo.[myDevide] 0,0 DECLARE @RETVAL int execute @RetVAl = dbo.[mydevide] 1,2 print @RetVAl END Was ist nun das Besondere? Pur auf dem SQL Server hätten wir das nicht so schön machen können. Neue SPs werden nicht automatisch angemeldet! In der Praxis wird man dies noch ausformulieren und in eigene Stored Procedures stecken. Faszinierend ist es nach ½ Jahr im stabilen Betrieb sich die tatsächlichen Fehler anzusehen und zu analysieren. VERSCHLÜSSELN DER DATENBANKREZEPTE Auf dem SQL Server kann man den TSQL Code von Trigger ,Views, Stored Procedures und Functions normalerweise sehen und analysieren. Die Quelltexte finden sich in der Sys-Query: Sys.syscomments Benutzung von Encryption Das ist bei uns nun gar kein Problem mehr, da wir ja eine Quelltextverwaltung haben. CREATE FUNCTION AplusB(@a int, @b int) RETURNS int WITH encryption AS BEGIN ◦ DECLARE @Sum int ◦ SET @Sum = @a + @b ◦ RETURN @Sum END GO Select dbo.aPlusB(10,20) Suche im Logfile Leider fördert aber eine simple Suche im Logfile genau unseren Text zutage. Somit ist dies sicher gar kein Schutz! Dem Kunden kann man ja den Zugriff auf seine Logfiles im Produktivsystem nicht verwehren. Wie komme ich an das Logfile Als Administrator via div. Backups Wir setzen die Datenbank kurzzeitig Offline und können dann primitiv mit dem Editor auf das Logfile zugreifen. BITTE NICHT IM PRODUKTIVSYSTEM AUSPROBIEREN SKRIPTERSTELLUNG DURCH SQL SERVER SQL Server 2005, (als auch schon SQL Server 2000) können sämtliche Datenbankobjekte in einzelne Dateien exportieren. Dies kann als Rezept für den Neubau einer Datenbank dienen oder: ◦ Als Basis für ein Datenbankprojekt Beispiel: Skripte für bestehende Datenbankobjekte erzeugen Etwa für vorheriges Projekt BEISPIEL: SQL COMPARE VON RED GATE Genügt ein Vergleichs Tool nicht völlig aus? Beides hat seine Berechtigung Verwenden Sie für normalen Quelltext ein Produkt wie Visual Source Safe? Warum nicht ein Produkt wie Araxis Merge? Vergleich der Dateien Samt Vergleich des Codes und Übersichten Visual Source Safe od. ähnlich Mergetools wie Araxis Merge Wir haben einen Satz von Dateien und vergleichen gegen die Quelltextverwaltung. Wir installieren den Source Safe und richten dies ein. Wir haben eine Historie von Änderungen samt Zeitpunkt und Benutzer Quelltexte sind oft nur durch eine Person veränderbar. Wir haben zwei oder mehr Sätze von Dateien zum Vergleich Ad hoc einsetzbar Optimiert auf Geschwindigkeit Ermöglicht eine Synchronisierung von Versionen Man vergleicht 2 Verzeichniszustände, egal wer, wann darauf zugegriffen hat. Gegenüberstellung Code Projekt Datenbank Projekt SQL Compare Red Gate Wir haben einen Satz von Dateien und Vergleichen gegen die Quelltextverwaltung Wir nutzen Visual Studio erstellen ein Projekt. Wir haben eine Historie von Änderungen samt Zeitpunkt und Benutzer Quelltexte sind oft nur durch eine Person veränderbar. Wir haben zwei Datenbanken zum Vergleich Ad hoc einsetzbar Optimiert auf Geschwindigkeit Ermöglicht eine Synchronisierung einzelnen Objekten Man vergleicht 2 Datenbanken, egal wer, wann darauf zugegriffen hat. Gegenüberstellung Datenbank DIVERSES Fragen Tipps Tricks Anregungen … Fragen, Tipps und Tricks Soll ich die kompletten Datenbankobjekte im Datenbankprojekt mitführen oder genügen nicht sogar Verzeichnisse mit aktuellen Projektänderungen? ◦ Die Antwort ergibt sich teilweise schon aus der Struktur des Projekttemplates, mit: Create, Update, Queries (ggf. Tests), … ◦ Beides kann Sinn machen. Wählen Sie aber gut! Fragen, Tipps und Tricks Sollte ich USE <Databasename> mitskripten? ◦ Wenn Sie hier überlegen, dann eher nein! Soll ich Relationen schon in der Tabelle skripten? ◦ Das funktioniert nur bei einfachen Modellen: ◦ Wenn Tabelle A auf B, B auf C und C wiederum mit A eine Relation aufbaut, kann man das nicht so einfach machen. Jedes Drop Table vernichtet 2 Relationen, die dann wieder hinzugefügt werden müssen. Nutzen Sie Erweiterten Eigenschaften im SQL Server Erweiterte Eigenschaften entsprechen Attributen in .NET DatenbankObjekte kann man so Eigenschaften zuweisen. EXEC sys.sp_addextendedproperty @name=N'SchonInEuro', @value=N'1' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'PROCEDURE',@level1name=N'SPGet_ErrorCntPerSP‘ Problem: Linked Server Eigentlich kein Problem, aber wer hat schon in COM+ die Transaktionen des DTC korrekt eingestellt? Die einzige immer funktionierende Möglichkeit Abfragen auf Linked Server überhaupt anlegen zu können, ist ohne Transaktion. Somit versagen hier Tools wie der Enterprise Mangager, SQL Compare und viele andere Tools. Tipp: Oft tut es der Query Analyser! Somit Vorsicht bei verteilten Skipten ohne korrekt vorliegendem DTC. Vergessen von Go zwischen DDL und DCL (Objekt und Berechtigung) CREATE PROCEDURE dbo.ABC … AS BEGIN ◦ … END -- hier Vergessen wir das GO GRANT EXECUTE ON [dbo].[ABC] TO [MyUser] GO Dies führt dazu, dass der User (auch MyUser) später bei Ausführung einen Fehler bekommt. MyUser darf sich bei der Ausführung ja nicht das Recht geben (Soll er ja nicht haben) Wir haben es aber beim Entwickeln schon. Wünschenswertes Nutzung von Parametern im Entwerfen von SQL (DQL, DML) Abhilfe: Verwenden Sie Kommentare für unterschiedliche SELECTs Nutzen Sie „SQL-Entwerfen“ ohne Parameter in WHERE-Statement Beispiele Einfaches Wechseln der aktuellen Datenbank In allen Versionen des Datenbankprojektes müssen erst alle offenen Dateien geschlossen werden, damit man die Datenbank schließen kann. Wunsch Präprozessorbefehle Wie etwa in VB/C# So könnte man den Code abhängig von einem Flag ausführen #ifdef BerechnungBis010107 CREATE … #else CREATE … #endif Viel Erfolg mit eigenen Datenbank Projekten ! Fragen Weitere Tipps Bitte den Primärschlüssel möglichst einfach wählen. Im Falle einer ID möglichst unabhängig vom Kunden. Dann tun Änderungen nicht weh: Kundenspez. Nummern müssen plötzlich ohne Lücken sein. Kundenspez. Nummern enthalten plötzlich Buchstaben Statt einer Zahl soll die ID eine GUID sein. … Mythen Ich nutze keine Autoincremente oder Guids Ich nutze keine Relationen Ich nutze keine Einschränkungen auf Datensatzebene Ich setze keine Indizes Der Primärschlüssel reicht, Fremdschlüssel indiziere ich nicht zusätzlich Index auf Datum bringt nichts Top N beschleunigt immer die Abfrage Abfragen optimieren Index ist nicht die „einzige“ Optimierung Fallbeispiel: 12 Abfragen für jeden Monat in das ERP-System. Ggf. besser eine Abfrage in eine temporäre Tabelle durchführen. Dann greifen folgende 12 Abfragen schneller und auf gleichen Datenbestand zu. Abfragen optimieren 2 Genauso, wie in C#/VB .NET kapselt man Teilabfragen (Basisabfragen) auf dem SQL Server. Bei Enterprise Version kein Problem, ansonsten werden aber Abfragen NICHT indiziert. So kann es vorkommen, dass man die Abfrage unterschiedlich verwendet und ganz andere Laufzeiten herauskommen. Etwa bei mir in einem Beispiel: User A mit quasi allen Daten 3 Sekunden, User B mit wenig Daten Timeout. Wechsel des Ausführplans. Alles in einer Abfrage (Ohne Zwischenabfragen) 100 ms. Nun kann der SQL Server optimieren Abfragen optimieren 3 Fragen Sie nur die Felder ab, die benötigt werden. Wenn Sie wissen wollen, ob ein Kunde schon mal bestellt hat, geben Sie nicht alle Bestellungen zurück! Der Unterschied ist immens, da ◦ im ersten Fall pur auf den Index zugegriffen werden muss, ◦ im zweiten Fall auf die Tabelle. Tipp: In .NET können Sie statt der Records auch den Skalar wert abfragen