Zur Zeit wird gefiltert nach: Datenbank
Filter zurücksetzen

CLR Funktion für den SQL Server erstellen

Lange habe ich auf die Gelegenheit und einen Anwendungsfall gewartet, um eine CLR Funktion für den SQL Server schreiben zu können. Für ein Migrationsprojekt habe ich nun einen Einsatzzweck gefunden. ;-)

Hauptgrund dafür ist, dass eine Anwendung abgelöst werden soll, die ihre Business Logik in C# implementiert hat. Der Code ist nach den gängigen Clean Code Regeln erstellt, das ist sofort ersichtlich, die Datenhaltung ist dafür sehr kreativ.

Da der imperative Code die Logik aus den Daten zusammenstellt, habe ich mich für den Ansatz entschieden, eine CLR Funktion für den SQL Server zu schreiben. Würde ich diese Logik in T-SQL abbilden, wäre die Performanz und Verständlichkeit grausam. Da eine grosse Datenmenge migriert werden muss, ist ein Ansatz notwendig, der einerseits performant läuft, anderseits wenig Aufwand verursacht.

So habe ich nun die Möglichkeit eine SQL Server Eigenschaft zu nutzen, die seit der Version 2005 existiert.

Gehen wir nun der Reihe nach vor. Zuerst wird ein Projekt vom Typ Klassenbibliothek angelegt. Anschliessend lege ich einen Schlüssel an, damit die Assembly auch signiert werden kann. Die Implementierung ist recht einfach. Es werden die Referenzen:

  • System.Data.Sql
  • System.Data.SqlTypes
  • Microsoft.SqlServer.Server

benötigt. Das Grundgerüst der Klasse hat folgenden Aufbau:


using System;
using System.Collections;
using System.Collections.Generic;
using System.Data.Sql;
using System.Data.SqlTypes;
using System.Linq;
using System.Text;
using Microsoft.SqlServer.Server;

namespace Firma.Projekt.TVFStringConvert
{
    public class StringConvertTable
    {
        [SqlFunction(FillRowMethodName = "FillRow")]
        public static IEnumerable Inititalize(string tags)
        {
            return ParseString(tags);
        }

        public static void FillRow(Object obj, out SqlInt32 keyId, out SqlInt32 valueId)
        {
            if (obj == null)
                SqlContext.Pipe.Send("The obj to create a tupel is null.");

            Tag tag = obj as Tag;
            keyId = tag.ParentId;
            valueId = tag.Id;
        }       

        public static ICollection<Tag> ParseString(string tagString)
        {
            if (tagString != null)
            {
                return ExternalLib.Convert(tagString);
            }

            return new List<Tag>(0);
        }       
    }
}

Einstiegspunkt ist in meinem Beispiel die Methode Initialize. Diese wird mit dem Attribute SqlFunction versehen. Mit diesem wird festgelegt, dass die Methode FillRow ein Tupel für die Verwendung in T-SQL aufbereitet. Der erste Parameter enthält die Informationen dafür. Die eigentliche Logik beziehe ich dabei aus einer anderen Bibliothek und ist nicht Bestandteil des Codebeispiels.

Nachdem diese CLR Funktion erstellt ist, muss diese auf dem SQL-Server registriert werden. Dies erfolgt in mehreren Schritten. Zuerst wird die Assembly registriert und anschliessend eine Funktion erstellt, welche auf die Logik in der Assembly zeigt. Nachfolgendes Beispiel zeigt den Ablauf:


USE TestDatabase
GO

-- Für Permission Set External_Access und Unsafe
-- Bad Practice, besser die DLL mit einem richtigen Zertifikat signieren 
-- ALTER DATABASE TestDatabase SET TRUSTWORTHY ON

IF EXISTS (SELECT name FROM sysobjects WHERE name = 'StringConvertTable')
   DROP FUNCTION StringConvertTable
GO

IF EXISTS (SELECT name FROM sys.assemblies WHERE name = 'TVFStringConvert')
   DROP ASSEMBLY [TVFStringConvert]
GO

CREATE ASSEMBLY [TVFStringConvert] 
FROM 'C:\projects\ Firma.Projekt.TVFStringConvert\Firma.Projekt.TVFStringConvert.dll'
WITH PERMISSION_SET = UNSAFE --SAFE 
GO

CREATE FUNCTION StringConvertTable(@tags nvarchar(max)) 
RETURNS TABLE (
   keyId int,
   valueId int
)
AS EXTERNAL NAME TVFStringConvert.[Firma.Projekt.TVFStringConvert.StringConvertTable].[Inititalize]
GO

-- Test (Weiterverwendung mit CROSS APPLY)
SELECT * FROM StringConvertTable('kreative Datenlogik')
GO

Falls die Fehlermeldung

Execution of user code in the .NET Framework is disabled. Enable "clr enabled" configuration option.

erscheint, muss diese Option aktiviert werden.

Mit folgendem Befehl ist dies möglich:


-- Servereinstellungen
EXEC sp_configure 'clr enabled', 1
GO
RECONFIGURE
GO

Ich habe nun einen praktikablen und pragmatischen Weg, der auf die Situation abgestimmt mit akzeptabler Performanz die Daten (kreative Datenlogik) in auswertbarer Form für die Migration bereitstellt.

Mein nächster Schritt besteht darin das Ganze mit parallelisierter Ausführung weiter zu beschleunigen.

Weitere Informationen zum Thema:

Entity Framework 4.3 - Schema Migrations für Code First (DbContext)

Eine Anwendung lebt bekanntlich von Änderungen. Nicht selten haben diese Änderungen auch Auswirkungen auf das Datenbankmodell. Das Entity Framework unterstützt mittlerweile DB First, Model First und Code First. Für DB First und Model First ist es kein grösseres Problem Änderungen auf der Datenbank nachzuführen, mit ein paar Tricks und Tools sind sogar Roundtrips zwischen Datenbank und Modell möglich.

Die möglichen Vorgehensweisen sind in der Präsentation Tipps und Tricks Entity Framework ersichtlich.

Bei Code First war dieser Ansatz bisher ein Ding der Unmöglichkeit. Lediglich mit Zusatztools wie SQL Delta und einer ITIL-konformen Umgebung konnten die Änderungen vorgenommen werden, ohne dass die produktiven Daten negativ beeinflusst wurden. Der Störfaktor war jedoch immer der Modelhash in der Tabelle EdmMetadata. Entweder hat man diesen Eintrag mit aktualisiert oder aber die Konvention entfernt.

Seit ein paar Tagen ist nun die Beta 1 vom Entity Framework 4.3 draussen und ich wollte natürlich auch gleich die Anpassungen ausprobieren. Die Pakete gibt es über NuGet und die Unterstützung für die Installation von Vorabversionen benötigt im minium die Version 1.6. Ich musste zuvor auf diese Version aktualisieren. Die ältere Version Entity Framework.Migrations muss noch vom System entfernt werden (erfordert einen Neustart von Visual Studio).

Nach diesem Update besteht die Möglichkeit die Vorabversion mit dem Befehl:

Abbildung 1
Abbildung 1 Install-Package Entity Framework -IncludePreRelease

auf dem System zu installieren.

Also beginne ich mit einem kleinen Beispiel:


    public abstract class Product
    {
      public int Id { get; set; }
 
      [StringLength(50)]
      [Required]
      public string Name { get; set; }
 
      [StringLength(400)]
      public string Description { get; set; }
    }
 
    public class Book : Product
    {
      [StringLength(10)]
      [Required]
      public string ISBN10 { get; set; }
 
      [StringLength(13)]
      [Required]
      public string ISBN13 { get; set; }
 
      public int LanguageCD { get; set; }
 
      [Required]
      public int Pages { get; set; }
    }
 
    public class EBook : Book
    {
      [Required]
      public string Filename { get; set; }
    }
 
    public class Hardcover : Book
    {
      [StringLength(20)]
      [Required]
      public string Size { get; set; }
 
      [Required]
      public double Weight { get; set; }
    }
 
    public class BookInheritanceContext : DbContext
    {
 
      public BookInheritanceContext()
        : base("EfCodeFirstMigrations")
      { 
 
      }
 
      public IDbSet<Product> Products { get; set; }
 
      protected override void OnModelCreating(DbModelBuilder modelBuilder)
      {
        //  TPH ist Standardkonvention, da beste Performance.
        //  Für  Individual-Lösungen  macht das auch durchaus Sinn , wenn
        //  jedoch die Release-Tauglichkeit  gewährleistet werden muss
        //  ist TPT die bessere Wahl.
 
        //  Mapping  für benutzerdefinierten Diskriminator 
        // // const  string   discriminator  = "ProductTypeNbr ";
        // // modelBuilder . Entity <Product >( )
        ////  .Map<Book>(m => m.Requires(discriminator).HasValue(2))
        ////  .Map<EBook>(m => m.Requires(discriminator).HasValue(3))
        ////  .Map<Hardcover>(m => m.Requires(discriminator).HasValue(4))
        ////  .ToTable("Product");
 
        base.OnModelCreating(modelBuilder);
      }
    }

Nun beginnt die Phase der Erweiterung. In diesem Zusammenhang ist es wichtig zu akzeptieren, dass die Arbeit mit NuGet ein wenig intensiver wird. Im neuen Release sollen nun ein paar Erweiterungen vorgenommen werden, die Auswirkungen auf das Datenmodell haben werden.

Der erste Schritt ist nun das öffnen der NuGet-Konsole und die Eingabe von:

Abbildung 2
Abbildung 2 Enable-Migrations

auszuführen. Nach dieser Aktion befindet sich ein neuer Ordner Migrations im Projekt.

Abbildung 3
Abbildung 3 Neuer Ordner Migrations

Ich beginne nun mit meinen Anpassungen an der Klasse EBook. In der neuen Version soll die Möglichkeit bestehen, dass pro Buch mehrere Dateien für alternative Dateiformate (mobi, epub, pdf usw.) hinterlegt werden können.


public class EBook : Book
    {
      [Required]
      public string Filename { get; set; }
 
      [Required]
      public ICollection<BookFile> AlternativeFiles { get; internal set; }
    }
 
    public class BookFile
    {
      public int Id { get; set; }
      public int Type { get; set; }
      public int Filename { get; set; }
    }

Nach der Fertigstellung geht es nun darum das Update der Datenbank vorzunehmen. Auch hier ist die primäre Schaltzentrale die NuGet-Konsole. Zuerst muss jedoch eine kleine Anpassung in der Configuration-Klasse vorgenommen werden. Im Konstruktur muss der Wert von AutomaticMigrationsEnabled auf true gesetzt werden.

Kommen wir zurück auf die NuGet-Konsole, mit dem Befehl:

Abbildung 4
Abbildung 4 Update-Database -Script

wird ein SQL-Skript mit den notwendigen Änderungen auf der Datenbank erstellt.

Der Output für dieses Beispiel:


CREATE TABLE [BookFiles] (
    [Id] [int] NOT NULL IDENTITY,
    [Type] [int] NOT NULL,
    [Filename] [int] NOT NULL,
    [EBook_Id] [int],
    CONSTRAINT [PK_BookFiles] PRIMARY KEY ([Id])
)
CREATE INDEX [IX_EBook_Id] ON [BookFiles]([EBook_Id])
ALTER TABLE [BookFiles] ADD CONSTRAINT [FK_BookFiles_Products_EBook_Id] FOREIGN KEY ([EBook_Id]) REFERENCES [Products] ([Id])
CREATE TABLE [__MigrationHistory] (
    [MigrationId] [nvarchar](255) NOT NULL,
    [CreatedOn] [datetime] NOT NULL,
    [Model] [varbinary](max) NOT NULL,
    [ProductVersion] [nvarchar](32) NOT NULL,
    CONSTRAINT [PK___MigrationHistory] PRIMARY KEY ([MigrationId])
)
BEGIN TRY
    EXEC sp_MS_marksystemobject '__MigrationHistory'
END TRY
BEGIN CATCH
END CATCH
INSERT INTO [__MigrationHistory] ([MigrationId], [CreatedOn], [Model], [ProductVersion]) VALUES ('201201151955537_AutomaticMigration', '2012-01-15T19:55:54.189Z', 0x1F8B...EDMX-Modell...0, '4.3.0-beta1')

Neben den Tabellen wird neu auch für die Fremdschlüssel-Spalten ein Fremdschlüsselindex erstellt. Bisher war das ein negativer Unterschied zum Model First - Ansatz, der mit der Version EF 4.3 der Vergangenheit angehören wird.

Was auch auffällt ist die Systemtabelle __MigrationHistory, in der das aktuelle Abbild des Modells gespeichert wird. Die Tabelle EdmMetadata existiert nicht mehr. Mit EF 4.3 gehört diese ebenfalls der Vergangenheit an. Bei bestehenden Modellen wird diese jedoch erst entfernt, wenn die Datenbank neu erstellt wird. Ein Detail, welches sich daraus ergibt: Migrations funktionieren nur mit dem SQL-Server!!!!

Ohne den Swich "-Script" lassen sich die Änderungen direkt an die Datenbank übertragen. Bei dem automatischen Ansatz würde ich persönlich darauf verzichten, da es keine Möglichkeit der Versionierung gibt. Dieser Teil lässt sich mit dem Speichern der SQL-Skripts jedoch organisatorisch in den Griff bekommen. Beim codebasierten Ansatz ist es ein wenig eleganter gelöst, jedoch sind die Möglichkeiten auch hier begrenzt.

Bei dieser Beta soll es sich um die Letzte handeln, sodass im Laufe des ersten Quartals die finale Version Entity Framework 4.3 zur Verfügung stehen wird.

Ich finde diesen Ansatz sehr interessant, da nun auch beim codezentrierten Ansatz mit dem Entity Framework Schema-Migrations möglich werden. Als Entwickler muss man jedoch berücksichtigen, dass diese nur mit dem SQL-Server funktionieren. Zudem ist ein Round-Trip nicht, bzw. nur mit einem nicht im Verhältnis stehenden Aufwand möglich. (Stichwort: Meet in the Middle). Hier beginnt das Problem aber häufig mit der Planung.

Weitere nützliche Informationen befinden sich in den Blogs vom ADO.NET - Team unter:

Entity Framework 4.1 - Code-/Model First und die Unterschiede einer Gemeinsamkeit

Code- bzw. Model First mit dem Entity Framework sind 2 unterschiedliche Ansatzmodelle. Bei Model First wird in der Regel mit dem Mapping begonnen, die Vorgehensweise dazu ist auch als Middle out bekannt. Anhand des Mappings wird anschliessend der Code und das DDL-Skript für die Datenbank erstellt. Bei Code First wird zuerst der Code geschrieben und daraus das Mapping und das DDL-Skript für die Datenbank erstellt. Dieser Ansatz ist auch häufig als Top down geläufig.

Abbildung 1
Abbildung 1 Middle out als Vertreter der modellzentrierten Anwendungsentwicklung
Abbildung 2
Abbildung 2 Top down für die codezentrierte Anwendungsentwicklung

Die Gemeinsamkeit beider Vorgehensweisen ist die Erstellung der Datenbank. Der Unterschied liegt jedoch darin, dass bei der modellzentrierten Anwendungsentwicklung mit Hilfe von T4 Einfluss auf das DDL-Skript genommen werden kann, während bei Code First die Datenbank erstellt wird.

Wo liegt also nun der Unterschied in dieser Gemeinsamkeit, ausser bei T4? Dazu muss man sich nur das von der T4 erstellte SQL-Skript betrachten:


-- --------------------------------------------------
-- Creating all FOREIGN KEY constraints
-- --------------------------------------------------
 
-- Creating foreign key on [OrderID] in table 'OrderItems'
ALTER TABLE [dbo].[OrderItems]
ADD CONSTRAINT [FK_OrderOrderItem]
    FOREIGN KEY ([OrderID])
    REFERENCES [dbo].[Orders]
        ([ID])
    ON DELETE NO ACTION ON UPDATE NO ACTION;
 
-- Creating non-clustered index for FOREIGN KEY 'FK_OrderOrderItem'
CREATE INDEX [IX_FK_OrderOrderItem]
ON [dbo].[OrderItems]
    ([OrderID]);
GO

Im Bereich der Fremdschlüssel werden im Gegensatz zum Code First - Ansatz die FK-Indizes angelegt. Darin liegt der Unterschied. Bei wenigen Daten wird das nicht besonders schmerzhaft sein, wenn jedoch das Datenvolumen wächst und der Datenbankserver zudem noch virtualisiert ist, können da an der einen oder anderen Ecke Performance-Probleme entstehen, natürlich schleichend. Betrachten wir es anhand eines Beispiels mit wenigen Daten. In meinem Testszenario existieren ein paar Tausend Datensätze in der Orders-Tabelle aus der 10 Datensätze selektiert werden sollen.

Die SQL-Abfrage hat folgenden Aufbau:


SELECT
	*
FROM dbo.Orders o
INNER JOIN dbo.OrderItems oi ON o.Id = oi.OrderId 
WHERE o.id IN (1,5,10,34,80,92,500,897,3456,4567)

Diese wird einmal mit und ohne Fremdschlüssel-Index auf die OrderId-Spalte ausgeführt. Der Ausführungsplan zeigt folgendes:

Abbildung 3
Abbildung 3 Ausführungsplan mit und ohne Fremdschlüssel-Indizes

Wenn man die Kosten in Relation betrachtet, so benötigt die Datenbank ohne Fremdschlüssel mehr Ressourcen.

Nun gibt es mehrere Möglichkeiten die Indizes zu erstellen. Ich bevorzuge die Art der Metaprogrammierung. Sprich der SQL-Server genauer gesagt ein SQL-Skript soll mithilfe der Angaben die notwendigen Inidizes selbst erstellen. Dafür habe ich folgendes Skript geschrieben:


DECLARE @table nvarchar(100), 
	@fkName nvarchar(100), 
	@columnName nvarchar(100), 
	@index nvarchar(800)

DECLARE missing_FkIndex_cursor CURSOR FOR SELECT
      t.name as TB_Name
      , fk.name as FK_Name
      , c.name as Column_Name
FROM sys.foreign_key_columns fkc
INNER JOIN sys.tables t ON t.object_id = fkc.parent_object_id
INNER JOIN sys.foreign_keys fk ON fk.parent_object_id =
fkc.parent_object_id
INNER JOIN sys.columns c ON c.object_id = fk.parent_object_id AND
c.column_id = fkc.parent_column_id
LEFT OUTER JOIN sys.index_columns indc ON indc.object_id = c.object_id AND
c.column_id = indc.column_id
LEFT OUTER JOIN sys.indexes ind ON ind.object_id = indc.object_id AND
ind.index_id = indc.index_id
WHERE fk.type = 'F' AND ind.name is null

OPEN missing_FkIndex_cursor;

FETCH NEXT FROM missing_FkIndex_cursor 
INTO  @table, @fkName, @columnName

WHILE @@FETCH_STATUS = 0
BEGIN
	SET @index = 'CREATE INDEX IX_FK_' + @fkName + '_' + @columnName + ' ON ' + @table + ' (' + @columnName + ')'
	EXECUTE sp_executesql @index
	PRINT @index
	
	FETCH NEXT FROM missing_FkIndex_cursor 
		INTO  @table, @fkName, @columnName
END
CLOSE missing_FkIndex_cursor;
DEALLOCATE missing_FkIndex_cursor;

Dieses Skript erstellt den Fremschlüsselindex nur, wenn dieser noch nicht existiert. Nun liegt es in der Natur des Menschen, dass solche Anpassungen, gerade wenn man die gewohnte Umgebung verlassen muss, vergessen werden.

Für die Code First Variante wäre es nun möglich, dieses Skript in einen Initializer zu packen, der bei der Erstellung der Datenbank ausgeführt wird. Der Vorteil dieser Variante, der Initalizer kann in eine Library verstaut und zur Erstellung der Datenbank genutzt werden.

Der Initializer könnte folgenden Aufbau haben:


 public class CreateSqlServerDatabaseWithFkIndizesIfNotExists<T> 
    : CreateDatabaseIfNotExists<T> where T : DbContext
  {
    protected override void Seed(T context)
    {
      context.Database.ExecuteSqlCommand(GetFkIndexSqlScript());
      base.Seed(context);
    }

    private string GetFkIndexSqlScript()
    {
      return @"
        DECLARE @table nvarchar(100), 
	        @fkName nvarchar(100), 
	        @columnName nvarchar(100), 
	        @index nvarchar(800)

        DECLARE missing_FkIndex_cursor CURSOR FOR SELECT
              t.name as TB_Name, 
              fk.name as FK_Name
              , c.name as Column_Name
              --, ind.name as Index_Name
              --, fk.type_desc as FK_Type
        FROM sys.foreign_key_columns fkc
        INNER JOIN sys.tables t ON t.object_id = fkc.parent_object_id
        INNER JOIN sys.foreign_keys fk ON fk.parent_object_id =
        fkc.parent_object_id
        INNER JOIN sys.columns c ON c.object_id = fk.parent_object_id AND
        c.column_id = fkc.parent_column_id
        LEFT OUTER JOIN sys.index_columns indc ON indc.object_id = c.object_id AND
        c.column_id = indc.column_id
        LEFT OUTER JOIN sys.indexes ind ON ind.object_id = indc.object_id AND
        ind.index_id = indc.index_id
        WHERE fk.type = 'F' AND ind.name is null

        OPEN missing_FkIndex_cursor;

        FETCH NEXT FROM missing_FkIndex_cursor 
        INTO  @table, @fkName, @columnName

        WHILE @@FETCH_STATUS = 0
        BEGIN
	        SET @index = 'CREATE INDEX IX_FK_' + @fkName + '_' + @columnName + ' ON ' + @table + ' (' + @columnName + ')'
	        EXECUTE sp_executesql @index
	        PRINT @index
	
	        FETCH NEXT FROM missing_FkIndex_cursor 
		        INTO  @table, @fkName, @columnName
        END
        CLOSE missing_FkIndex_cursor;
        DEALLOCATE missing_FkIndex_cursor;      ";
    }
  }

Wie die Datenbank mit dem Skript initialisiert werden kann, zeigt folgender Code:


      var initializer = new CreateSqlServerDatabaseWithFkIndizesIfNotExists<TestContext>();
      Database.SetInitializer<TestContext>
        (initializer);
      initializer.InitializeDatabase(new TestContext());

Dieser Ansatz ist etwas tricky, aber es ist aktuell der einzige lauffähige Code. Muss es mal mit dem Update 1 von EF 4.1 nochmals durchtesten.

Wenn nun die Datenbank erstellt wird, läuft das o.a. Skript und der Unterschied einer Gemeinsamkeit ist verschwunden. ;-)

Entity Framework 4 - ORM und die Unterschiede zwischen Domänen- und ER-Modell

Brownfield bzw. DB First ist unter vielen Entwicklern unbeliebt. Am liebsten würden sie auf der grünen Wiese beginnen. Es gibt viele Begriffe und Ideologien, häufig stellt es aber eine Herausforderung dar, diese in der Praxis zu vereinen.

DB First ist grundsätzlich nicht immer schlecht, da bei diesem Ansatz der Blickpunkt auf den Daten liegt. Gerade auch im Hinblick, wenn die Datenbank bereits besteht, macht es nicht immer Sinn ein neues Datenbankmodell zu erstellen und eine Migration der Daten ins Auge zu fassen.

Die Herausforderungen, die dann auf einen zukommen, hat Patrick in seinem Beitrag Data quality as a business value beschrieben.

Bei solchen Szenarien habe ich gelegentlich den Eindruck, dass einige Entwickler den Bezug zur Realität verlieren. Tragisch wird es dann, wenn auch die Einsicht für Kompromisse fehlt und auf biegen und brechen der gewünschte Ansatz durchgedrückt werden muss. Dabei wird gelegentlich vergessen, dass beim ORM auf der einen Seite mit zwei Arten von Technologien gearbeitet wird, die auf unterschiedliche Weise funktionieren und optimiert werden. Auf der anderen Seite dienen die relationalen Daten nicht selten als Quelle für ein DWH oder Reporting mit anderen Tools wie zum Beispiel Cognos. Hier stellt sich dann die Frage, welcher Ansatz ist nun günstiger? Sind es ständig ändernde Datenmodelle oder ein O/R Modell, bei dem sich die konzeptionelle Sicht von der Datensicht unterscheiden darf?

Ich habe nun so einen Fall, dass eine Anwendung von .NET 3.5 auf .NET 4.0 portiert wird. Dadurch habe ich die Möglichkeit im Entity Data Model (EDM) auch die Fremdschlüssel in das Modell mit einzubeziehen und ein paar Workarounds zu entfernen.

Es gibt einen Punkt im Modell, wo Vererbung verwendet wird. Bedingt durch einen fehlenden Diskriminator in der Basistabelle war die Modellierung in der ersten Version des Entity Framework sehr komplex und an eine Arbeit mit dem Designer war nicht mehr zu denken.

Die Typerkennung konnte ich aus mehreren existierenden Spalten ableiten und so habe ich im Model mit <definingquery></definingquery>den Diskriminator bilden können.

Mit dem Einsatz mehrerer Spalten auf Tabellen-Ebene sollte der Feldmissbrauch verhindert werden, eine Regel im Datenbankdesign, die nicht unbedingt in der Objektorientieren Welt vorkommt. Das sich diese Felder alle in einer Tabelle (TPH) befinden ist primär ein Optimierungsentscheid, da der Einsatz von Super-Entitäten (TPT) einen erhöhten Join-Aufwand zur Folge haben können und im Entity Framework 3.5 ganz klar einen Fehlentscheid darstellen. Im EDM unterscheidet sich diese Tabelle vom Design, da eine Vererbungshierarchie vorhanden ist.

Mit der Portierung war nun die Idee, dass diese Tabelle einen Diskriminator erhält, damit die komplizierte <definingquery></definingquery>aus dem Modell entfernt werden kann. Bei Modellaktualisierungen wird diese häufig zerstört und eine manuelle Nachbearbeitung wird dadurch erforderlich, natürlich im XML.

Ein zusätzliches Feld hätte in diesem Zusammenhang auch eine Datenmigration zur Folge und so schlug der Verantwortliche für die Datenbank vor stattdessen eine View zu verwenden, da der bisher abgeleitete Diskriminator-Wert über eine andere Tabelle zur Verfügung steht.

Im ersten Moment mag das ein wenig ungewohnt klingen, aber dieser Ansatz verursacht auf Ebene der Datenbank geringe bis keine Kosten, da eine Datenmigration nicht erforderlich wird.

Im zweiten Moment kommen dann die Überlegungen:

  • Vererbung, kein Problem
  • Fremdschlüssel von einer Tabelle zur View, geht nicht aber in der konzeptionellen Sicht lässt sich die Zuordnung theoretisch realisieren

Beginnen wir mit dem Beispiel, ich habe eine Tabelle, die nun als View zur Verfügung steht und zusätzlich den Diskriminator enthält. Im Modell sollen die Zuordnungen und die Vererbung beibehalten werden, damit Code-Anpassungen ausgeschlossen werden können. Das Ziel soll nun sein, die <definingquery></definingquery>für diesen Bereich zu entfernen, damit das EDM zukünftig wieder einfacher erweitert werden kann.

Ein kleiner Teilausschnitt des bestehenden Modells sieht so aus:

Abbildung 1
Abbildung 1 Teilauszug eines Modells mit Vererbung (TPH). Die Entität Calculation soll durch eine View ersetzt werden

Die Entität Calculation ist der Knackpunkt, bisher wird der Diskriminator innerhalb <definingquery></definingquery>definiert und macht die Aktualisierung des Modells zu einer Herausforderung.

Der Unterbau soll durch ein View ersetzt werden. Grundsätzlich liesse sich dass im XML per Hand realisieren, doch ich verwende mal den Designer und entferne die Entität Calculation aus dem Modell.

Abbildung 2
Abbildung 2 Modell nach der Entfernung der Entität

Nun füge ich die View hinzu, in dem ich den Punkt Modell aus Datenbank aktualisieren wähle, und nenne diese wieder Calculation. Nach diesem Schritt habe ich jedoch noch nicht die Vererbung sowie die Beziehung zur Header-Entität.

Abbildung 3
Abbildung 3 zeigt das Modell nachdem die View hinzugefügt wurde

Was das Entity Framework immer noch macht, ist die eigenständige Definition von Entitätsschlüsseln bei Views. In der aktuellen Version können die Schlüssel der Eigenschaften abgewählt werden, sodass in meinen Fall nur eine Eigenschaft als Primärschlüssel existiert. In der Vorgängerversion war das ein wenig Gefrickel.

Als nächstes erstelle ich die Zuordnung zur Header Entität. Dazu wähle ich auf der Entität Header den Menüpunkt Hinzufügen/Zuordnung.

Abbildung 4
Abbildung 4 Zuordnung zwischen Tabelle und View erstellen

Wenn ich die Zuordnung erstellt habe, dann begrüsst mich folgende Fehlermeldung:

Fehler 11009: Die Eigenschaft 'HeaderPH_AutoID' ist nicht zugeordnet.

Dieser Fehler kommt zustande, weil die View die ID bereits enthält und eine neue Eigenschaft für die Zuordnung erstellt wurde. Als Erstes muss also die ID aus der Entität entfernt werden und die ID des Storage Modell auf die HeaderID über die Mapping Details realisiert werden.

Abbildung 5
Abbildung 5 zeigt die Anpassung des Mappings

Anschliessend geht es darum, die Vererbungshierarchie wieder herzustellen, dazu leite ich die Entität MarketShare von der Entität Calculation ab. Dann entferne ich die bereits zugeordneten Spalten für MarketShare aus der Calculation-Entität. Das Gleiche gilt auch für den nun existierenden Diskriminator. Dieser wird ebenfalls aus der Calculation-Entität entfernt.

Nun muss für die Entität MarketShare das Mapping auf die View des Storage-Layers definiert und die Bedingung hinzugefügt werden. Nach diesen Anpassungen sieht mein Modell wieder aus wie in Abbildung 1 und die Validierung ist auch erfolgreich.

In meiner Business-Logik sind nun keine Anpassungen notwendig, da das Modell identisch ist. Der Datenbankverantwortliche ist zufrieden, da die Anpassung keine Migration zur Folge hat.

Dieses Beispiel funktioniert nur in Verbindung mit DB First (ggf. auch Model First im Roundtrip) und dem EDM-Designer.

Mit diesem Kompromiss lässt sich eigentlich leben, da keine Anpassungen in der Business-Logik notwendig werden.

Das Entity Framework macht die Migration trotzdem nicht einfach, wenn im alten Modell Workarounds zum Einsatz kamen, um die Fremschlüssel abzubilden oder die Prozedur-Signaturen im Context von Hand geschrieben worden sind. Diese müssen dann entfernt werden. Weiterhin kann es auch passieren, dass einige Eigenschaften ihre Werte für die Einstellung Nullable verlieren, auch hier ist dann Handarbeit angesagt, damit das Modell wieder fehlerfrei validiert wird.

Das Beispiel zeigt aber auch, dass die Philosophie des ER-Modells von Dr. P. Chen, auf welchen das EDM aufbaut, sehr flexibel sein kann.

20.05.2011
11:03

Oracle - Ausgeführte SQL-Statements auflisten

Irgendwie komme ich doch nicht so richtig von Oracle los. Habe wieder einen Analyse-Job gefasst, um ein paar Abfragen zu optimieren.

Auf dem SQL Server würde ich den Profiler verwenden und mir zudem die ausgeführten SQL-Statements über die Query-Stats holen.

Auf einer Oracle-Datenbank kann dies mit folgender Abfrage nachgebildet werden:


SELECT
   module, 
   sql_fulltext , 
   u.username , 
   round((s.disk_reads/decode(s.executions,0,1, s.executions)),2)  disk_reads_per_exec, 
   s.disk_reads , 
   s.buffer_gets , 
   s.parse_calls , 
   s.sorts , 
   s.executions , 
   s.rows_processed , 
   s.first_load_time , 
   sharable_mem , 
   persistent_mem , 
   runtime_mem, 
   cpu_time, 
   elapsed_time
  FROM
   sys.v_$sql s, 
   sys.all_users u 
  WHERE
   s.parsing_user_id=u.user_id 
   and u.username = 'theuser'
  ORDER BY
   FIRST_LOAD_TIME desc

Werde mal weiter auf Lösungssuche gehen...

Translate this page

Kategorien

  • [-].NET Development (207)
  • [-]Datenbank (24)
  • HTML (1)
  • Konfiguration (12)
  • Mind Map (9)
  • Off-topic (9)
  • Open Source (3)
  • Qualität (6)
  • Sharepoint (2)
  • Sicherheit (2)

Archiv

Social Bookmarking

Bookmark bei: Mr. Wong Bookmark bei: Webnews Bookmark bei: Icio Bookmark bei: Oneview Bookmark bei: Linkarena Bookmark bei: Favoriten Bookmark bei: Seekxl Bookmark bei: Favit Bookmark bei: Social Bookmarking Tool Bookmark bei: Power Oldie Bookmark bei: Bookmarks.cc Bookmark bei: Newskick Bookmark bei: Newsider Bookmark bei: Linksilo Bookmark bei: Readster Bookmark bei: Folkd Bookmark bei: Yigg Bookmark bei: Digg Bookmark bei: Del.icio.us Bookmark bei: Reddit Bookmark bei: Simpy Bookmark bei: StumbleUpon Bookmark bei: Slashdot Bookmark bei: Netscape Bookmark bei: Furl Bookmark bei: Yahoo Bookmark bei: Spurl Bookmark bei: Google Bookmark bei: Blinklist Bookmark bei: Blogmarks Bookmark bei: Diigo Bookmark bei: Technorati Bookmark bei: Newsvine Bookmark bei: Blinkbits Bookmark bei: Ma.Gnolia Bookmark bei: Smarking Bookmark bei: Netvouz Information