head.WriteLine()

Donnerstag, September 28, 2006

Lokalisierung in .NET 2.0

Eine weitere Änderung in .NET 2.0 betrifft die Code-Generierung bei lokalisierbaren Eigenschaften. Bisher war es so, dass der Windows Forms Code Generator grundsätzlich für alle Eigenschaften Code erzeugt hat, die mit dem Localizable-Attribut dekoriert waren, unabhängig davon, ob diese ihre Standardwerte enthielten oder nicht.

Probieren Sie zur Veranschaung einmal folgendes Beispiel in Visual Studio 2003: Ziehen Sie einen Button auf eine Form und schauen Sie sich den in der InitializeComponent()-Methode generierten Code an. Sie werden feststellen, dass lediglich 4 Eigenschaften zugewiesen wurden, nämlich extakt die, die keine Standardwerte enthielten. Wechseln Sie nun wieder in den Designer und setzen Sie die Localizable-Eigenschaft der Form auf true. Wenn Sie sich jetzt erneut den Code anschauen, werden Sie feststellen, dass diesmal für 20 Eigenschaften Code generiert wurde, unabhängig davon, ob diese mit ihren jeweiligen Standardwerten belegt sind. Schauen wir uns das mal aus der Nähe an.

Eine Komponente kann die Code Generierung unter anderem durch die Attribute DefaultValue und Localizable steuern. Während Erstere den Standardwert einer Eigenschaft angibt, bestimmt Letztere, ob diese lokalisierbar ist. Das folgende Beispiel verdeutlicht dies:

public class MyControl : Control
{
    private string m_myData = string.Empty;

    
[DefaultValue("")]
    [Localizable(true)]

    public string MyData
    {
        get { return m_myData; }
        set { m_myData = value; }
    }
}

Das DefaultValue-Attribut gibt hierbei den Standardwert der Eigenschaft an. Auf diese Weise wird nur dann Code erzeugt, wenn die jeweilige Instanz des Controls einen abweichenden Wert für die MyData-Eigenschaft enthält. Zusätzlich wurde die Eigenschaft mit dem Localizable-Attribut versehen. Dies bewirkt, dass im Falle einer lokalisierten Form, der Wert der Eigenschaft aus den Ressourcen ermittelt wird.

Wenn Sie dieses Control nun auf eine nicht lokalisierte Form ziehen und die MyData-Eigenschaft ändern, wird der folgende Code in der InitializeComponent()-Methode erzeugt:

this.myControl1.MyData = "123";

Enthält die Eigenschaft jedoch den Standardwert, so wird eine explizite Zuweisung des Wertes überflüssig und daher auch kein Code erzeugt.

Handelt es sich jedoch um eine lokaliserte Form, so findet diese Prüfung nicht statt, sondern es wird in jedem Fall Code nach folgendendem Stickmuster erzeugt:

System.Resources.ResourceManager resources = new System.Resources.ResourceManager(typeof(Form1));

this.myControl1.MyData = resources.GetString("myControl1.MyData");

Dieses Verhalten wurde so implementiert, da es sein kann, dass eine Eigenschaft in einer Sprache gesetzt und in einer anderen Sprache nicht gesetzt sein kann. Dies ergibt zwar durchaus Sinn, führt jedoch zu einem weitaus höheren Code-Aufkommen. Zudem müssen alle Werte stets aus den Ressourcen ermittelt werden, was weitaus langsamer ist, als würden sie direkt zugewiesen. In der Praxis ist es jedoch so, dass eine Eigenschaft meist in allen Sprachen gesetzt wird und somit die Kosten den Nutzen nicht aufwiegen.

Daher wurde das Lokalisierungskonzept in .NET 2.0 grundlegend geändert. So besteht das Prinzip des Localizable-Attributs zwar fort, der generierte Code ist nun jedoch ein anderer.

System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Form1));

resources.ApplyResources(this.myControl1, "myControl1");
this.myControl1.Name = "myControl1";

Wie Sie sehen, wurde hierbei lediglich für die Name-Eigenschaft Code erzeugt. Alle weiteren lokalisierbaren Eigenschaften werden über die ApplyResources()-Methode der ComponentResourceManager-Klasse zugewiesen. Während der Code-Generierung werden die entsprechenden Werte in der Ressource-Datei gespeichert und hierbei die Standardwerte berücksichtigt. Zur Laufzeit analysiert ApplyResources() nun per Reflection die Komponente und liest die Werte aus den jeweiligen Sprach-Ressourcen.

Wieder so eine kleine Änderung, die in komplexen Projekten jedoch einen erheblichen Performance-Vorteil bringt.

Montag, September 25, 2006

Austausch von Daten zwischen Stored Procedures

Daten über eine Stored Procedure zu ermitteln und auszugeben ist ja relativ einfach. Schwieriger wird es jedoch, wenn Daten zwischen verschiedenen Prozeduren ausgetauscht werden sollen.

Hierfür gibt es zwei Lösungswege:

  • Austausch über einen Cursor
  • Verwendung einer TABLE-Variable

Austausch über einen Cursor

Hierbei deklariert die aufgerufene Prozedur einen Cursor als OUTPUT-Parameter.

Das folgende Beispiel demonstriert die Vorgehensweise:

CREATE PROCEDURE CursorPublisher
    @Cursor CURSOR VARYING OUTPUT
AS
    SET @Cursor = CURSOR STATIC FOR
    SELECT FirstName, LastName FROM Person.Contact

    OPEN @Cursor
GO

Hierbei wird der Cursor durch die Prozedur befüllt und geöffnet.

Der Aufrufer definiert nun einen Cursor gleichen Aufbaus übergibt ihn der Prozedur als Parameter. Im Anschluß kann das Ergebnis in einer Schleife durchlaufen und die Werte ausgegeben werden.

CREATE PROCEDURE CursorConsumer AS
    DECLARE @Cursor CURSOR
    DECLARE @FirstName nvarchar(50),
            @LastName nvarchar(50)

    SET NOCOUNT ON

    EXEC CursorPublisher @Cursor OUTPUT

    WHILE (@@FETCH_STATUS = 0)
    BEGIN
        FETCH NEXT FROM @Cursor INTO @FirstName, @LastName
        PRINT 'FirstName=' + @FirstName
        PRINT 'LastName=' + @LastName
    END

    DEALLOCATE @Cursor
GO

Verwendung einer TABLE-Variable

Es gibt jedoch eine wesentlich einfachere und effizientere Methode das gleiche zu erreichen. Hierbei gibt die aufgerufene Prozedur die Ergebnisse in Form eines normalen Resultsets zurück:

CREATE PROCEDURE ResultsetPublisher
AS
    SELECT FirstName, LastName FROM Person.Contact
GO

Der Aufrufer definiert nun anstelle eines Cursors eine TABLE-Variable, die das Ergebnis aufnimmt.

CREATE PROCEDURE ResultsetConsumer
AS
    DECLARE @Resultset TABLE
    (
        FirstName nvarchar(50),
        LastName nvarchar(50)
    )

    SET NOCOUNT ON

    INSERT INTO @Resultset
    EXEC ResultsetPublisher

    SELECT * FROM @Resultset
GO

Das Befüllen der Variable wird hierbei über INSERT INTO in Kombination mit EXEC vollzogen.

Neben der leichteren Implementierung, hat diese Variante noch weitere Vorteile:

  • Das Ergebnis kann mit anderen Tabellen, Views, Funktionen oder TABLE-Variablen verknüpft werden
  • Der Zugriff ist schneller, da hierbei mengen- und nicht zeilenorientiert gearbeitet wird
  • Die Prozedur kann auch ohne weiteres von Client-Anwendungen aufgerufen werden

Aber entscheiden Sie selbst!

Slides und Samples von der BASTA!

Die BASTA! war auch diesmal wieder richtig klasse! Die Slides und Samples meiner Vorträge finden Sie hier:

Component Development mit .NET 2.0: Slides und Samples
SQL Server 2005 als Applikationsserver (zusammen mit Marcel Gnoth): Slides / Samples