Thursday, October 25, 2007

Output Parameter in dynamischem T-SQL

In einer Stored Procedure werden in einem ersten Schritt zwei Variablen gesetzt, die für die weitere Verarbeitung benötigt werden. Dies könnte so aussehen:

SELECT @var1 = Var1, @var2 = Var2
FROM MyTable
WHERE ID = @ID

Nun muss das Statement aber dynamisch zusammengestellt werden und der Select-String danach ausgeführt werden. Dies könnte so aussehen:

SET @query = '
SELECT @var1 = Var1, @var2 = Var2
FROM MyTable
WHERE ID = ' + CAST(@ID as CHAR(15))
EXEC (@query)

In meinem Fall wird das Statement auf einem Hostsystem ausgeführt und der Aufruf muss über Openquery gemacht werden. Dies könnte so aussehen:

SET @query = '
SELECT @var1 = Var1, @var2 = Var2
FROM OPENQUERY(MySystem, ''SELECT Var1, Var2 FROM MyTable WHERE ID = '
+ dbo.MyTypeConverter(@id) + ''')'
EXEC (@query)

Ganz klar, dass die beiden Variablen so nicht mehr abgefüllt werden, sondern zu einem Fehler führen. Wie können diese gesetzt werden?
Gemäss einer ersten Idee wurde eine Variable vom Typ TABLE verwendet und das Openquery-Resultat mit INSERT INTO angefügt. Danach das erste Select-Statement verwendet um die Variablen zu setzten. Funktioniert! Ist aber nicht sehr elegant.
Hübscher ist's so:

SET @params = ' @var1 int OUTPUT, @var2 nvarchar(max) OUTPUT'
SET @query = '
SELECT @var1Out = Var1, @var2Out = Var2
FROM OPENQUERY(MySystem, SELECT Var1, Var2 FROM MyTable '
+ dbo.MyTypeConverter(@id) + ''')'
EXEC sp_executesql @query, @params, @var1Out = @var1 OUTPUT, @var2Out = @var2 OUTPUT

Dieser Ansatz mit der System Procedure sp_executesql bietet zudem noch weitere Vorteile die in der SQL Server Hilfe nachgelesen werden können.

Friday, October 05, 2007

Dynamic Method Performance

In einem interessanten Artikel im MSDN Magazine wird auf unterschiedliche Performance Probleme, im Speziellen im Zusammenhang mit Reflection, eingegangen. Mich interessierte vor allem die gelobte Performance der dynamischen Methoden, die seit der .NET Framework Version 2.0 zur Verfügung stehen. Eine einfache Testanwendung hat gezeigt, dass der Artikel recht behält.
In der Testanwendung wird einem Objekt ein String eine Million mal zugewiesen. Dies zuerst direkt verdrahtet, danach mit dynamischer Methode und zuletzt via Reflection. Das Ergebnis ist eindeutig. Die direkt verdrahtete Methode ist natürlich die Schnellste, die dynamische Methode braucht dafür doppelt so lange (was ich immer noch sehr gut finde) und mittels Reflection dauert es 100mal länger. What a gain!
Übrigens, die Assembly habe ich mit Lutz's Reflector untersucht, um sicher zu stellen, dass die Schlaufe auch wirklich bei allen drei Varianten ausgeführt wird.

Und hier noch der Code:

public class Program
{
private delegate void DemoDelegate(Foo arg);

static void Main(string[] args)
{
Stopwatch stopwatch = new Stopwatch();

Foo f = new Foo();
stopwatch.Start();
for (int i = 0; i < 1000000; i++)
{
f.Text = "Hello World!";
}
stopwatch.Stop();
Console.WriteLine("Direct: {0}", stopwatch.Elapsed);

stopwatch.Reset();

DemoDelegate del = createMethod();
stopwatch.Start();
for (int i = 0; i < 1000000; i++)
{
del(f);
}
stopwatch.Stop();

Console.WriteLine("Dynamic method: {0}", stopwatch.Elapsed);

stopwatch.Reset();

PropertyInfo property = f.GetType().GetProperty("Text");
stopwatch.Start();
for (int i = 0; i < 1000000; i++)
{
property.SetValue(f, "Hello World!", null);
}
stopwatch.Stop();
Console.WriteLine("Reflection: {0}", stopwatch.Elapsed);


Console.ReadKey();
}

private static DemoDelegate createMethod()
{
Type[] parameterTypes = new Type[] { typeof(Foo) };
DynamicMethod method = new DynamicMethod("Demo", null, parameterTypes, typeof(Program));

ILGenerator iLGenerator = method.GetILGenerator();
iLGenerator.Emit(OpCodes.Ldarg_0);
iLGenerator.Emit(OpCodes.Ldstr, "Hello World!");
MethodInfo setMethod = typeof(Foo).GetProperty("Text").GetSetMethod();
iLGenerator.EmitCall(OpCodes.Call, setMethod, null);
iLGenerator.Emit(OpCodes.Ret);

return (DemoDelegate)method.CreateDelegate(typeof(DemoDelegate));
}
}

public class Foo
{
private string _text;

public string Text
{
get { return _text; }
set { _text = value; }
}
}