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:
Wieder gefunden
Wenn Du ein Schiff bauen willst, dann trommle nicht Männer zusammen um Holz zu beschaffen, Aufgaben zu vergeben und die Arbeit einzuteilen, sondern lehre die Männer die Sehnsucht nach dem weiten, endlosen Meer.
Antoine de Saint-Exupery
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:
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:
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:
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:
Sharepoint 2010 - Business Prozesse mit Workflows
In meinen ersten Beitrag habe ich ja schon über die interessanten Nebeneffekte von Sharepoint berichtet. Auch diese Woche hatte ich wieder Nebeneffekte, die schon arg Grenzwertig waren. Eine Technolgie, die sich als Zeitfresser entpuppte war Linq to Sharepoint.
Meine Best Practice - Empfehlung: Nicht einsetzen für Business-Anwendungen, die ihren Namen gerecht werden wollen und auch einiges an Daten zu verwalten haben.
Details und Workaround-Frickelein werden folgen.
Zurzeit bin ich immer noch in der dritten Phase und ich glaube, dass es nach dieser Phase drei mögliche Wege gibt, die von Entwicklern eingeschlagen werden. Aktuell gehe ich tiefer in die Materie zu Workflows ein. Seit der PDC 2008 habe ich mich dazu entschieden, Microsoft-Technologien nur noch systematisch anzugehen, da der pragmatische Weg in meinen Augen unkontrollierbar geworden ist. Linq to Sharepoint hat mich zusätzlich darin bestärkt, hier habe ich zu sehr auf die Aussagen von Sharepoint MVP´s vertraut. Kommt nicht wieder vor.
Im Bereich der Workflows habe ich die möglichen Ansetze und Varianten im nachfolgenden Mind Map festgehalten. Hier muss ich zugeben, dass mich dieser Bereich von Sharepoint sehr interessiert.
Sharepoint 2010 - Mein Einstieg
Sharepoint gehört nun seit 2 Monaten zu einem meiner Aufgabenfelder und ich muss sagen, die Lernkurve ist aus meiner Sicht nicht zu unterschätzen. Der Hauptgrund dafür: Es kann viel, aber einiges davon doch noch nicht so richtig oder mit interessanten Nebeneffekten.
Wie bereits erwähnt die Lernkurve wird dadurch unglaublich steil, wohl deshalb hat Sharepoint unter Entwicklern nicht den besten Ruf. Wenn ich in meinem Bekanntenkreis herumfrage, dann sind die Endbenutzer überwiegend zufrieden bis begeistert von Sharepoint, Entwickler dagegen nicht. Es gibt Ausnahmen, diese tragen in der Regel den Zusatz MVP oder wollen es werden. MVP bedeutet in diesem Zusammenhang Marketing Voll Profi.
Gestern habe ich mich mit einem langjährigen Sharepoint-Entwickler unterhalten und dabei auch meinen Frust abgelassen. Die Antwort darauf war: "Das ging mir auch so." Also gehe ich davon aus, dass man als Entwickler für Sharepoint mehrere Phasen durchläuft, bis man die Macken dieser Plattform kennt und somit die Lernkurve überwunden hat.
Grundsätzlich gehe ich immer ohne Vorurteile eine neue Technologie an, ich wurde aber von mehreren Entwicklern vorgewarnt. Mit diesem Schritt startete ich in die erste Phase von Sharepoint.
In dieser ersten Phase habe ich öfter überlegt wie denn wohl die XML-Konfigurationen fehlerfrei zusammenarbeiten (es funktioniert nicht immer), also versucht ein Verständnis für diese Plattform zu entwickeln. Mit dem Kennenlernen der ersten Macken startete ich in die zweite Phase.
In der zweiten Phase überlegte ich, wie sich das Ganze sauber testen lässt. In einigen Bereichen bin ich immer noch auf der Suche nach einer robusten Lösung. Mit Sicherheit weiss ich aber, dass bei Sharepoint die Priorität auf den Integrationstests liegen muss, bspw. mit Testdaten in den Listen über dem Schwellenwert. So lassen sich besonders schnell Macken im System finden, gerade auch bei der Verwendung von Linq to Sharepoint.
Ein paar Gründe warum ich diese Meinung vertrete sind:
- die XML-Konfigurationen
- die starken Abhängigkeiten
- auch die Tatsache, dass der Endanwender die Möglichkeit hat, Einstellungen am System vorzunehmen, die nicht unerheblich sein können (Nebeneffekte).
Des Weiteren kann es gelegentlich auch vorkommen, dass ContentTypes und Features ihre Arbeit verweigern. Für zukünftige Projekte möchte ich hier auch Integrationstests als Frühwarnsysteme einsetzen können, auch um zu sehen, ob es die Konfiguration zerhauen hat. In ein paar Bereichen wird mir T4 ein wenig Arbeit abnehmen können.
Zurück zu den Phasen, nach meinem gestrigen Gespräch ist es wohl die dritte Phase, in der ich mich aktuell befinde, denn auch langjährige Sharepoint-Entwickler hatten mit dem Kopf genickt und sagten, das Gefühl hatten sie auch schon.
In der dritten Phase verspürt man den Wunsch danach, mal eine Person aus dem Sharepoint-Team hauen zu dürfen. Die abgeschwächte Form könnte auch die Frage zu sich selbst sein: Was haben die geraucht? Wie lange diese Phase andauert kann ich jetzt noch nicht sagen und welche Phase danach kommt auch noch nicht. Ich glaube aber, dass es in dieser Phase eine sehr hohe Absprungrate gibt und die zukünftige Meinungsbildung über Sharepoint stattfindet.
Grundsätzlich finde ich den Ansatz von Sharepoint nicht schlecht. Das zum Beispiel die funktionale Ausgestaltung simpler Workflows, mit Hilfe des Sharepoint Designers, durch einen Poweruser erfolgen kann, hat in einigen Situationen sicherlich positive Auswirkungen auf die Unternehmensarchitektur.
Die Herausforderung liegt aber auf der Überprüfung der Funktionalität. Ein weiteres Problem kann sein, dass sich ein Poweruser zuviel zutraut und bei Anpassungen den Bock der Böcke schiesst, was wieder negative Auswirkungen auf das Daily Business haben kann. Es gibt immer Vor- und Nachteile, hier muss im Vorfeld abgewogen werden können, ob der Weg sinnvoll ist.
Ich persönlich interessiere mich primär für die Bereiche Workflows, WCF, WIF und BI mit Sharepoint. Mit CMS basierend auf Sharepoint bin ich nun auch schon in Berührung gekommen und konnte mir mein Urteil darüber bilden.
Da ich nun schon einige Macken kennengelernt habe, werde ich diese in nächster Zeit veröffentlichen, falls mir Sharepoint die Zeit dafür gibt.
Vor allem interessiert mich auch, ob ich die dritte Phase überstehe. Falls nicht: Workflows, WCF, WIF und BI gehen ja zum Glück auch ohne Sharepoint. ;-)



Social Bookmarking