Freitag, 7. August 2015

UG Göttingen: Nach der Sommerpause schauen wir uns mal Roslyn an

Nach der Sommerpause im August, treffen wir uns ab September wieder. Dann wird es auch ausnahmsweise nicht am ersten Donnerstag im Monat sondern am 10. September 2015 stattfinden.
Nähere Informationen gibt es wie immer auf der Homepage der UG.

Mittwoch, 25. Juni 2014

Automatische Kommunikation zur Datenbank via Service Broker

(Automatically communication to a database via Service Broker)
Es gibt viele verschiedene Arten aus einem .Net-Programm auf eine Datenbank zuzugreifen und Daten auszulesen. Möchte man jedoch auf inhaltliche Änderungen einer Tabelle (z. B. Schnittstellentabelle) zeitnah reagieren, sollte man sich schon vorher ein paar Gedanken über die Abfragemethodik machen, um überflüssige Last zu vermeiden.
Die einfachste Möglichkeit Änderungen mitzubekommen, wäre wohl ein Timer, welcher in regelmäßigen Intervallen die Tabelle abfragt. Um hier aber zeitnah reagieren zu können, müsste der Intervall entsprechend kurz eingestellt werden. Dieses wiederum hätte wahrscheinlich viele unnötige Abfragen zur Folge, die folglich auch wieder Last auf dem SQL-Server verursachen.
Um dieses Verhalten zu umgehen, könnte man den Weg über den sogenannten Service Broker gehen, der im SQL-Server (im Datenbankmodul) integriert ist. Der Service Broker wird für komplette Datenbank aktiviert. In der Programmierung steuert man dann gezielt die Tabellen an. Der Service Broker dient zur asynchronen Kommunikation zwischen SQL-Server und externen Anwendung. Außerdem arbeitet der Service Broker mit Warteschlangen, so dass generell keine Informationen verloren gehen sollten.
Zum Aktivieren des Service Brokers (im Management Studio) in der Datenbank muss man wie folgt vorgehen:
- Öffnen der Datenbank-Eigenschaften (Rechtsklick auf Datenbank => Eigenschaften/Properties)
- Auswählen von Optionen/Options
- Die Option “Broker aktivieren” / “Broker enabled” auf True setzen

ServiceBroker

Mit der Aktivierung des Service Brokers funktioniert die Kommunikation zur Datenbank generell.
Nun kann dieser auch direkt im .Net angesprochen werden. Hierfür gibt es die Klasse SqlDependency.
Hier braucht man quasi nur die Verbindung starten und ein EventHandler hinterlegen, wo die Verarbeitung der Änderungen definiert ist.
public void StartWatcher()
{
    String connectionString = "Server=(local);Database=Playground;Trusted_Connection=True;";
    SqlDependency.Start(connectionString);
    ReadData();
}


private void ReadData()
{
    DataTable data = GetNewData();

    if (data != null && data.Rows.Count > 0)
    {
        ChangeEventArgs daten = new ChangeEventArgs(data.Rows[0]);
        ChangeEventHandler(this, daten);
    }
}


public DataTable GetNewData()
{
    DataTable dt = new DataTable();
    
    using (SqlConnection conn = new SqlConnection(ConnectionString))
    {
        conn.Open();

        SqlCommand cmd = conn.CreateCommand();
                
        cmd.CommandText = SELECT ID, Name, DescriptionShort FROM dbo.Product WHERE State = @State";
        cmd.Notification = null;

        cmd.Parameters.Add(new SqlParameter("@State", "new"));

        dep = new SqlDependency(cmd);
        dep.OnChange += new OnChangeEventHandler(dep_OnChange);

        SqlDataAdapter adapter = new SqlDataAdapter();
        adapter.SelectCommand = cmd;
        adapter.Fill(dt);

        conn.Close();
    }

    return dt;
}


public class ChangeEventArgs : EventArgs
{
    public readonly DataRow data;

    public ChangeEventArgs(DataRow data)
    {
        this.data = data;
    }
}


void dep_OnChange(object sender, SqlNotificationEventArgs e)
{
    ReadData();
}


Im gezeigten Beispiel wird nun also direkt die Verarbeitung des Handlers ausgeführt, sobald ein neuer Datensatz mit dem Status “new” in die Tabelle Product eingefügt wird.

Für die Abfragen in Verbindung mit dem Service Broker gibt es ein paar kleinere Spielregeln zu beachten, welche im MSDN zu finden sind. Eine wichtige Regel, über die ich gestolpert bin ist, dass die Abfragen z.B. SELECTS nicht mit Platzhaltern wie * verwendet werden dürfen. Hier muss man gezielt Spalten angeben, die abgefragt/eingelesen werden sollen.

Dienstag, 24. Juni 2014

UG Göttingen: Wir gehen in die Sommerpause!

Nach interner Abstimmung haben wir uns dafür entschieden in eine verlängerte Sommerpause zu gehen und die kommenden beiden Monate zu pausieren. Damit steht fest, dass in den Monaten Juli und August kein UserGroup Treffen stattfindet.

Nähere Informationen zum nächsten Treffen sind wie immer auf der UG-Webseite zu finden.
http://dotnetgoettingen.de/

Bei Entzugserscheinungen gibt es die Ausweichmöglichkeit ein oder zwei schöne Abende bei der UG Braunschweig zu verbringen.
Da ich aber noch nicht weiß, ob es hier auch eine Sommerpause gibt, sollte man sich vielleicht vorher noch einmal informieren.
Nähere Information entweder direkt bei Lars Keller erfragen oder auf der Homepage nachschauen.
http://www.dotnet-braunschweig.de/

Donnerstag, 12. Juni 2014

Deaktivieren und Aktivieren von Indizes per SQL-Script

(SQL-Server: Disable or Enable Index per SQL-Script)

Manchmal ist es sinnvoll vor bestimmten Operationen (zum Beispiel Massenverarbeitungen per Bulk Insert oder Bulk Copy) Indizes auszublenden.Eine Möglichkeit wäre hier das Löschen und Erstellen des betroffenen Index. Sollte hierbei aber ein Fehler auftreten und man arbeitet nicht in einer Transaktion, dann kann es passieren, dass der Index komplett gelöscht bleibt.

USE [Playground]
GO

-- Drop the Index "ProductIndex1"
DROP INDEX [ProductIndex1] ON [Product]
GO

-- Create the Index "ProductIndex1"
CREATE NONCLUSTERED INDEX [ProductIndex1] ON [Product]
(
[ID] ASC
)
WITH
(
PAD_INDEX = OFF,
STATISTICS_NORECOMPUTE = OFF,
SORT_IN_TEMPDB = OFF,
DROP_EXISTING = OFF,
ONLINE = OFF,
ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON
) ON [PRIMARY]
GO


Meiner Meinung nach ist die schönere Variante, das Deaktivieren mit anschließendem Reaktivieren bzw. Neu-Aufbau.


USE [Playground]
GO

-- Diable the Index "ProductIndex1"
ALTER INDEX [ProductIndex1] ON [Product] DISABLE
GO

-- Enable and Rebuild the Index "ProductIndex1"
ALTER INDEX [ProductIndex1] ON [Product] REBUILD
GO

Donnerstag, 5. Juni 2014

SQL-Server: Neue Spalte an bestimmter Position in Tabelle einfügen

(SQL-Server: Insert column at a special position in a table)

In seltenen Fällen kann es doch mal notwendig sein, dass man eine neue Spalte an einer bestimmten Position in einer Tabelle einfügen und diese nicht einfach am Ende anfügen will. Leider gibt es es hierfür keine “einfache” Funktion. Über das SQL Server Management Studio (SSMS) gibt es zwar diese Möglichkeit, jedoch geht es nur, wenn die betroffene Tabelle noch nicht gefüllt ist oder gelöscht werden darf.

Wie geht das Management Studio aber (grob gesagt) intern vor?
1. Die betroffene Tabelle wird mit neuen Spalten als temporäre Tabelle erstellt.
2. Die Inhalte werden aus der originalen Tabelle in die temporäre Tabelle kopiert.
3. Die originale Tabelle wird gelöscht.
4. Die temporäre Tabelle wird umbenannt und als permanente Tabelle bereitgestellt.

Genau nach dieser Vorgehensweise habe ich nun ein SQL-Skript geschrieben, welches eine neue Spalte an einer beliebigen Position einer Tabelle einfügt und dabei keine Daten verliert.

USE [Spielwiese]
GO

SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

BEGIN TRY

BEGIN TRAN TransactionInsertColumn;

-- Create a new table with the additional column (DescriptionShort)
CREATE TABLE [dbo].[Product_NEW] (
[ID] INT IDENTITY (1, 1) NOT NULL,
[Name] VARCHAR (50) NOT NULL,
[DescriptionShort] VARCHAR (50) NULL,
[Description] VARCHAR (255) NULL
);

SET IDENTITY_INSERT [Product_NEW] ON;

-- Copy all data from the original table to the new (extended) one
INSERT INTO [Product_NEW]
([ID]
,[Name]
,[DescriptionShort] -- This is the new inserted column
,[Description]
)
SELECT [ID]
,[Name]
,NULL -- Content of the new column is NULL (or can set here)
,[Description]
FROM [Product]

SET IDENTITY_INSERT [Product_NEW] OFF;

-- Rename the original table to _OLD
EXEC sp_rename 'Product', 'Product_OLD';

-- Rename the new table to the original name
EXEC sp_rename 'Product_NEW', 'Product';

-- Drop the old table
DROP TABLE Product_OLD;

-- Optional: Create index here
CREATE NONCLUSTERED INDEX [ProductIndex1]
ON [Product]([Id] ASC);

-- Optional: Create primary key here
ALTER TABLE [Product]
ADD CONSTRAINT [Product_PK] PRIMARY KEY CLUSTERED ([ID] ASC);

-- Optional: Create foreign keys here
--ALTER TABLE [Product]
-- ADD CONSTRAINT [FKProductHeaderID] FOREIGN KEY ([FKProductHeader]) REFERENCES [ProductHeader] ([ProductID]);

-- NOTE:
-- All rights (grant/deny) are lost and have to set new
--GRANT SELECT ON [Product] TO "RoleReadOnly";
--GRANT SELECT ON [Product] TO "RoleReadWrite";

COMMIT TRAN TransactionInsertColumn;
END TRY
BEGIN CATCH
SELECT ERROR_MESSAGE(), ERROR_NUMBER() -- Show the last error message if the transaction was aborted
ROLLBACK TRAN TransactionInsertColumn
END CATCH

Donnerstag, 27. März 2014

Ausgeführte SQL-Statements ermitteln bzw. loggen

In speziellen Fällen ist es manchmal sinnvoll, wenn man gezielt Datenbankabfragen protokolliert.
Eine einfache Möglichkeit wäre ein Tabellen-Trigger, in dem das ausgeführte SQL-Statement ermittelt und beispielsweise in eine eigene Log-Tabelle geschrieben wird.

Ein Trigger, der als Ergebnis das ausgeführte Statement zurückgibt, könnte wie folgt erstellt werden.

CREATE TRIGGER GetSqlStatement
ON [dbo].[Produkte]
AFTER INSERT, UPDATE, DELETE
AS
BEGIN
DECLARE @TEMP TABLE
(EventType NVARCHAR(30), Parameters INT, EventInfo NVARCHAR(4000))
INSERT INTO @TEMP EXEC('DBCC INPUTBUFFER(@@SPID)')
SELECT EventInfo FROM @TEMP
END
GO


Wenn man nun ein DML-Statement wie z.B. ein Insert auf die entsprechende Tabelle absetzt, wird der Trigger ausgeführt und das gerade ausgeführte Statement zurückgegeben.

GetStatement


So hätte man die Möglichkeit direkt im Trigger das Statement (EventInfo) wie gewünscht zu protokollieren. Es würden so jegliche inhaltliche Änderungen der Daten gespeichert werden.