Tuesday, March 11, 2008

Virtual, new and override - the differences

Auf die Frage was ist der Unterschied zwischen new und override, im konkreten Fall für ein Property verwendet, bekommt man stehts die korrekte Antwort. New versteckt das Property in der Basisklasse, override überschreibt dieses. Gut, aber was heisst das genau? Nun werden die sinnvollen Antworten doch eher rare.

Folgendes Beispiel zeigt die Funktionsweisen auf. Parent ist die Basisklasse, die zwei Properties Foo und Bar anbietet. Child ist eine Klasse die von Parent ableitet und die beiden Properties einmal mit new und einmal mit override überschreibt. In der Klasse MyClass werden zwei Instanzen von Child erzeugt, wobei im ersten Fall die Deklaration auf die Basisklasse Parent lautet. Die beiden Properties der beiden Instancen werden abgefragt und ausgedruckt.

public class MyClass
{
public static void Main()
{
Parent p = new Child();
Child c = new Child();
Console.WriteLine(p.Foo);
Console.WriteLine(p.Bar);
Console.WriteLine(c.Foo);
Console.WriteLine(c.Bar);
Console.ReadLine();
}
}
public class Parent
{
public string Foo {get{return "ParentFoo";}}
public virtual string Bar {get{return "ParentBar";}}
}
public class Child : Parent
{
public new string Foo {get{return "ChildFoo";}}
public override string Bar {get{return "ChildBar";}}
}

Das aufschlussreiche Ergebniss sieht so aus:
ParentFoo
ChildBar
ChildFoo
ChildBar

Da zwei Instancen von Child erzeugt wurden ist der Ausdruck ParentFoo doch für den einen oder anderen erstaunlich. Wie kommt das? Die Antwort liegt im Early- und Late Binding.

Im Fall von Foo wurde das Property auf der Parent Klasse nicht mit virtual deklariert. Somit wird das Early Binding verwendet. Das heisst, der Compiler legt zur Kompilierzeit fest welches Property aufgerufen wird. Da im ersten Fall die Variable als Parent deklariert ist, wird in jedem Fall (egal was für eine Instanz effektiv vorhanden ist) das Property Foo der Klasse Parent aufgerufen und im zweiten Fall das Property der Klasse Child.

Im Fall von Bar wurde das Property auf der Basisklasse mit virtual deklariert und somit das Early Binding verhindert. Das heisst, das zur Laufzeit die Instance analysiert wird und entsprechend das Property der effektiv vorhandenen Klasse (in unserem Fall Child) aufgerufen wird.

Und zum Schluss noch einen Blick auf den IL Code:

.method public hidebysig static void Main() cil managed
{
.entrypoint
.maxstack 1
.locals init (
[0] class Parent parent,
[1] class Child child)
L_0000: newobj instance void Child::.ctor()
L_0005: stloc.0
L_0006: newobj instance void Child::.ctor()
L_000b: stloc.1
L_000c: ldloc.0
L_000d: callvirt instance string Parent::get_Foo()
L_0012: call void [mscorlib]System.Console::WriteLine(string)
L_0017: ldloc.0
L_0018: callvirt instance string Parent::get_Bar()
L_001d: call void [mscorlib]System.Console::WriteLine(string)
L_0022: ldloc.1
L_0023: callvirt instance string Child::get_Foo()
L_0028: call void [mscorlib]System.Console::WriteLine(string)
L_002d: ldloc.1
L_002e: callvirt instance string Parent::get_Bar()
L_0033: call void [mscorlib]System.Console::WriteLine(string)
L_0038: call string [mscorlib]System.Console::ReadLine()
L_003d: pop
L_003e: ret
}

Würde man nicht erwarten, dass beim Early Binding ein call und nicht ein callvirt aufgerufen wird? Eigentlich schon. Das callvirt wird generiert, damit die Klasse auf null geprüft werden kann. Der JIT Compiler wird aber nach der null Prüfung diesen Aufruf effektiv in einen non-virtual Aufruf umsetzen.

Wednesday, January 02, 2008

Mehrere Tabellen auf eine Entität mappen

Meistens sind Daten in einer Datenbank normalisiert abgelegt. In der Applikation möchte man aber oft mit den nicht normalisierten Daten arbeiten. Ist das ADO.NET Entity Framework die Lösung?

Jein lautet die Antwort. Und um es vorne weg zu nehmen, ein Mapping von mehreren Tabellen auf eine Entität ist möglich, aber nur wenn diese eine 1:1 Beziehung haben. Das heisst, in den meisten Fällen wird man besser fahren, wenn auf der Datenbank eine geeignete View erstellt, diese auf die Entität gemapped und das Update, Insert und Delete über Stored Procedures gelöst wird.

Nun aber zur Lösung. Zuerst wird ein Model mit dem Assistenten erstellt, das alle gewünschten Tabellen enthält.

Die Anpassungen am Conceptual Model (CSDL) sind einfach. Auf der Entität werden die gewünschten Attribute aus den weiteren Tabellen hinzugefügt. So wurde etwa hier die Entität Cars um das Attribut Value erweitert.

<?xml:namespace prefix = edmx /><edmx:conceptualmodels>
<schema xmlns="http://schemas.microsoft.com/ado/2006/04/edm" alias="Self" namespace="Model">
<entitycontainer name="Entities">
<entityset name="Cars" entitytype="Model2.Cars">
</entitycontainer>
<entitytype name="Cars">
<key>
<propertyref name="ID_Cars">
</key>
<property name="ID_Cars" nullable="false" type="Guid"></property>
<property name="Brand" type="String" maxlength="50"></property>
<property name="Model" type="String" maxlength="50"></property>
<property name="Value" type="String" maxlength="50"></property>
</entitytype>
</schema>
</edmx:conceptualmodels>

Anpassungen am Storage Model (SSDL) sind keine notwendig, da sich an der Datenbank, welche durch dieses Model repräsentiert wird, nichts ändert.

Durch Anpassung des Mappings werden die neu definierten Attribute auf der Entität nun auf die im Storage Model vorhandenen gemapped. Dies wird realisiert durch Hinzufügen eines zusätzlichen Mapping Fragment.

<edmx:mappings>
<mapping xmlns="urn:schemas-microsoft-com:windows:storage:mapping:CS" space="C-S">
<entitycontainermapping cdmentitycontainer="Entities" storageentitycontainer="dbo">
<entitysetmapping name="Cars">
<entitytypemapping typename="IsTypeOf(Model.Cars)">
<mappingfragment storeentityset="Cars">
<scalarproperty name="ID_Cars" columnname="ID_Cars">
<scalarproperty name="Brand" columnname="Brand">
<scalarproperty name="Model" columnname="Model">
</mappingfragment>
<mappingfragment storeentityset="Extensions">
<scalarproperty name="ID_Cars" columnname="ID_Extensions">
<scalarproperty name="Value" columnname="Value">
</mappingfragment>

</entitytypemapping>
</entitysetmapping>
</entitycontainermapping>
</mapping>
</edmx:mappings>

Nicht mehr benötigter Code wie EntitySets und AssociationSets sind im CSDL und dem Mapping zu löschen.