Zur Zeit wird gefiltert nach: May 1
Filter zurücksetzen

27.05.2010
22:33

SQL Server Datum gem. ISO 8601 parsen

Ich hatte ein kleines Problem, dass sehr schnell Zeit bei der Fehlersuche beanspruchen kann. Das Problem bezog sich bei mir auf einen ETL-Job. Eine Datenquelle liefert nur das Jahr und den Monat. Hiermit müssen schon beim Laden kleine Differenz-Berechnungen gemacht werden. Bevor ich hier eigene Logik entwickle, kam mir die DATEADD-Funktion in den Sinn. Diese Funktion benötigt aber ein Datum.

Je nach Umgebung kann das Datum aus einem String wie zum Beispiel '23.05.2010' im Format 'DD.MM.YYYY' ermittelt werden (DIN 5008), ändert sich aber die Umgebung funktioniert dies nicht mehr. Ein möglicher Ansatz wäre der Tausch von Monat und Tag, damit es wieder funktioniert. Das eigentliche Problem ist damit jedoch nicht behoben, denn wenn im Hintergrund des Ladejobs ein Configuration Repository arbeitet, ist es eine Frage der Zeit, bis ein Fehler auftritt.

T-SQL
DECLARE @Year int,
	@Month int
SET @Year = 2010
SET @Month = 5

SET LANGUAGE GERMAN
PRINT CONVERT(DATETIME,'23.' +  STR(@Month) + '.' + STR(@YEAR))

SET LANGUAGE ENGLISH
-- Fehler out-of-range
PRINT CONVERT(DATETIME,'23.' +  STR(@Month) + '.' + STR(@YEAR))

-- Quick and Dirty Korrektur
PRINT CONVERT(DATETIME, STR(@Month) + '.' + '23.' + STR(@YEAR))

Ausgabe Die Spracheneinstellung wurde auf Deutsch geändert. Mai 23 2010 12:00AM
Changed language setting to us_english. -- Fehler out-of-range Meldung 242, Ebene 16, Status 3, Zeile 11 The conversion of a char data type to a datetime data type resulted in an out-of-range datetime value.
-- Quick and Dirty Korrektur May 23 2010 12:00AM

Da es umgebungstechnisch mit dem Datum immer ein Problem darstellt, ist die Verwendung des ODBC - Datumsformat 'YYYY-MM-DD 00:00:00.000' empfehlenswert, da es auch der Norm ISO 8601 entspricht. Beim ersten Versuch funktioniert es auch wie erwartet.

T-SQL
DECLARE @Year int,
	@Month int
SET @Year = 2010
SET @Month = 5

SET LANGUAGE ENGLISH
PRINT CONVERT(DATETIME, STR(@Year) + '-' + STR(@Month) + '-1')


Ausgabe Changed language setting to us_english. May 1 2010 12:00AM

Ändert sich jedoch die Umgebung, wird das Datum nicht korrekt wieder gegeben. Damit dieses Problem definitiv behoben werden kann, ist die explizite Angabe beim Konvertieren erforderlich. Für das ODBC-Datum inkl. Millisekunden ist es der Ausdruck 121.

T-SQL
DECLARE @Year int,
	@Month int
SET @Year = 2010
SET @Month = 5

SET LANGUAGE ENGLISH
PRINT CONVERT(DATETIME, STR(@Year) + '-' + STR(@Month) + '-1')

SET LANGUAGE GERMAN
PRINT 'Fehlerhafte Interpretation'
PRINT CONVERT(DATETIME, STR(@Year) + '-' + STR(@Month) + '-1')

PRINT 'PARSE ODBC kanonisch (mit Millisekunden)'
SET LANGUAGE GERMAN
PRINT CONVERT(DATETIME, STR(@Year) + '-' + STR(@Month) + '-1', 121)

SET LANGUAGE ENGLISH
PRINT CONVERT(DATETIME, STR(@Year) + '-' + STR(@Month) + '-1', 121)

Ausgabe Changed language setting to us_english. May 1 2010 12:00AM Die Spracheneinstellung wurde auf Deutsch geändert. Fehlerhafte Interpretation Jan 5 2010 12:00AM PARSE ODBC kanonisch (mit Millisekunden) Die Spracheneinstellung wurde auf Deutsch geändert. Mai 1 2010 12:00AM Changed language setting to us_english. May 1 2010 12:00AM

Entity Framework 4 - SQL-Verbesserungen

Die generierten SQL-Statements in EF1 sind häufig ein Reizthema. Das Problem mit dem IN-Statement habe ich bereits erwähnt, aber das allein ist nicht das Problem. Ein häufiges Beispiel für Optimierungen sind die kompilierten Abfragen, die aber auf das generierte SQL keinen Einfluss haben. Was zum Teil für unterhaltsame Abfragen im EF1 generiert werden, ist in diesem Blogbeitrag ersichtlich.

Visual Studio 2010 Erweiterung für Item Templates (Vorlagen)

Mit Visual Studio kommt neu der Erweiterungsmanager hinzu. Mit diesen soll die Erweiterung der Entwicklungsumgebung noch einfacher gehen. Da ich eine angepasste Vorlage per Hand in den Zielordner platziert habe, hat es mich natürlich interessiert, wie dies mit dem Erweiterungsmanager realisiert werden kann. Zuerst wird die Visual Studio SDK benötigt. Diese steht hier zum Download bereit. Nach der Installation steht ein neuer Auswahlpunkt "Extensibility" in den installierten Vorlagen zur Verfügung.

Abbildung 1 Auswahl Projekt für eine Visual Studio Erweiterung
Abbildung 1

Wenn das Projekt angelegt ist, wird die Manifestdatei angezeigt. Hier sind die Angaben zum Produktnamen, dem Autor und der Version wichtige Informationen.

Abbildung 2 Manifestdatei der Visual Studio Extension
Abbildung 2

Im Content Bereich wird es interessant, wenn die Vorlagen mittels Erweiterungsmanager zur Verfügung gestellt werden sollen. Als Inhaltstyp kann Vorlage ausgewählt und die Quelle dieser Vorlage gewählt werden. Bei der Angabe von Unterordnern wird eine Struktur angelegt, die nach der Installation bei den installierten Vorlagen ersichtlich ist.

Abbildung 3 Vorlage dem Projekt hinzufügen
Abbildung 3

Was hierbei zu beachten ist, die Datei wird in das Projekt kopiert. Das kann sich als Fallstrick bei Änderungen erweisen. Die hinzugefügte Vorlage erscheint anschliessend im Inhaltsbereich der Manifestdatei.

Abbildung 4 Übersicht der Vorlagen im Content-Bereich
Abbildung 4

Nach diesen Angaben kann das Projekt erstellt werden und im Ausgabeverzeichnis steht eine *.vsix-Datei zur Verfügung. Bis hier ist eigentlich alles ohne grosse Probleme verlaufen. Nach der Installation der Erweiterung wird diese auch im Erweiterungsmanager angezeigt.

Abbildung 5 Erweiterung im Erweiterungsmanager
Abbildung 5

Also kommt jetzt die Überprüfung, ob die Vorlagen zur Verfügung stehen. Füge ich ein neues Element hinzu, dann gibt es einen neuen Punkt "databinding" und dort steht auch die Klassenvorlage zur Auswahl.

Abbildung 6 Neuer Auswahlpunkt databinding
Abbildung 6

Was aber nicht zur Verfügung steht, ist die angepasste Vorlage für die Selbstnachverfolgung von Entitäten. Also überprüfe ich, ob diese korrekt installiert wurde. Dazu suche ich den Installationsorder. Interessanterweise findet man diesem unter dem Pfad "C:\Users\Username\AppData\Local\Microsoft\VisualStudio\10.0\Extensions\rld\EntityFrameworkSelfTrackingEntitiesGerman\1.0\ItemTemplates\databinding" das Verzeichnis ef und die passende Vorlage. Es wurde also korrekt installiert.

Mein nächster Trick ist der alte Weg. Ich kopiere die Datei in das Vorlagenverzeichnis im Dokumentenbereich "C:\Users\Username\Documents\Visual Studio 2010\Templates\ItemTemplates\databinding" Hier lege ich die gleiche Struktur an, also "ef" und kopiere die Self Tracking-Vorlage hinein.

Abbildung 7 Kopiervorgang in die alte Vorlagenumgebung
Abbildung 7

Nach dieser Aktion steht im Punkt "databinding" der Punkt "ef" zur Verfügung und darin die T4-Vorlage für Entitäten mit Selbstnachverfolgung.

Abbildung 8 Sichtbare T4-Vorlage nach umkopieren in die alte Vorlagenumgebung
Abbildung 8

Was an der Sache einerseits interessant wirkt, ist das Zusammenfügen des Ordners "databinding" der 2 Verzeichnisse. Andererseits ist auch hier Murphy unterwegs, denn die T4-Vorlage wird über den Deploy-Vorgang der Erweiterung nicht dargestellt. Mal sehen, ob ich zu diesem Problem etwas finde.

Entity Framework 4 – Eigene Codegenerierungsvorlagen erstellen

Nachdem ich in der deutschen Version von Visual Studio die Verbesserungen des Entity Framework 4 teste und das erste Mal bei den ADO.NET-Entitäts-Generator mit Selbstnachverfolgung (Self Tracking Entities) wegen einem Fehler hängen geblieben bin, hat es mich interessiert, wie ich eigene Vorlagen erstellen kann, die T4-Vorlagen für die Generierung enthalten. Das Problem in der deutschen Version sind die lokalisierten Ressourcen, die Anführungsstriche enthalten, der Fehler ist mittlerweile bestätigt.

In Visual Studio gibt es 2 Vorlagentypen:

  • Die Projektvorlage
  • Symbolvorlage

Die Projektvorlage steht immer dann zur Verfügung, wenn ich eine neue Solution erstellen will. Die Symbolvorlage kommt zum Einsatz, wenn über das Dialogfeld "Neues Element hinzufügen" der Projektmappe ein Element hinzugefügt werden soll. In diesem Bereich ist auch das Codegenerierungselement für das ADO.NET Entity Framework angesiedelt.

Abbildung 1 Auflistung der Codegenerierungsvorlagen über Dialog "Neues Element hinzufügen"
Abbildung 1

Wenn ich Codegenerierungsvorlagen für das Entity Framework über die Auswahl "Neues Codegenerierungselement hinzufügen" im Bereich des EDM-Designer auswählen will, stehen nur die Elemente "ADO.NET EntityObject Generator" und "ADO.NET-Entitäts-Generator mit Selbstnachverfolgung" zur Verfügung. Die anderen Elemente aus der Auflistung fehlen.

Abbildung 2 Auswahl über Dialog "Neues Codegenerierungselement hinzufügen" im EDM- Designer
Abbildung 2

Als Beispiel nehme ich die korrigierten T4-Vorlagen, die keine Fehler mehr produzieren und einsatztauglich sind. Damit diese Vorlage auch über den Menüpunkt im EDM Designer zur Verfügung steht, muss die Bezeichnung der vstemplate-Datei mit den Präfix ADONETArtifactGenerator_ beginnen. Das ist die Konvention, die es einzuhalten gilt. Als Grundlage nehme ich die SelfTacking – Basisvorlage von Visual Studio und nehme Anpassungen am Namen, der Description und am Icon vor.

 XML (ADONETArtifactGenerator_T4SelfTrackingGermanCodeGenTemplateCS.vstemplate)
<VSTemplate Version="3.0.0" xmlns="http://schemas.microsoft.com/developer/vstemplate/2005" Type="Item">

  <TemplateData>
    <Name>ADO.NET Entitäts Selbstnachverfolgung (Workaround)</Name>
    <Description>Diese Variante arbeitet auf Basis korrigierter T4-Templates.</Description>
    <DefaultName>Model.tt</DefaultName>
    <ProjectType>CSharp</ProjectType>
    <Icon>__TemplateIcon.ico</Icon>
    <ProvideDefaultName>true</ProvideDefaultName>
    <NumberOfParentCategoriesToRollUp>1</NumberOfParentCategoriesToRollUp>
    <RequiredFrameworkVersion>4.0</RequiredFrameworkVersion>
    <TemplateID>Microsoft.Data.Entity.Design.T4STEItemTemplate.CS</TemplateID>
  </TemplateData>

  <TemplateContent>

    <References>
      <Reference>
        <Assembly>System</Assembly>
      </Reference>
      <Reference>
        <Assembly>System.Data</Assembly>
      </Reference>
      <Reference>
        <Assembly>System.Data.Entity</Assembly>
      </Reference>
      <Reference>
        <Assembly>System.Runtime.Serialization</Assembly>
      </Reference>
      <Reference>
        <Assembly>System.Xml</Assembly>
      </Reference>
      <Reference>
        <Assembly>System.Core</Assembly>
      </Reference>
      <Reference>
        <Assembly>System.Security</Assembly>
      </Reference>
    </References>

    <ProjectItem OpenInEditor="false" SubType="" TargetFileName="$fileinputname$.Context.tt" ReplaceParameters="true">CSharpSelfTracking.Context.tt</ProjectItem>
    <ProjectItem OpenInEditor="false" SubType="" TargetFileName="$fileinputname$.tt" ReplaceParameters="true">CSharpSelfTracking.Types.tt</ProjectItem>
  </TemplateContent>

  <WizardExtension>
    <Assembly>Microsoft.Data.Entity.Design, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</Assembly>
    <FullClassName>Microsoft.Data.Entity.Design.VisualStudio.ModelWizard.AddArtifactGeneratorWizard</FullClassName>
  </WizardExtension>

</VSTemplate>

Anschliessend muss bei den erzeugten T4-Vorlagen die Konstante $edmxInputFile$ für die Variable mit den Namen inputFile neu gesetzt werden. Die korrigierten Vorlagen für die Typen und den Kontext beinhalten anschliessend folgenden Code:

T4 - Codeblock (CSharpSelfTracking.Types.tt)
<#@ template language="C#" debug="false" hostspecific="true"#>
<#@ include file="EF.Utility.CS.ttinclude"#><#@
 output extension=".cs"#><#
// Copyright (c) Microsoft Corporation.  All rights reserved.

CodeGenerationTools code = new CodeGenerationTools(this);
MetadataLoader loader = new MetadataLoader(this);
CodeRegion region = new CodeRegion(this, 1);
MetadataTools ef = new MetadataTools(this);

string inputFile = @"$edmxInputFile$";
MetadataWorkspace metadataWorkspace = null;
bool allMetadataLoaded =loader.TryLoadAllMetadata(inputFile, out metadataWorkspace);
EdmItemCollection ItemCollection = (EdmItemCollection)metadataWorkspace.GetItemCollection(DataSpace.CSpace);
OriginalValueMembers originalValueMembers = new OriginalValueMembers(allMetadataLoaded, metadataWorkspace, ef);
string namespaceName = code.VsNamespaceSuggestion();

EntityFrameworkTemplateFileManager fileManager = EntityFrameworkTemplateFileManager.Create(this);

// Unterstützungscode in Ausgabedatei für die primäre Vorlage schreiben
WriteHeader(fileManager);
BeginNamespace(namespaceName, code);
WriteObjectChangeTracker();
WriteIObjectWithChangeTracker();
WriteCustomObservableCollection();
WriteINotifyComplexPropertyChanging();
WriteEqualityComparer();
EndNamespace(namespaceName);

// Entitätstypen ausgeben
foreach (EntityType entity in ItemCollection.GetItems<EntityType>().OrderBy(e => e.Name))
{
    fileManager.StartNewFile(entity.Name + ".cs");
    BeginNamespace(namespaceName, code);
    WriteEntityTypeSerializationInfo(entity, ItemCollection, code, ef);
#>
<#=Accessibility.ForType(entity)#> <#=code.SpaceAfter(code.AbstractOption(entity))#>partial class <#=code.Escape(entity)

#><#=code.StringBefore(" : ", code.Escape(entity.BaseType))#><#=entity.BaseType == null ? ": " : ", 

"#>IObjectWithChangeTracker, INotifyPropertyChanged
{
<#
    region.Begin("Primitive Eigenschaften");

    foreach (EdmProperty edmProperty in entity.Properties.Where(p => p.TypeUsage.EdmType is PrimitiveType && 

p.DeclaringType == entity))
    {
#>

    [DataMember]
    <#=Accessibility.ForProperty(edmProperty)#> <#=code.Escape(edmProperty.TypeUsage)#> <#=code.Escape(edmProperty)#>
    {
        <#=code.SpaceAfter(Accessibility.ForGetter(edmProperty))#>get { return <#=code.FieldName(edmProperty)#>; }
        <#=code.SpaceAfter(Accessibility.ForSetter(edmProperty))#>set
        {
<#
        if (((PrimitiveType)edmProperty.TypeUsage.EdmType).PrimitiveTypeKind == PrimitiveTypeKind.Binary &&
            (ef.IsKey(edmProperty) || entity.NavigationProperties.Where(np=>np.GetDependentProperties().Contains

(edmProperty)).Any()))
        {
#>
            if (!EqualityComparer.BinaryEquals(<#=code.FieldName(edmProperty)#>, value))
<#
        }
        else
        {
#>
            if (<#=code.FieldName(edmProperty)#> != value)
<#
        }
#>
            {
<#
        if (ef.IsKey(edmProperty))
        {
            string errorMessage = String.Format("Die '{0}'-Eigenschaft ist Teil des Objektschlüssels und kann nicht 

geändert werden. Änderungen an Schlüsseleigenschaften können nur ausgeführt werden, wenn das Objekt nachverfolgt wird oder 

sich im Added-Zustand befindet.", edmProperty.Name);
#>
                if (ChangeTracker.ChangeTrackingEnabled && ChangeTracker.State != ObjectState.Added)
                {
                    throw new InvalidOperationException("<#=errorMessage#>");
                }
<#
        }
        else if (originalValueMembers.IsOriginalValueMember(edmProperty))
        {
#>
                ChangeTracker.RecordOriginalValue("<#=edmProperty.Name#>", <#=code.FieldName(edmProperty)#>);
<#
        }

        bool hasDependentProperties = entity.NavigationProperties.Where(np=>np.GetDependentProperties().Contains

(edmProperty)).Any();
        if (hasDependentProperties)
        {
#>
                if (!IsDeserializing)
                {
<#
        }
        foreach (var np in entity.NavigationProperties.Where(np=>np.GetDependentProperties().Contains(edmProperty)))
        {
            EdmProperty principalProperty = ef.GetCorrespondingPrincipalProperty(np, edmProperty);
            if (((PrimitiveType)principalProperty.TypeUsage.EdmType).PrimitiveTypeKind == PrimitiveTypeKind.Binary)
            {
#>
                    if (<#=code.Escape(np)#> != null && !EqualityComparer.BinaryEquals(<#=code.Escape(np)

#>.<#=code.Escape(principalProperty)#>, value))
<#
            }
            else
            {
#>
                    if (<#=code.Escape(np)#> != null && <#=code.Escape(np)#>.<#=code.Escape(principalProperty)#> != value)
<#
            }
#>
                    {
<#
            if (!(np.GetDependentProperties().Where(p=>ef.IsNullable(p)).Any() &&
                  np.GetDependentProperties().Count() > 1))
            {
#>
                        <#=code.Escape(np)#> = null;
<#
            }
            else
            {
#>
                        var previousValue = <#=code.FieldName(np)#>;
                        <#=code.FieldName(np)#> = null;
                        Fixup<#=np.Name#>(previousValue, skipKeys: true);
                        OnNavigationPropertyChanged("<#=np.Name#>");
<#
            }
#>
                    }
<#
        }
        if (hasDependentProperties)
        {
#>
                }
<#
        }
#>
                <#=code.FieldName(edmProperty)#> = value;
                OnPropertyChanged("<#=edmProperty.Name#>");
            }
        }
    }
    private <#=code.Escape(edmProperty.TypeUsage)#> <#=code.FieldName(edmProperty)#><#=code.StringBefore(" = ", 

code.CreateLiteral(edmProperty.DefaultValue))#>;
<#
    }
    region.End();

    region.Begin("Komplexe Eigenschaften");

    foreach(EdmProperty edmProperty in entity.Properties.Where(p => p.TypeUsage.EdmType is ComplexType && p.DeclaringType 

== entity))
    {
#>

    [DataMember]
    <#=Accessibility.ForProperty(edmProperty)#> <#=code.Escape(edmProperty.TypeUsage)#> <#=code.Escape(edmProperty)#>
    {
        <#=code.SpaceAfter(Accessibility.ForGetter(edmProperty))#>get
        {
            if (!<#=InitializedTrackingField(edmProperty, code)#> && <#=code.FieldName(edmProperty)#> == null)
            {
                <#=code.FieldName(edmProperty)#> = new <#=code.Escape(edmProperty.TypeUsage)#>();
                ((INotifyComplexPropertyChanging)<#=code.FieldName(edmProperty)#>).ComplexPropertyChanging += 

Handle<#=edmProperty.Name#>Changing;
            }
            <#=InitializedTrackingField(edmProperty, code)#> = true;
            return <#=code.FieldName(edmProperty)#>;
        }
        <#=code.SpaceAfter(Accessibility.ForSetter(edmProperty))#>set
        {
            <#=InitializedTrackingField(edmProperty, code)#> = true;
            if (!Equals(<#=code.FieldName(edmProperty)#>, value))
            {
                if (<#=code.FieldName(edmProperty)#> != null)
                {
                    ((INotifyComplexPropertyChanging)<#=code.FieldName(edmProperty)#>).ComplexPropertyChanging -= 

Handle<#=edmProperty.Name#>Changing;
                }

                Handle<#=edmProperty.Name#>Changing(this, null);
                <#=code.FieldName(edmProperty)#> = value;
                OnPropertyChanged("<#=edmProperty.Name#>");

                if (value != null)
                {
                    ((INotifyComplexPropertyChanging)<#=code.FieldName(edmProperty)#>).ComplexPropertyChanging += 

Handle<#=edmProperty.Name#>Changing;
                }
            }
        }
    }
    private <#=code.Escape(edmProperty.TypeUsage)#> <#=code.FieldName(edmProperty)#>;
    private bool <#=InitializedTrackingField(edmProperty, code)#>;
<#
    }

    region.End();

    ////////
    //////// Navigationseigenschaften schreiben 

-------------------------------------------------------------------------------------------
    ////////

    region.Begin("Navigationseigenschaften");

    foreach (NavigationProperty navProperty in entity.NavigationProperties.Where(np => np.DeclaringType == entity))
    {
        NavigationProperty inverse = ef.Inverse(navProperty);
        if (inverse != null &&  !IsReadWriteAccessibleProperty(inverse))
        {
            inverse = null;
        }
#>

    [DataMember]
<#
        if (navProperty.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many)
        {
#>
    <#=Accessibility.ForReadOnlyProperty(navProperty)#> TrackableCollection<<#=code.Escape

(navProperty.ToEndMember.GetEntityType())#>> <#=code.Escape(navProperty)#>
    {
        get
        {
            if (<#=code.FieldName(navProperty)#> == null)
            {
                <#=code.FieldName(navProperty)#> = new TrackableCollection<<#=code.Escape

(navProperty.ToEndMember.GetEntityType())#>>();
                <#=code.FieldName(navProperty)#>.CollectionChanged += Fixup<#=navProperty.Name#>;
            }
            return <#=code.FieldName(navProperty)#>;
        }
        set
        {
            if (!ReferenceEquals(<#=code.FieldName(navProperty)#>, value))
            {
                if (ChangeTracker.ChangeTrackingEnabled)
                {
                    throw new InvalidOperationException("FixupChangeTrackingCollection kann nicht festgelegt werden, wenn 

ChangeTracking aktiviert ist.");
                }
                if (<#=code.FieldName(navProperty)#> != null)
                {
                    <#=code.FieldName(navProperty)#>.CollectionChanged -= Fixup<#=navProperty.Name#>;
<#
        if (ef.IsCascadeDeletePrincipal(navProperty))
        {
#>
                    // Dies ist das Prinzipalende einer Zuordnung, die Löschweitergaben durchführt.
                    // Entfernen Sie den Löschweitergabe-Ereignishandler für alle Entitäten in der aktuellen Auflistung.
                    foreach (<#=code.Escape(navProperty.ToEndMember.GetEntityType())#> item in <#=code.FieldName

(navProperty)#>)
                    {
                        ChangeTracker.ObjectStateChanging -= item.HandleCascadeDelete;
                    }
<#
        }
#>
                }
                <#=code.FieldName(navProperty)#> = value;
                if (<#=code.FieldName(navProperty)#> != null)
                {
                    <#=code.FieldName(navProperty)#>.CollectionChanged += Fixup<#=navProperty.Name#>;
<#
        if (ef.IsCascadeDeletePrincipal(navProperty))
        {
#>
                    // Dies ist das Prinzipalende einer Zuordnung, die Löschweitergaben durchführt.
                    // Fügen Sie den Löschweitergabe-Ereignishandler für alle Entitäten hinzu, die sich bereits in der 

neuen Auflistung befinden.
                    foreach (<#=code.Escape(navProperty.ToEndMember.GetEntityType())#> item in <#=code.FieldName

(navProperty)#>)
                    {
                        ChangeTracker.ObjectStateChanging += item.HandleCascadeDelete;
                    }
<#
        }
#>
                }
                OnNavigationPropertyChanged("<#=navProperty.Name#>");
            }
        }
    }
    private TrackableCollection<<#=code.Escape(navProperty.ToEndMember.GetEntityType())#>> <#=code.FieldName(navProperty)

#>;
<#
        }
        else
        {
#>
    <#=Accessibility.ForProperty(navProperty)#> <#=code.Escape(navProperty.ToEndMember.GetEntityType())#> <#=code.Escape

(navProperty)#>
    {
        <#=code.SpaceAfter(Accessibility.ForGetter(navProperty))#>get { return <#=code.FieldName(navProperty)#>; }
        <#=code.SpaceAfter(Accessibility.ForSetter(navProperty))#>set
        {
            if (!ReferenceEquals(<#=code.FieldName(navProperty)#>, value))
            {
<#
            // Ist dies das abhängige Ende einer identifizierenden Beziehung, kann das Prinzipalende nur geändert werden, 

wenn sich das abhängige Element im Status "Hinzugefügt" befindet und der Prinzipalschlüssel mit dem Fremdschlüssel des 

abhängigen Elements übereinstimmt.
            if (ef.IsPrincipalEndOfIdentifyingRelationship((AssociationEndMember)navProperty.ToEndMember))
            {
#>
                if (ChangeTracker.ChangeTrackingEnabled && ChangeTracker.State != ObjectState.Added && value != null)
                {
<#
                List<EdmProperty> dependents = navProperty.GetDependentProperties().ToList();
                int dependentCount = dependents.Count;
                StringBuilder keyMatchCondition = new StringBuilder();
                for (int i = 0; i < dependentCount; i++)
                {
                    EdmProperty dependentProperty = dependents[i];
                    EdmProperty principalProperty = ef.GetCorrespondingPrincipalProperty(navProperty, dependentProperty);
                    string escapedDependent = code.Escape(dependentProperty);
                    string escapedPrincipal = code.Escape(principalProperty);

                    if (i > 0)
                    {
                        keyMatchCondition.AppendFormat(" || ");
                    }

                    string equality = null;
                    if (((PrimitiveType)principalProperty.TypeUsage.EdmType).PrimitiveTypeKind == 

PrimitiveTypeKind.Binary)
                    {
                        equality = "!EqualityComparer.BinaryEquals({0}, value.{1})";
                    }
                    else
                    {
                        equality = "{0} != value.{1}";
                    }
                    keyMatchCondition.AppendFormat(CultureInfo.InvariantCulture, equality, escapedDependent, 

escapedPrincipal);
                }
#>
                    // Dies ist das abhängige Ende einer identifizierenden Beziehung. Das Prinzipalende kann daher nicht 

geändert werden kann, wenn es bereits festgelegt ist.
                    // Andernfalls kann es nur auf eine Entität mit einem Primärschlüssel festgelegt werden, der denselben 

Wert wie der Fremdschlüssel des abhängigen Elements aufweist.
                    if (<#=keyMatchCondition.ToString()#>)
                    {
                        throw new InvalidOperationException("Das Prinzipalende einer identifizierenden Beziehung kann nur 

geändert werden, wenn das abhängige Ende den Status "Hinzugefügt" aufweist.");
                    }
                }
<#
            }
#>
                var previousValue = <#=code.FieldName(navProperty)#>;
                <#=code.FieldName(navProperty)#> = value;
                Fixup<#=navProperty.Name#>(previousValue);
                OnNavigationPropertyChanged("<#=navProperty.Name#>");
            }
        }
    }
    private <#=code.Escape(navProperty.ToEndMember.GetEntityType())#> <#=code.FieldName(navProperty)#>;
<#
        }
    }
    region.End();

    region.Begin("ChangeTracking");
    if (entity.BaseType == null)
    {
#>

    protected virtual void OnPropertyChanged(String propertyName)
    {
        if (ChangeTracker.State != ObjectState.Added && ChangeTracker.State != ObjectState.Deleted)
        {
            ChangeTracker.State = ObjectState.Modified;
        }
        if (_propertyChanged != null)
        {
            _propertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    protected virtual void OnNavigationPropertyChanged(String propertyName)
    {
        if (_propertyChanged != null)
        {
            _propertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged{ add { _propertyChanged += value; } remove { 

_propertyChanged -= value; } }
    private event PropertyChangedEventHandler _propertyChanged;
    private ObjectChangeTracker _changeTracker;

    [DataMember]
    public ObjectChangeTracker ChangeTracker
    {
        get
        {
            if (_changeTracker == null)
            {
                _changeTracker = new ObjectChangeTracker();
                _changeTracker.ObjectStateChanging += HandleObjectStateChanging;
            }
            return _changeTracker;
        }
        set
        {
            if(_changeTracker != null)
            {
                _changeTracker.ObjectStateChanging -= HandleObjectStateChanging;
            }
            _changeTracker = value;
            if(_changeTracker != null)
            {
                _changeTracker.ObjectStateChanging += HandleObjectStateChanging;
            }
        }
    }

    private void HandleObjectStateChanging(object sender, ObjectStateChangingEventArgs e)
    {
        if (e.NewState == ObjectState.Deleted)
        {
            ClearNavigationProperties();
        }
    }
<#
    // Wenn dieser Entitätstyp an Beziehungen beteiligt ist, bei denen am anderen Ende eine OnDelete-
    // Löschweitergabe definiert wurde, oder wenn er das abhängige Element in einer identifizierenden Beziehung ist, wird 

ein
    // Ereignishandler benötigt, um Benachrichtigungen zu behandeln, die beim Löschen des übergeordneten Elements 

ausgelöst werden.
    if (ItemCollection.GetItems<AssociationType>().Where(a =>
        ((RefType)a.AssociationEndMembers[0].TypeUsage.EdmType).ElementType == entity && ef.IsCascadeDeletePrincipal

(a.AssociationEndMembers[1]) ||
        ((RefType)a.AssociationEndMembers[1].TypeUsage.EdmType).ElementType == entity && ef.IsCascadeDeletePrincipal

(a.AssociationEndMembers[0])).Any())
    {
#>

    // Dieser Entitätstyp ist das abhängige Ende in mindestens einer Zuordnung, die Löschweitergaben durchführt.
    // Dieser Ereignishandler verarbeitet Benachrichtigungen, die beim Löschen des Prinzipalendes ausgelöst werden.
    internal void HandleCascadeDelete(object sender, ObjectStateChangingEventArgs e)
    {
        if (e.NewState == ObjectState.Deleted)
        {
            this.MarkAsDeleted();
        }
    }
<#
    }
#>

    protected bool IsDeserializing { get; private set; }

    [OnDeserializing]
    public void OnDeserializingMethod(StreamingContext context)
    {
        IsDeserializing = true;
    }

    [OnDeserialized]
    public void OnDeserializedMethod(StreamingContext context)
    {
        IsDeserializing = false;
        ChangeTracker.ChangeTrackingEnabled = true;
    }
<#
    }

    foreach(EdmProperty edmProperty in entity.Properties.Where(p => p.TypeUsage.EdmType is ComplexType && p.DeclaringType 

== entity))
    {
#>
    // <#=String.Format(CultureInfo.CurrentCulture, "Zeichnet die ursprünglichen Werte für die komplexe Eigenschaft '{0}' 

auf.", edmProperty.Name)#>
    private void Handle<#=edmProperty.Name#>Changing(object sender, EventArgs args)
    {
        if (ChangeTracker.State != ObjectState.Added && ChangeTracker.State != ObjectState.Deleted)
        {
            ChangeTracker.State = ObjectState.Modified;
        }
<#
        if (originalValueMembers.IsOriginalValueMember(edmProperty))
        {
#>
        <#=code.Escape(edmProperty.TypeUsage)#>.RecordComplexOriginalValues("<#=edmProperty.Name#>", this.<#=code.Escape

(edmProperty)#>, ChangeTracker);
<#
        }
#>
    }

<#
    }

    List<AssociationEndMember> shadowAssociationEnds = new List<AssociationEndMember>();
    foreach(var association in ItemCollection.GetItems<AssociationType>().Where(x => !

IsForeignKeyOrIdentifyingRelationship(ef, x) &&
                                                                                ((((RefType)x.AssociationEndMembers

[0].TypeUsage.EdmType).ElementType == entity &&
                                                                                   x.AssociationEndMembers

[0].RelationshipMultiplicity != RelationshipMultiplicity.One &&
                                                                                   x.AssociationEndMembers

[1].RelationshipMultiplicity != RelationshipMultiplicity.Many) ||
                                                                                 ((RefType)x.AssociationEndMembers

[1].TypeUsage.EdmType).ElementType == entity &&
                                                                                   x.AssociationEndMembers

[1].RelationshipMultiplicity != RelationshipMultiplicity.One &&
                                                                                   x.AssociationEndMembers

[0].RelationshipMultiplicity != RelationshipMultiplicity.Many)))
    {
        if (!entity.NavigationProperties.Any(x => x.RelationshipType == association))
        {
            for (int i = 0; i < 2; i++)
            {
                int targetRoleIndex = 0;
                if (((RefType)association.AssociationEndMembers[i].TypeUsage.EdmType).ElementType == entity)
                {
                    targetRoleIndex = (i + 1) % 2;
                    shadowAssociationEnds.Add(association.AssociationEndMembers[targetRoleIndex]);
                }
            }
        }
    }
#>

    protected <#=entity.BaseType == null ? "virtual " : "override " #>void ClearNavigationProperties()
    {
<#
    if (entity.BaseType != null)
    {
#>
        base.ClearNavigationProperties();
<#
    }
    foreach (NavigationProperty navProperty in entity.NavigationProperties.Where(np => np.DeclaringType == entity))
    {
        if (navProperty.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many)
        {
#>
        <#=code.Escape(navProperty)#>.Clear();
<#
        }
        else
        {
#>
        <#=code.Escape(navProperty)#> = null;
<#
            if (IsSaveReference(ef, navProperty))
            {
#>
        Fixup<#=navProperty.Name#>Keys();
<#
            }
        }
    }
    foreach(var associationEnd in shadowAssociationEnds)
    {
        AssociationType association = associationEnd.DeclaringType as AssociationType;
#>
        <#=CreateFixupMethodName(associationEnd)#>(null, true);
<#
    }
#>
    }
<#
    region.End();

    region.Begin("Fixup für Zuordnungen");

    foreach (NavigationProperty navProperty in entity.NavigationProperties.Where(np => np.DeclaringType == entity))
    {
        NavigationProperty inverse = ef.Inverse(navProperty);

        if (inverse != null && !IsReadWriteAccessibleProperty(inverse))
        {
            inverse = null;
        }

        if (navProperty.ToEndMember.RelationshipMultiplicity != RelationshipMultiplicity.Many)
        {
            var skipKeysArgument = navProperty.GetDependentProperties().Where(p=>ef.IsNullable(p)).Any()
                ? ", bool skipKeys = false"
                : String.Empty;
#>

    private void Fixup<#=navProperty.Name#>(<#=code.Escape(navProperty.ToEndMember.GetEntityType())#> previousValue<#= 

skipKeysArgument #>)
    {
<#
        if (ef.IsCascadeDeletePrincipal(navProperty))
        {
#>
        // Dies ist das Prinzipalende einer Zuordnung, die Löschweitergaben durchführt.
        // Aktualisieren Sie den Ereignislistener, sodass er auf das neue abhängige Element verweist.
        if (previousValue != null)
        {
            ChangeTracker.ObjectStateChanging -= previousValue.HandleCascadeDelete;
        }

        if (<#=code.Escape(navProperty)#> != null)
        {
            ChangeTracker.ObjectStateChanging += <#=code.Escape(navProperty)#>.HandleCascadeDelete;
        }

<#
        }
        else if (inverse == null && ef.IsCascadeDeletePrincipal((AssociationEndMember)navProperty.ToEndMember))
        {
#>
        // Dies ist das abhängige Ende einer Zuordnung, die Löschweitergaben durchführt.
        // Aktualisieren Sie den Ereignislistener des Prinzipals, sodass er auf das neue abhängige Element verweist.
        // Dies ist eine unidirektionale Beziehung vom abhängigen Element zum Prinzipal. Das abhängige Element ist daher
        // für das Verwalten des Löschweitergabe-Ereignishandlers verantwortlich. Alle anderen Fälle werden durch das 

Prinzipalende verwaltet.
        if (previousValue != null)
        {
            previousValue.ChangeTracker.ObjectStateChanging -= HandleCascadeDelete;
        }

        if (<#=code.Escape(navProperty)#> != null)
        {
            <#=code.Escape(navProperty)#>.ChangeTracker.ObjectStateChanging += HandleCascadeDelete;
        }

<#
        }
#>
        if (IsDeserializing)
        {
            return;
        }

<#
        if (inverse != null)
        {
            if (inverse.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many)
            {
#>
        if (previousValue != null && previousValue.<#=code.Escape(inverse)#>.Contains(this))
        {
            previousValue.<#=code.Escape(inverse)#>.Remove(this);
        }
<#
            }
            else
            {
#>
        if (previousValue != null && ReferenceEquals(previousValue.<#=code.Escape(inverse)#>, this))
        {
            previousValue.<#=code.Escape(inverse)#> = null;
        }
<#
            }

            if (inverse.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many)
            {
#>

        if (<#=code.Escape(navProperty)#> != null)
        {
            if (!<#=code.Escape(navProperty)#>.<#=code.Escape(inverse)#>.Contains(this))
            {
                <#=code.Escape(navProperty)#>.<#=code.Escape(inverse)#>.Add(this);
            }

<#
                foreach (var dependentProperty in navProperty.GetDependentProperties())
                {
#>
            <#=code.Escape(dependentProperty)#> = <#=code.Escape(navProperty)#>.<#=code.Escape

(ef.GetCorrespondingPrincipalProperty(navProperty, dependentProperty))#>;
<#
                }
#>
        }
<#
                if (navProperty.GetDependentProperties().Where(p=>ef.IsNullable(p)).Any())
                {
#>
        else if (!skipKeys)
        {
<#
                foreach (var dependentProperty in navProperty.GetDependentProperties().Where(p => ef.IsNullable(p)))
                {
#>
            <#=code.Escape(dependentProperty)#> = null;
<#
                }
#>
        }

<#
                }
            }
            else
            {
#>

        if (<#=code.Escape(navProperty)#> != null)
        {
            <#=code.Escape(navProperty)#>.<#=code.Escape(inverse)#> = this;
<#
                foreach (var dependentProperty in navProperty.GetDependentProperties())
                {
#>
            <#=code.Escape(dependentProperty)#> = <#=code.Escape(navProperty)#>.<#=code.Escape

(ef.GetCorrespondingPrincipalProperty(navProperty, dependentProperty))#>;
<#
                }
#>
        }

<#
            }
        }
        else
        {
            if (navProperty.GetDependentProperties().Any())
            {
#>
        if (<#=code.Escape(navProperty)#> != null)
        {
<#
                foreach (var dependentProperty in navProperty.GetDependentProperties())
                {
#>
            <#=code.Escape(dependentProperty)#> = <#=code.Escape(navProperty)#>.<#=code.Escape

(ef.GetCorrespondingPrincipalProperty(navProperty, dependentProperty))#>;
<#
                }
#>
        }

<#
                if (navProperty.GetDependentProperties().Where(p => ef.IsNullable(p)).Any())
                {
#>
        else if (!skipKeys)
        {
<#
                    foreach (var dependentProperty in navProperty.GetDependentProperties().Where(p => ef.IsNullable(p)))
                    {
#>
            <#=code.Escape(dependentProperty)#> = null;
<#
                    }
#>
        }

<#
                }
            }
            else if (IsForeignKeyOrIdentifyingRelationship(ef, navProperty))
            {
#>
        if (<#=code.Escape(navProperty)#> != null)
        {
<#
                foreach (var fromProperty in ef.GetPrincipalProperties(navProperty))
                {
#>
            <#=code.Escape(navProperty)#>.<#=code.Escape(ef.GetCorrespondingDependentProperty(navProperty, fromProperty))

#> = <#=code.Escape(fromProperty)#>;
<#
                }
#>
        }

<#
            }
        }
#>
        if (ChangeTracker.ChangeTrackingEnabled)
        {
            if (ChangeTracker.OriginalValues.ContainsKey("<#=navProperty.Name#>")
                && (ChangeTracker.OriginalValues["<#=navProperty.Name#>"] == <#=code.Escape(navProperty)#>))
            {
                ChangeTracker.OriginalValues.Remove("<#=navProperty.Name#>");
            }
            else
            {
                ChangeTracker.RecordOriginalValue("<#=navProperty.Name#>", previousValue);
<#
        if (ef.IsPrincipalEndOfIdentifyingRelationship((AssociationEndMember)navProperty.FromEndMember))
        {
#>
                // Dies ist das Prinzipalende einer identifizierenden Zuordnung. Das abhängige Element muss daher gelöscht 

werden, wenn die Beziehung entfernt wird.
                // Ist der aktuelle Status des abhängigen Elements "Hinzugefügt", kann die Beziehung geändert werden, ohne 

dass das abhängige Element gelöscht wird.
                if (previousValue != null && previousValue.ChangeTracker.State != ObjectState.Added)
                {
                    previousValue.MarkAsDeleted();
                }
<#
        }
        else if (inverse == null && ef.IsPrincipalEndOfIdentifyingRelationship((AssociationEndMember)

navProperty.ToEndMember))
        {
#>
                // Dies ist das abhängige Ende einer identifizierenden Zuordnung. Es muss daher gelöscht werden, wenn die 

Beziehung
                // entfernt wird. Ist der aktuelle Status "Hinzugefügt", kann die Beziehung geändert werden, ohne dass das 

abhängige Element gelöscht wird.
                // Dies ist eine unidirektionale Beziehung vom abhängigen Element zum Prinzipal. Das abhängige Element ist 

daher
                // für die Löschweitergabe verantwortlich. Alle anderen Fälle werden durch das Prinzipalende verwaltet.
                if (previousValue != null && ChangeTracker.State != ObjectState.Added)
                {
                    this.MarkAsDeleted();
                }
<#
        }
#>
            }
            if (<#=code.Escape(navProperty)#> != null && !<#=code.Escape(navProperty)

#>.ChangeTracker.ChangeTrackingEnabled)
            {
                <#=code.Escape(navProperty)#>.StartTracking();
            }
<#
        if (IsSaveReference(ef, navProperty))
        {
#>
            Fixup<#=navProperty.Name#>Keys();
<#
        }
        if (inverse == null &&
            !IsForeignKeyOrIdentifyingRelationship(ef, navProperty) &&
            navProperty.FromEndMember.RelationshipMultiplicity != RelationshipMultiplicity.Many &&
            navProperty.ToEndMember.RelationshipMultiplicity != RelationshipMultiplicity.One)
        {
#>
            if (previousValue != null)
            {
                previousValue.<#=CreateFixupMethodName(navProperty.FromEndMember)#>(null, false);
            }
            if (<#=code.Escape(navProperty)#> != null)
            {
                <#=code.Escape(navProperty)#>.<#=CreateFixupMethodName(navProperty.FromEndMember)#>(this, false);
            }
<#
        }
#>
        }
    }
<#
        if (IsSaveReference(ef, navProperty))
        {
            EntityType targetType = (EntityType)navProperty.TypeUsage.EdmType;
            List<string> keyNames = targetType.KeyMembers.Select(x => x.Name).ToList();
#>

    private void Fixup<#=navProperty.Name#>Keys()
    {
<#
            for(int k=0; k < keyNames.Count; k++)
            {
#>
        const string <#=CreateKeyNameVariable(code.Escape(keyNames[k]))#> = "<#=CreateReferenceValueLookupKey(navProperty, 

keyNames[k])#>";
<#
            }
#>

        if(ChangeTracker.ExtendedProperties.ContainsKey(<#=CreateKeyNameVariable(code.Escape(keyNames[0]))#>)

<#=keyNames.Count > 1 ? " &&" : ")"#>
<#
            for(int k=1; k < keyNames.Count; k++)
            {
#>
           ChangeTracker.ExtendedProperties.ContainsKey(<#=CreateKeyNameVariable(code.Escape(keyNames[k]))#>)<#=k < 

keyNames.Count - 1 ? " &&" : ")" #>
<#
            }
#>
        {
            if(<#=code.Escape(navProperty)#> == null ||
<#
            for(int k=0; k < keyNames.Count; k++)
            {
                string equality = ((PrimitiveType)targetType.KeyMembers[keyNames[k]].TypeUsage.EdmType).PrimitiveTypeKind 

== PrimitiveTypeKind.Binary ? "EqualityComparer.Binary" : String.Empty;
#>
               !<#=equality#>Equals(ChangeTracker.ExtendedProperties[<#=CreateKeyNameVariable(code.Escape(keyNames[k]))

#>], <#=code.Escape(navProperty)#>.<#=code.Escape(keyNames[k])#>)<#=k < keyNames.Count - 1 ? " ||" : ")" #>
<#
            }
#>
            {
<#
            for(int k=0; k < keyNames.Count; k++)
            {
#>
                ChangeTracker.RecordOriginalValue(<#=CreateKeyNameVariable(code.Escape(keyNames[k]))#>, 

ChangeTracker.ExtendedProperties[<#=CreateKeyNameVariable(code.Escape(keyNames[k]))#>]);
<#
            }
#>
            }
<#
            for(int k=0; k < keyNames.Count; k++)
            {
#>
            ChangeTracker.ExtendedProperties.Remove(<#=CreateKeyNameVariable(code.Escape(keyNames[k]))#>);
<#
            }
#>
        }
    }
<#
            }
        }
    }

    foreach (NavigationProperty navProperty in entity.NavigationProperties.Where(np => np.DeclaringType == entity))
    {
        NavigationProperty inverse = ef.Inverse(navProperty);

        if (inverse != null && !IsReadWriteAccessibleProperty(inverse))
        {
            inverse = null;
        }

        if (navProperty.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many)
        {
#>

    private void Fixup<#=navProperty.Name#>(object sender, NotifyCollectionChangedEventArgs e)
    {
        if (IsDeserializing)
        {
            return;
        }

        if (e.NewItems != null)
        {
            foreach (<#=code.Escape(navProperty.ToEndMember.GetEntityType())#> item in e.NewItems)
            {
<#
                if (inverse != null)
                {
                    if (inverse.ToEndMember.RelationshipMultiplicity != RelationshipMultiplicity.Many)
                    {
#>
                item.<#=code.Escape(inverse)#> = this;
<#
                    }
                    else
                    {
#>
                if (!item.<#=code.Escape(inverse)#>.Contains(this))
                {
                    item.<#=code.Escape(inverse)#>.Add(this);
                }
<#
                    }
                }
                else if (IsForeignKeyOrIdentifyingRelationship(ef, navProperty))
                {
                    foreach (var fromProperty in ef.GetPrincipalProperties(navProperty))
                    {
#>
                item.<#=code.Escape(ef.GetCorrespondingDependentProperty(navProperty, fromProperty))#> = <#=code.Escape

(fromProperty)#>;
<#
                    }
                }
                else if (navProperty.FromEndMember.RelationshipMultiplicity == RelationshipMultiplicity.ZeroOrOne)
                {
#>
                item.<#=CreateFixupMethodName(navProperty.FromEndMember)#>(this, false);
<#
                }
#>
                if (ChangeTracker.ChangeTrackingEnabled)
                {
                    if (!item.ChangeTracker.ChangeTrackingEnabled)
                    {
                        item.StartTracking();
                    }
                    ChangeTracker.RecordAdditionToCollectionProperties("<#=code.Escape(navProperty)#>", item);
                }
<#
                if (ef.IsCascadeDeletePrincipal(navProperty))
                {
#>
                // Dies ist das Prinzipalende einer Zuordnung, die Löschweitergaben durchführt.
                // Aktualisieren Sie den Ereignislistener, sodass er auf das neue abhängige Element verweist.
                ChangeTracker.ObjectStateChanging += item.HandleCascadeDelete;
<#
                }
#>
            }
        }

        if (e.OldItems != null)
        {
            foreach (<#=code.Escape(navProperty.ToEndMember.GetEntityType())#> item in e.OldItems)
            {
<#
                if (inverse != null)
                {
                    if (inverse.ToEndMember.RelationshipMultiplicity != RelationshipMultiplicity.Many)
                    {
#>
                if (ReferenceEquals(item.<#=code.Escape(inverse)#>, this))
                {
                    item.<#=code.Escape(inverse)#> = null;
                }
<#
                    }
                    else
                    {
#>
                if (item.<#=code.Escape(inverse)#>.Contains(this))
                {
                    item.<#=code.Escape(inverse)#>.Remove(this);
                }
<#
                    }
                }
                else if (IsForeignKeyOrIdentifyingRelationship(ef, navProperty))
                {
                    foreach (var fromProperty in ef.GetPrincipalProperties(navProperty))
                    {
                        var p = ef.GetCorrespondingDependentProperty(navProperty, fromProperty);
                        if (ef.IsNullable(p.TypeUsage))
                        {
#>
                item.<#=code.Escape(p)#> = null;
<#
                        }
                    }
                }
                else if (navProperty.FromEndMember.RelationshipMultiplicity == RelationshipMultiplicity.ZeroOrOne)
                {
#>
                item.<#=CreateFixupMethodName(navProperty.FromEndMember)#>(null, false);
<#
                }
#>
                if (ChangeTracker.ChangeTrackingEnabled)
                {
                    ChangeTracker.RecordRemovalFromCollectionProperties("<#=code.Escape(navProperty)#>", item);
<#
                if (ef.IsPrincipalEndOfIdentifyingRelationship((AssociationEndMember)navProperty.FromEndMember))
                {
#>
                    // Löschen Sie das abhängige Ende dieser identifizierenden Zuordnung. Wenn der aktuelle Status 

"Hinzugefügt" ist,
                    // kann die Beziehung geändert werden, ohne dass das abhängige Element gelöscht wird.
                    if (item.ChangeTracker.State != ObjectState.Added)
                    {
                        item.MarkAsDeleted();
                    }
<#
                }
#>
                }
<#
                if (ef.IsCascadeDeletePrincipal(navProperty))
                {
#>
                // Dies ist das Prinzipalende einer Zuordnung, die Löschweitergaben durchführt.
                // Entfernen Sie das vorherige abhängige Element aus dem Ereignislistener.
                ChangeTracker.ObjectStateChanging -= item.HandleCascadeDelete;
<#
                }
#>
            }
        }
    }
<#
        }
    }

    foreach(var associationEnd in shadowAssociationEnds)
    {
        AssociationType association = associationEnd.DeclaringType as AssociationType;
        EntityType targetType = ((RefType)associationEnd.TypeUsage.EdmType).ElementType as EntityType;
        List<string> keyNames = targetType.KeyMembers.Select(x => x.Name).ToList();
#>

    internal void <#=CreateFixupMethodName(associationEnd)#>(<#=code.Escape(targetType)#> value, bool forceRemove)
    {
<#
            for(int k=0; k < keyNames.Count; k++)
            {
#>
        const string <#=CreateKeyNameVariable(code.Escape(keyNames[k]))#> = "<#=CreateReferenceValueLookupKey

(associationEnd, keyNames[k])#>";
<#
            }
#>

        if (ChangeTracker.ChangeTrackingEnabled &&
<#
        for(int k=0; k < keyNames.Count; k++)
        {
#>
            ChangeTracker.ExtendedProperties.ContainsKey(<#=CreateKeyNameVariable(code.Escape(keyNames[k]))#>)<#=k < 

keyNames.Count - 1 ? " &&" : ")"#>
<#
        }
#>
        {
            if (forceRemove ||
<#
        for(int k=0; k < keyNames.Count; k++)
        {
                string equality = ((PrimitiveType)targetType.KeyMembers[keyNames[k]].TypeUsage.EdmType).PrimitiveTypeKind 

== PrimitiveTypeKind.Binary ? "EqualityComparer.Binary" : String.Empty;
#>
                !<#=equality#>Equals(ChangeTracker.ExtendedProperties[<#=CreateKeyNameVariable(code.Escape(keyNames[k]))

#>], value == null ? null : (object)value.<#=code.Escape(keyNames[k])#>)<#=k < keyNames.Count - 1 ? " ||" : ")"#>
<#
        }
#>
            {
<#
        for(int k=0; k < keyNames.Count; k++)
        {
#>
                ChangeTracker.RecordOriginalValue(<#=CreateKeyNameVariable(code.Escape(keyNames[k]))#>, 

ChangeTracker.ExtendedProperties[<#=CreateKeyNameVariable(code.Escape(keyNames[k]))#>]);
<#
        }
#>
                if (value == null)
                {
<#
        for(int k=0; k < keyNames.Count; k++)
        {
#>
                    ChangeTracker.ExtendedProperties.Remove(<#=CreateKeyNameVariable(code.Escape(keyNames[k]))#>);
<#
        }
#>
                }
                else
                {
<#
        for(int k=0; k < keyNames.Count; k++)
        {
#>
                    ChangeTracker.ExtendedProperties[<#=CreateKeyNameVariable(code.Escape(keyNames[k]))#>] = 

value.<#=code.Escape(keyNames[k])#>;
<#
        }
#>
                }
            }
        }
    }
<#
    }

    region.End();
#>
}
<#
    EndNamespace(namespaceName);
}

foreach (ComplexType complex in ItemCollection.GetItems<ComplexType>().OrderBy(e => e.Name))
{
    fileManager.StartNewFile(complex.Name + ".cs");
    BeginNamespace(namespaceName, code);
#>

<#=Accessibility.ForType(complex)#> partial class <#=code.Escape(complex)#> : INotifyComplexPropertyChanging, 

INotifyPropertyChanged
{
<#
    region.Begin("Primitive Eigenschaften");

    foreach(EdmProperty edmProperty in complex.Properties.Where(p => p.TypeUsage.EdmType is PrimitiveType && 

p.DeclaringType == complex))
    {
#>

    [DataMember]
    <#=Accessibility.ForProperty(edmProperty)#> <#=code.Escape(edmProperty.TypeUsage)#> <#=code.Escape(edmProperty)#>
    {
        <#=code.SpaceAfter(Accessibility.ForGetter(edmProperty))#>get { return <#=code.FieldName(edmProperty)#>; }
        <#=code.SpaceAfter(Accessibility.ForSetter(edmProperty))#>set
        {
            if (<#=code.FieldName(edmProperty)#> != value)
            {
                OnComplexPropertyChanging();
                <#=code.FieldName(edmProperty)#> = value;
                OnPropertyChanged("<#=edmProperty.Name#>");
            }
        }
    }
    private <#=code.Escape(edmProperty.TypeUsage)#> <#=code.FieldName(edmProperty)#>;
<#
    }

    region.End();

    region.Begin("Komplexe Eigenschaften");

    foreach(EdmProperty edmProperty in complex.Properties.Where(p => p.TypeUsage.EdmType is ComplexType && p.DeclaringType 

== complex))
    {
#>

    [DataMember]
    <#=Accessibility.ForProperty(edmProperty)#> <#=code.Escape(edmProperty.TypeUsage)#> <#=code.Escape(edmProperty)#>
    {
        <#=code.SpaceAfter(Accessibility.ForGetter(edmProperty))#>get
        {
            if (!<#=InitializedTrackingField(edmProperty, code)#> && <#=code.FieldName(edmProperty)#> == null)
            {
                <#=code.FieldName(edmProperty)#> = new <#=code.Escape(edmProperty.TypeUsage)#>();
                ((INotifyComplexPropertyChanging)<#=code.FieldName(edmProperty)#>).ComplexPropertyChanging += 

HandleComplexPropertyChanging;
            }
            <#=InitializedTrackingField(edmProperty, code)#> = true;
            return <#=code.FieldName(edmProperty)#>;
        }
        <#=code.SpaceAfter(Accessibility.ForSetter(edmProperty))#>set
        {
            <#=InitializedTrackingField(edmProperty, code)#> = true;
            if (!Equals(<#=code.FieldName(edmProperty)#>, value))
            {
                if (<#=code.FieldName(edmProperty)#> != null)
                {
                    ((INotifyComplexPropertyChanging)<#=code.FieldName(edmProperty)#>).ComplexPropertyChanging -= 

HandleComplexPropertyChanging;
                }

                OnComplexPropertyChanging();
                <#=code.FieldName(edmProperty)#> = value;
                OnPropertyChanged("<#=edmProperty.Name#>");

                if (value != null)
                {
                    ((INotifyComplexPropertyChanging)value).ComplexPropertyChanging += HandleComplexPropertyChanging;
                }
            }
        }
    }
    private <#=code.Escape(edmProperty.TypeUsage)#> <#=code.FieldName(edmProperty)#>;
    private bool <#=InitializedTrackingField(edmProperty, code)#>;
<#
    }

    region.End();

    region.Begin("ChangeTracking");
#>

    private void OnComplexPropertyChanging()
    {
        if (_complexPropertyChanging != null)
        {
            _complexPropertyChanging(this, new EventArgs());
        }
    }

    event EventHandler INotifyComplexPropertyChanging.ComplexPropertyChanging { add { _complexPropertyChanging += value; } 

remove { _complexPropertyChanging -= value; } }
    private event EventHandler _complexPropertyChanging;

    private void OnPropertyChanged(String propertyName)
    {
        if (_propertyChanged != null)
        {
            _propertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged { add { _propertyChanged += value; } remove { 

_propertyChanged -= value; } }
    private event PropertyChangedEventHandler _propertyChanged;
<#
    if(complex.Properties.Where(p => p.TypeUsage.EdmType is ComplexType && p.DeclaringType == complex).Count() > 0)
    {
#>

    private void HandleComplexPropertyChanging(object sender, EventArgs args)
    {
        // Überträgt das Ereignis an alle Listener, da in einer geschachtelten komplexen Eigenschaft etwas geändert wurde
        OnComplexPropertyChanging();
    }
<#
    }
#>

    public static void RecordComplexOriginalValues(String parentPropertyName, <#=code.Escape(complex)#> complexObject, 

ObjectChangeTracker changeTracker)
    {
        if (String.IsNullOrEmpty(parentPropertyName))
        {
            throw new ArgumentException("Der Zeichenfolgenparameter darf nicht NULL oder leer sein.", 

"parentPropertyName");
        }

        if (changeTracker == null)
        {
            throw new ArgumentNullException("changeTracker");
        }
<#
        foreach(EdmProperty complexProperty in complex.Properties)
        {
            if (complexProperty.TypeUsage.EdmType is ComplexType)
            {
#>
        <#=code.Escape(complexProperty.TypeUsage)#>.RecordComplexOriginalValues(String.Format

(CultureInfo.InvariantCulture, "{0}.<#=complexProperty.Name#>", parentPropertyName), complexObject == null ? null : 

complexObject.<#=code.Escape(complexProperty)#>, changeTracker);
<#
            }
            else
            {
#>
        changeTracker.RecordOriginalValue(String.Format(CultureInfo.InvariantCulture, "{0}.<#=complexProperty.Name#>", 

parentPropertyName), complexObject == null ? null : (object)complexObject.<#=code.Escape(complexProperty)#>);
<#
            }
        }
#>
    }
<#
    region.End();
#>
}
<#
    EndNamespace(namespaceName);
}

if (!VerifyTypesAreCaseInsensitiveUnique(ItemCollection))
{
    return "";
}

fileManager.Process();

#>
<#+
void WriteHeader(EntityFrameworkTemplateFileManager fileManager, params string[] extraUsings)
{
    fileManager.StartHeader();
#>
//------------------------------------------------------------------------------
// <auto-generated>
//     Der Code wurde aus einer Vorlage generiert.
//
//     Änderungen an dieser Datei führen möglicherweise zu falschem Verhalten und gehen verloren, falls
//     der Code erneut generiert wird.
// </auto-generated>
//------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Globalization;
using System.Runtime.Serialization;
<#=String.Join(String.Empty, extraUsings.Select(u => "using " + u + ";" + Environment.NewLine).ToArray())#>
<#+
    fileManager.EndBlock();
}

void BeginNamespace(string namespaceName, CodeGenerationTools code)
{
    CodeRegion region = new CodeRegion(this);
    if (!String.IsNullOrEmpty(namespaceName))
    {
#>
namespace <#=code.EscapeNamespace(namespaceName)#>
{
<#+
        PushIndent(CodeRegion.GetIndent(1));
    }
}

void EndNamespace(string namespaceName)
{
    if (!String.IsNullOrEmpty(namespaceName))
    {
        PopIndent();
#>
}
<#+
    }
}

bool IsReadWriteAccessibleProperty(EdmMember member)
{
    string setter = Accessibility.ForWriteOnlyProperty(member);
    string getter = Accessibility.ForReadOnlyProperty(member);

    return getter != "private" && getter != "protected" && setter != "private" && setter != "protected";
}

string InitializedTrackingField(EdmProperty property, CodeGenerationTools code)
{
    string namePart = property.Name + "Initialized";
    if (code.CamelCaseFields)
    {
        namePart = code.CamelCase(namePart);
    }
    return "_" + namePart;
}

void WriteEntityTypeSerializationInfo(EntityType type, ItemCollection itemCollection, CodeGenerationTools code, 

MetadataTools tools)
{
#>
[DataContract(IsReference = true)]
<#+
    foreach(EntityType subtype in tools.GetSubtypesOf(type, itemCollection, true))
    {
#>
[KnownType(typeof(<#=code.Escape(subtype)#>))]
<#+
    }
    List<EntityType> knownNavPropertyTypes = new List<EntityType>();
    foreach(NavigationProperty navProperty in type.NavigationProperties.Where(np => np.DeclaringType == type))
    {
        EntityType navPropertyType = navProperty.ToEndMember.GetEntityType();
        if(!knownNavPropertyTypes.Contains(navPropertyType))
        {
            knownNavPropertyTypes.Add(navPropertyType);
        }
    }
    foreach(EntityType knownNavPropertyType in knownNavPropertyTypes)
    {
#>
[KnownType(typeof(<#=code.Escape(knownNavPropertyType)#>))]
<#+
    }
}

bool IsSaveReference(MetadataTools tools, NavigationProperty navProperty)
{
    return !IsForeignKeyOrIdentifyingRelationship(tools, navProperty) &&
           navProperty.ToEndMember.RelationshipMultiplicity != RelationshipMultiplicity.Many &&         // Das Ziel ist 

ein Verweis
           navProperty.FromEndMember.RelationshipMultiplicity != RelationshipMultiplicity.One;          // Die Quelle 

lässt NULL-Werte zu (d. h. nicht PK)
}

string CreateFixupMethodName(RelationshipEndMember endMember)
{
    return String.Format(CultureInfo.InvariantCulture, "Fixup{0}_{1}_{2}Keys", endMember.DeclaringType.NamespaceName, 

endMember.DeclaringType.Name, endMember.Name);
}

string CreateKeyNameVariable(string keyName)
{
    return String.Format(CultureInfo.InvariantCulture, "{0}KeyName", keyName);
}

string CreateReferenceValueLookupKey(AssociationEndMember endMember, string keyName)
{
    return String.Format(CultureInfo.InvariantCulture, "Navigate({0}.{1}).{2}", endMember.DeclaringType.FullName, 

endMember.Name, keyName);
}

string CreateReferenceValueLookupKey(NavigationProperty navProp, string keyName)
{
    return String.Format(CultureInfo.InvariantCulture, "{0}.{1}", navProp.Name, keyName);
}

void WriteCustomObservableCollection()
{
#>

// Eine System.Collections.ObjectModel.ObservableCollection, die Folgendes auslöst:
// Benachrichtigungen zum Entfernen einzelner Elemente beim Löschen, und das Hinzufügen von Duplikaten wird verhindert.
public class TrackableCollection<T> : ObservableCollection<T>
{
    protected override void ClearItems()
    {
        new List<T>(this).ForEach(t => Remove(t));
    }

    protected override void InsertItem(int index, T item)
    {
        if (!this.Contains(item))
        {
            base.InsertItem(index, item);
        }
    }
}
<#+
}

void WriteObjectChangeTracker()
{
#>
// Hilfsprogrammklasse, die den Großteil der erforderlichen Nachverfolgungsschritte erfasst
// für Entitäten mit Selbstnachverfolgung.
[DataContract(IsReference = true)]
public class ObjectChangeTracker
{
    #region  Fields

    private bool _isDeserializing;
    private ObjectState _objectState = ObjectState.Added;
    private bool _changeTrackingEnabled;
    private OriginalValuesDictionary _originalValues;
    private ExtendedPropertiesDictionary _extendedProperties;
    private ObjectsAddedToCollectionProperties _objectsAddedToCollections = new ObjectsAddedToCollectionProperties();
    private ObjectsRemovedFromCollectionProperties _objectsRemovedFromCollections = new 

ObjectsRemovedFromCollectionProperties();

    #endregion

    #region Events

    public event EventHandler<ObjectStateChangingEventArgs> ObjectStateChanging;

    #endregion

    protected virtual void OnObjectStateChanging(ObjectState newState)
    {
        if (ObjectStateChanging != null)
        {
            ObjectStateChanging(this, new ObjectStateChangingEventArgs(){ NewState = newState });
        }
    }

    [DataMember]
    public ObjectState State
    {
        get { return _objectState; }
        set
        {
            if (_isDeserializing || _changeTrackingEnabled)
            {
                OnObjectStateChanging(value);
                _objectState = value;
            }
        }
    }

    public bool ChangeTrackingEnabled
    {
        get { return _changeTrackingEnabled; }
        set { _changeTrackingEnabled = value; }
    }

    // Gibt die entfernten Objekte an Eigenschaften mit einem Auflistungswert zurück, die geändert wurden.
    [DataMember]
    public ObjectsRemovedFromCollectionProperties ObjectsRemovedFromCollectionProperties
    {
        get
        {
            if (_objectsRemovedFromCollections == null)
            {
                _objectsRemovedFromCollections = new ObjectsRemovedFromCollectionProperties();
            }
            return _objectsRemovedFromCollections;
        }
    }

    // Gibt die ursprünglichen Werte für die geänderten Eigenschaften zurück.
    [DataMember]
    public OriginalValuesDictionary OriginalValues
    {
        get
        {
            if (_originalValues == null)
            {
                _originalValues = new OriginalValuesDictionary();
            }
            return _originalValues;
        }
    }

    // Gibt die erweiterten Eigenschaftswerte zurück.
    // Dies schließt Schlüsselwerte für unabhängige Zuordnungen ein, die erforderlich sind für
    // Parallelitätsmodell im Entity Framework
    [DataMember]
    public ExtendedPropertiesDictionary ExtendedProperties
    {
        get
        {
            if (_extendedProperties == null)
            {
                _extendedProperties = new ExtendedPropertiesDictionary();
            }
            return _extendedProperties;
        }
    }

    // Gibt die hinzugefügten Werte an Eigenschaften mit einem Auflistungswert zurück, die geändert wurden.
    [DataMember]
    public ObjectsAddedToCollectionProperties ObjectsAddedToCollectionProperties
    {
        get
        {
            if (_objectsAddedToCollections == null)
            {
                _objectsAddedToCollections = new ObjectsAddedToCollectionProperties();
            }
            return _objectsAddedToCollections;
        }
    }

    #region MethodsForChangeTrackingOnClient

    [OnDeserializing]
    public void OnDeserializingMethod(StreamingContext context)
    {
        _isDeserializing = true;
    }

    [OnDeserialized]
    public void OnDeserializedMethod(StreamingContext context)
    {
        _isDeserializing = false;
    }

    // Setzt ObjectChangeTracker auf den Zustand "Unverändert" zurück und
    // löscht die ursprünglichen Werte sowie die Aufzeichnung der Änderungen
    // an Sammlungseigenschaften
    public void AcceptChanges()
    {
        OnObjectStateChanging(ObjectState.Unchanged);
        OriginalValues.Clear();
        ObjectsAddedToCollectionProperties.Clear();
        ObjectsRemovedFromCollectionProperties.Clear();
        ChangeTrackingEnabled = true;
        _objectState = ObjectState.Unchanged;
    }

    // Erfasst den ursprünglichen Wert für eine Eigenschaft, die geändert wird.
    internal void RecordOriginalValue(string propertyName, object value)
    {
        if (_changeTrackingEnabled && _objectState != ObjectState.Added)
        {
            if (!OriginalValues.ContainsKey(propertyName))
            {
                OriginalValues[propertyName] = value;
            }
        }
    }

    // Zeichnet einen Hinzufügevorgang für Auflistungswerteigenschaften in SelfTracking-Entitäten auf.
    internal void RecordAdditionToCollectionProperties(string propertyName, object value)
    {
        if (_changeTrackingEnabled)
        {
            // Entität nach dem Löschen wieder hinzufügen (es wird empfohlen, hier keine weiteren Schritte auszuführen)
            if (ObjectsRemovedFromCollectionProperties.ContainsKey(propertyName)
                && ObjectsRemovedFromCollectionProperties[propertyName].Contains(value))
            {
                ObjectsRemovedFromCollectionProperties[propertyName].Remove(value);
                if (ObjectsRemovedFromCollectionProperties[propertyName].Count == 0)
                {
                    ObjectsRemovedFromCollectionProperties.Remove(propertyName);
                }
                return;
            }

            if (!ObjectsAddedToCollectionProperties.ContainsKey(propertyName))
            {
                ObjectsAddedToCollectionProperties[propertyName] = new ObjectList();
                ObjectsAddedToCollectionProperties[propertyName].Add(value);
            }
            else
            {
                ObjectsAddedToCollectionProperties[propertyName].Add(value);
            }
        }
    }

    // Zeichnet einen Entfernungsvorgang für Auflistungswerteigenschaften in SelfTracking-Entitäten auf.
    internal void RecordRemovalFromCollectionProperties(string propertyName, object value)
    {
        if (_changeTrackingEnabled)
        {
            // Entität nach dem Hinzufügen wieder löschen (es wird empfohlen, hier keine weiteren Schritte auszuführen)
            if (ObjectsAddedToCollectionProperties.ContainsKey(propertyName)
                && ObjectsAddedToCollectionProperties[propertyName].Contains(value))
            {
                ObjectsAddedToCollectionProperties[propertyName].Remove(value);
                if (ObjectsAddedToCollectionProperties[propertyName].Count == 0)
                {
                    ObjectsAddedToCollectionProperties.Remove(propertyName);
                }
                return;
            }

            if (!ObjectsRemovedFromCollectionProperties.ContainsKey(propertyName))
            {
                ObjectsRemovedFromCollectionProperties[propertyName] = new ObjectList();
                ObjectsRemovedFromCollectionProperties[propertyName].Add(value);
            }
            else
            {
                if (!ObjectsRemovedFromCollectionProperties[propertyName].Contains(value))
                {
                    ObjectsRemovedFromCollectionProperties[propertyName].Add(value);
                }
            }
        }
    }
    #endregion
}

#region EnumForObjectState
[Flags]
public enum ObjectState
{
    Unchanged = 0x1,
    Added = 0x2,
    Modified = 0x4,
    Deleted = 0x8
}
#endregion

[CollectionDataContract (Name = "ObjectsAddedToCollectionProperties",
    ItemName = "AddedObjectsForProperty", KeyName = "CollectionPropertyName", ValueName = "AddedObjects")]
public class ObjectsAddedToCollectionProperties : Dictionary<string, ObjectList> { }

[CollectionDataContract (Name = "ObjectsRemovedFromCollectionProperties",
    ItemName = "DeletedObjectsForProperty", KeyName = "CollectionPropertyName",ValueName = "DeletedObjects")]
public class ObjectsRemovedFromCollectionProperties : Dictionary<string, ObjectList> { }

[CollectionDataContract(Name = "OriginalValuesDictionary",
    ItemName = "OriginalValues", KeyName = "Name", ValueName = "OriginalValue")]
public class OriginalValuesDictionary : Dictionary<string, Object> { }

[CollectionDataContract(Name = "ExtendedPropertiesDictionary",
    ItemName = "ExtendedProperties", KeyName = "Name", ValueName = "ExtendedProperty")]
public class ExtendedPropertiesDictionary : Dictionary<string, Object> { }

[CollectionDataContract(ItemName = "ObjectValue")]
public class ObjectList : List<object> { }
<#+
}

void WriteINotifyComplexPropertyChanging()
{
#>

// Eine Schnittstelle, die ein Ereignis bereitstellt, das bei Änderungen von komplexen Eigenschaften ausgelöst wird.
// Änderungen können der Ersatz einer komplexen Eigenschaft mit einer neuen komplexen Typinstanz sein oder
// eine Änderung an einer skalaren Eigenschaft innerhalb einer komplexen Typinstanz.
public interface INotifyComplexPropertyChanging
{
    event EventHandler ComplexPropertyChanging;
}
<#+
}

void WriteIObjectWithChangeTracker()
{
#>
// Die Schnittstelle wird durch die Entitäten mit Selbstnachverfolgung implementiert, die von EF generiert werden.
// Es ist ein Adapter verfügbar, der die Schnittstelle in die von EF erwartete Schnittstelle konvertiert.
// Der Adapter befindet sich auf der Serverseite.
public interface IObjectWithChangeTracker
{
    // Besitzt alle Informationen zur Änderungsverfolgung für das untergeordnete Diagramm eines angegebenen Objekts.
    ObjectChangeTracker ChangeTracker { get; }
}

public class ObjectStateChangingEventArgs : EventArgs
{
    public ObjectState NewState { get; set; }
}

public static class ObjectWithChangeTrackerExtensions
{
    public static T MarkAsDeleted<T>(this T trackingItem) where T : IObjectWithChangeTracker
    {
        if (trackingItem == null)
        {
            throw new ArgumentNullException("trackingItem");
        }

        trackingItem.ChangeTracker.ChangeTrackingEnabled = true;
        trackingItem.ChangeTracker.State = ObjectState.Deleted;
        return trackingItem;
    }

    public static T MarkAsAdded<T>(this T trackingItem) where T : IObjectWithChangeTracker
    {
        if (trackingItem == null)
        {
            throw new ArgumentNullException("trackingItem");
        }

        trackingItem.ChangeTracker.ChangeTrackingEnabled = true;
        trackingItem.ChangeTracker.State = ObjectState.Added;
        return trackingItem;
    }

    public static T MarkAsModified<T>(this T trackingItem) where T : IObjectWithChangeTracker
    {
        if (trackingItem == null)
        {
            throw new ArgumentNullException("trackingItem");
        }

        trackingItem.ChangeTracker.ChangeTrackingEnabled = true;
        trackingItem.ChangeTracker.State = ObjectState.Modified;
        return trackingItem;
    }

    public static T MarkAsUnchanged<T>(this T trackingItem) where T : IObjectWithChangeTracker
    {
        if (trackingItem == null)
        {
            throw new ArgumentNullException("trackingItem");
        }

        trackingItem.ChangeTracker.ChangeTrackingEnabled = true;
        trackingItem.ChangeTracker.State = ObjectState.Unchanged;
        return trackingItem;
    }

    public static void StartTracking(this IObjectWithChangeTracker trackingItem)
    {
        if (trackingItem == null)
        {
            throw new ArgumentNullException("trackingItem");
        }

        trackingItem.ChangeTracker.ChangeTrackingEnabled = true;
    }

    public static void StopTracking(this IObjectWithChangeTracker trackingItem)
    {
        if (trackingItem == null)
        {
            throw new ArgumentNullException("trackingItem");
        }

        trackingItem.ChangeTracker.ChangeTrackingEnabled = false;
    }

    public static void AcceptChanges(this IObjectWithChangeTracker trackingItem)
    {
        if (trackingItem == null)
        {
            throw new ArgumentNullException("trackingItem");
        }

        trackingItem.ChangeTracker.AcceptChanges();
    }
}
<#+
}

void WriteEqualityComparer()
{
#>

public static class EqualityComparer
{
    // Hilfsmethode zum Bestimmen, ob zwei Bytearrays denselben Wert haben, auch wenn sie verschiedene Objektverweise sind
    public static bool BinaryEquals(object binaryValue1, object binaryValue2)
    {
        if (Object.ReferenceEquals(binaryValue1, binaryValue2))
        {
            return true;
        }

        byte[] array1 = binaryValue1 as byte[];
        byte[] array2 = binaryValue2 as byte[];

        if (array1 != null && array2 != null)
        {
            if (array1.Length != array2.Length)
            {
                return false;
            }

            for (int i = 0; i < array1.Length; i++)
            {
                if (array1[i] != array2[i])
                {
                    return false;
                }
            }

            return true;
        }

        return false;
    }
}
<#+
}

bool VerifyTypesAreCaseInsensitiveUnique(EdmItemCollection itemCollection)
{
    Dictionary<string, bool> alreadySeen = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
    foreach(StructuralType type in itemCollection.GetItems<StructuralType>())
    {
        if (!(type is EntityType || type is ComplexType))
        {
            continue;
        }

        if (alreadySeen.ContainsKey(type.FullName))
        {
            Error(String.Format(CultureInfo.CurrentCulture, "Die Vorlage bietet keine Unterstützung für Typen, die sich 

nur hinsichtlich Groß-/Kleinschreibung unterscheiden. Die {0}-Typen werden nicht unterstützt.", type.FullName));
            return false;
        }
        else
        {
            alreadySeen.Add(type.FullName, true);
        }

    }

    return true;
}

// "True", wenn die Zuordnung für die angegebene Navigationseigenschaft eine identifizierende Beziehung bzw. eine 

Fremdschlüsselbeziehung ist.
private bool IsForeignKeyOrIdentifyingRelationship(MetadataTools tools, NavigationProperty navProperty)
{
    if (tools == null)
    {
        throw new ArgumentNullException("tools");
    }

    if (navProperty == null)
    {
        throw new ArgumentNullException("navProperty");
    }

    return IsForeignKeyOrIdentifyingRelationship(tools, (AssociationType)navProperty.RelationshipType);
}

// "True", wenn die angegebene Zuordnung eine identifizierende Beziehung oder eine Fremdschlüsselbeziehung ist.
private bool IsForeignKeyOrIdentifyingRelationship(MetadataTools tools, AssociationType association)
{
    if (tools == null)
    {
        throw new ArgumentNullException("tools");
    }

    if (association == null)
    {
        throw new ArgumentNullException("association");
    }

    return association.IsForeignKey || tools.IsIdentifyingRelationship(association);
}

// Legen Sie recordRequiredOriginalValuesOnly im OriginalValueMembers-Konstruktor auf "false" fest, um immer alle 

ursprünglichen Werte aufzuzeichnen.
public class OriginalValueMembers
{
    private readonly HashSet<EdmProperty> _concurrencyMembers;

    public OriginalValueMembers(bool recordRequiredOriginalValuesOnly, MetadataWorkspace metadataWorkspace, MetadataTools 

metadataTools)
    {
        if (recordRequiredOriginalValuesOnly)
        {
            try
            {
                _concurrencyMembers = new HashSet<EdmProperty>();
                foreach (EntityContainer container in metadataWorkspace.GetItems<EntityContainer>(DataSpace.CSpace))
                {
                    ILookup<EntityType, EntityType> directSubTypeLookup = metadataWorkspace.GetItems<EntityType>

(DataSpace.CSpace).ToLookup(e => (EntityType)e.BaseType);
                    foreach (EntitySetBase eSet in container.BaseEntitySets.Where(es => es.BuiltInTypeKind == 

BuiltInTypeKind.EntitySet))
                    {
                        List<EntityType> subTypes = new List<EntityType>();
                        GetSubtypes(directSubTypeLookup, (EntityType)eSet.ElementType, subTypes);
                        foreach (EntityType eType in subTypes)
                        {
                            foreach (EdmProperty member in metadataWorkspace.GetRequiredOriginalValueMembers(eSet, eType))
                            {
                                _concurrencyMembers.Add(member);
                            }
                        }
                    }
                }

                // GetRequiredOriginalValueMembers gibt nicht immer Fremdschlüsseleigenschaften zurück, diese sind jedoch 

erforderlich.
                foreach (AssociationType assoc in metadataWorkspace.GetItems<AssociationType>(DataSpace.CSpace).Where(a => 

a.IsForeignKey))
                {
                    foreach (EdmProperty toProperty in assoc.ReferentialConstraints[0].ToProperties)
                    {
                        _concurrencyMembers.Add(toProperty);
                    }
                }
            }
            catch (Exception)
            {
                // Wenn Ausnahmen auftreten, sollen immer ursprüngliche Werte für alle Eigenschaften aufgezeichnet werden
                _concurrencyMembers = null;
            }
        }
    }

    public bool IsOriginalValueMember(EdmProperty edmProperty)
    {
        return _concurrencyMembers == null || _concurrencyMembers.Contains(edmProperty);
    }

    private static void GetSubtypes(ILookup<EntityType, EntityType> lookup, EntityType eType, List<EntityType> subTypes)
    {
        subTypes.Add(eType);
        foreach (EntityType subType in lookup[eType])
        {
            GetSubtypes(lookup, subType, subTypes);
        }
    }
}
#>

T4-Codeblock (CSharpSelfTracking.Context.tt)
<#@ template language="C#" debug="false" hostspecific="true"#>
<#@ include file="EF.Utility.CS.ttinclude"#><#@
 output extension=".cs"#><#
// Copyright (c) Microsoft Corporation.  All rights reserved.

CodeGenerationTools code = new CodeGenerationTools(this);
MetadataTools ef = new MetadataTools(this);
MetadataLoader loader = new MetadataLoader(this);
CodeRegion region = new CodeRegion(this);

EntityFrameworkTemplateFileManager fileManager = EntityFrameworkTemplateFileManager.Create(this);

string inputFile = @"$edmxInputFile$";
EdmItemCollection ItemCollection = loader.CreateEdmItemCollection(inputFile);
string namespaceName = code.VsNamespaceSuggestion();

EntityContainer container = ItemCollection.GetItems<EntityContainer>().FirstOrDefault();
if (container == null)
{
    return "// Im Modell ist kein EntityContainer vorhanden. Daher wurde kein Code generiert.";
}

WriteHeader(fileManager);
BeginNamespace(namespaceName, code);

#>
<#=Accessibility.ForType(container)#> partial class <#=code.Escape(container)#> : ObjectContext
{
    public const string ConnectionString = "name=<#=container.Name#>";
    public const string ContainerName = "<#=container.Name#>";

    #region Constructors

    public <#=code.Escape(container)#>()
        : base(ConnectionString, ContainerName)
    {
        Initialize();
    }

    public <#=code.Escape(container)#>(string connectionString)
        : base(connectionString, ContainerName)
    {
        Initialize();
    }

    public <#=code.Escape(container)#>(EntityConnection connection)
        : base(connection, ContainerName)
    {
        Initialize();
    }

    private void Initialize()
    {
        // Die Erstellung von Proxys erfordert die Verwendung des ProxyDataContractResolver und
        // kann Lazy Loading gestatten, wodurch das geladene Diagramm während der Serialisierung erweitert werden kann.
        ContextOptions.ProxyCreationEnabled = false;
        ObjectMaterialized += new ObjectMaterializedEventHandler(HandleObjectMaterialized);
    }

    private void HandleObjectMaterialized(object sender, ObjectMaterializedEventArgs e)
    {
        var entity = e.Entity as IObjectWithChangeTracker;
        if (entity != null)
        {
            bool changeTrackingEnabled = entity.ChangeTracker.ChangeTrackingEnabled;
            try
            {
                entity.MarkAsUnchanged();
            }
            finally
            {
                entity.ChangeTracker.ChangeTrackingEnabled = changeTrackingEnabled;
            }
            this.StoreReferenceKeyValues(entity);
        }
    }

    #endregion

<#
        region.Begin("ObjectSet-Eigenschaften", 2);

        foreach (EntitySet entitySet in container.BaseEntitySets.OfType<EntitySet>())
        {
#>

    <#=Accessibility.ForReadOnlyProperty(entitySet)#> ObjectSet<<#=code.Escape(entitySet.ElementType)#>> <#=code.Escape

(entitySet)#>
    {
        get { return <#=code.FieldName(entitySet) #>  ?? (<#=code.FieldName(entitySet)#> = CreateObjectSet<<#=code.Escape

(entitySet.ElementType)#>>("<#=entitySet.Name#>")); }
    }
    private ObjectSet<<#=code.Escape(entitySet.ElementType)#>> <#=code.FieldName(entitySet)#>;
<#
        }

        region.End();

        region.Begin("Funktionsimporte");

        foreach (EdmFunction edmFunction in container.FunctionImports)
        {
            var parameters = FunctionImportParameter.Create(edmFunction.Parameters, code, ef);
            string paramList = String.Join(", ", parameters.Select(p => p.FunctionParameterType + " " + 

p.FunctionParameterName).ToArray());
            if (edmFunction.ReturnParameter == null)
            {
                continue;
            }
            string returnTypeElement = code.Escape(ef.GetElementType(edmFunction.ReturnParameter.TypeUsage));

#>
    <#=AccessibilityAndVirtual(Accessibility.ForMethod(edmFunction))#> ObjectResult<<#=returnTypeElement#>> 

<#=code.Escape(edmFunction)#>(<#=paramList#>)
    {
<#
            foreach (var parameter in parameters)
            {
                if (!parameter.NeedsLocalVariable)
                {
                    continue;
                }
#>

        ObjectParameter <#=parameter.LocalVariableName#>;

        if (<#=parameter.IsNullableOfT ? parameter.FunctionParameterName + ".HasValue" : parameter.FunctionParameterName + 

" != null"#>)
        {
            <#=parameter.LocalVariableName#> = new ObjectParameter("<#=parameter.EsqlParameterName#>", 

<#=parameter.FunctionParameterName#>);
        }
        else
        {
            <#=parameter.LocalVariableName#> = new ObjectParameter("<#=parameter.EsqlParameterName#>", typeof

(<#=parameter.RawClrTypeName#>));
        }
<#
            }
#>
        return base.ExecuteFunction<<#=returnTypeElement#>>("<#=edmFunction.Name#>"<#=code.StringBefore(", ", String.Join

(", ", parameters.Select(p => p.ExecuteParameterName).ToArray()))#>);
    }
<#
        }
        region.End();
#>
}
<#
    EndNamespace(namespaceName);

    fileManager.StartNewFile(Path.GetFileNameWithoutExtension(Host.TemplateFile) + ".Extensions.cs");
    BeginNamespace(namespaceName, code);
    WriteApplyChanges(code);
    EndNamespace(namespaceName);

    fileManager.Process();
#>
<#+
private void WriteHeader(EntityFrameworkTemplateFileManager fileManager, params string[] extraUsings)
{
    fileManager.StartHeader();
#>
//------------------------------------------------------------------------------
// <auto-generated>
//     Der Code wurde aus einer Vorlage generiert.
//
//     Änderungen an dieser Datei führen möglicherweise zu falschem Verhalten und gehen verloren, falls
//     der Code erneut generiert wird.
// </auto-generated>
//------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data.Common;
using System.Data.EntityClient;
using System.Data.Metadata.Edm;
using System.Data.Objects.DataClasses;
using System.Data.Objects;
using System.Data;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
<#=String.Join(String.Empty, extraUsings.Select(u => "using " + u + ";" + Environment.NewLine).ToArray())#>
<#+
    fileManager.EndBlock();
}

void BeginNamespace(string namespaceName, CodeGenerationTools code)
{
    CodeRegion region = new CodeRegion(this);
    if (!String.IsNullOrEmpty(namespaceName))
    {
#>
namespace <#=code.EscapeNamespace(namespaceName)#>
{
<#+
        PushIndent(CodeRegion.GetIndent(1));
    }
}

void EndNamespace(string namespaceName)
{
    if (!String.IsNullOrEmpty(namespaceName))
    {
        PopIndent();
#>
}
<#+
    }
}

string AccessibilityAndVirtual(string accessibility)
{
    if (accessibility != "private")
    {
        return accessibility + " virtual";
    }

    return accessibility;
}
#>

<#+
void WriteApplyChanges(CodeGenerationTools code)
{
#>
public static class SelfTrackingEntitiesContextExtensions
{
    /// <summary>
    /// ApplyChanges übernimmt die Änderungen in einer verbundenen Reihe von Entitäten und wendet diese auf einen 

ObjectContext an.
    /// </summary>
    /// <typeparam name="TEntity">Erwarteter Typ von ObjectSet</typeparam>
    /// <param name="objectSet">Der ObjectSet, der auf den ObjectContext verweist, auf den Änderungen angewendet 

werden.</param>
    /// <param name="entity">Die Entität, die als Einstiegspunkt des Objektgraphen dient, der Änderungen enthält.</param>
    public static void ApplyChanges<TEntity>(this ObjectSet<TEntity> objectSet, TEntity entity) where TEntity : class, 

IObjectWithChangeTracker
    {
        if (objectSet == null)
        {
            throw new ArgumentNullException("objectSet");
        }

        objectSet.Context.ApplyChanges<TEntity>(objectSet.EntitySet.EntityContainer.Name + "." + objectSet.EntitySet.Name, 

entity);
    }

    /// <summary>
    /// ApplyChanges übernimmt die Änderungen in einer verbundenen Reihe von Entitäten und wendet diese auf einen 

ObjectContext an.
    /// </summary>
    /// <typeparam name="TEntity">Erwarteter Typ von EntitySet</typeparam>
    /// <param name="context">Der ObjectContext, auf den Änderungen angewendet werden.</param>
    /// <param name="entitySetName">Der EntitySet-Name der Entität.</param>
    /// <param name="entity">Die Entität, die als Einstiegspunkt des Objektgraphen dient, der Änderungen enthält.</param>
    public static void ApplyChanges<TEntity>(this ObjectContext context, string entitySetName, TEntity entity) where 

TEntity : IObjectWithChangeTracker
    {
        if (context == null)
        {
            throw new ArgumentNullException("context");
        }

        if (String.IsNullOrEmpty(entitySetName))
        {
            throw new ArgumentException("Der Zeichenfolgenparameter darf nicht NULL oder leer sein.", "entitySetName");
        }

        if (entity == null)
        {
            throw new ArgumentNullException("entity");
        }

        bool lazyLoadingSetting = context.ContextOptions.LazyLoadingEnabled;
        try
        {
            context.ContextOptions.LazyLoadingEnabled = false;

            EntityIndex entityIndex = AddHelper.AddAllEntities(context, entitySetName, entity);
            RelationshipSet allRelationships = new RelationshipSet(context, entityIndex.AllEntities);

            #region Handle Initial Entity State

            foreach (IObjectWithChangeTracker changedEntity in entityIndex.AllEntities.Where(x => x.ChangeTracker.State == 

ObjectState.Deleted))
            {
                HandleDeletedEntity(context, entityIndex, allRelationships, changedEntity);
            }

            foreach (IObjectWithChangeTracker changedEntity in entityIndex.AllEntities.Where(x => x.ChangeTracker.State != 

ObjectState.Deleted))
            {
                HandleEntity(context, entityIndex, allRelationships, changedEntity);
            }

            #endregion

            #region Loop through each object state entries

            foreach (IObjectWithChangeTracker changedEntity in entityIndex.AllEntities)
            {
                ObjectStateEntry entry = context.ObjectStateManager.GetObjectStateEntry(changedEntity);

                EntityType entityType = context.MetadataWorkspace.GetCSpaceEntityType(changedEntity.GetType());

                foreach (NavigationProperty navProp in entityType.NavigationProperties)
                {
                    RelatedEnd relatedEnd = entry.GetRelatedEnd(navProp.Name);
                    if(!((AssociationType)relatedEnd.RelationshipSet.ElementType).IsForeignKey)
                    {
                        ApplyChangesToIndependentAssociation(context, (IObjectWithChangeTracker)changedEntity, entry, 

navProp, relatedEnd, allRelationships);
                    }

                }
            }
            #endregion

            // Alle verbleibenden Beziehungen wieder in den geeigneten Zustand ändern
            foreach (var relationship in allRelationships)
            {
                context.ObjectStateManager.ChangeRelationshipState(
                    relationship.End0,
                    relationship.End1,
                    relationship.AssociationSet.ElementType.FullName,
                    relationship.AssociationEndMembers[1].Name,
                    relationship.State);
            }
        }
        finally
        {
            context.ContextOptions.LazyLoadingEnabled = lazyLoadingSetting;
        }
    }

    private static void ApplyChangesToIndependentAssociation(ObjectContext context, IObjectWithChangeTracker 

changedEntity, ObjectStateEntry entry, NavigationProperty navProp,
        IRelatedEnd relatedEnd, RelationshipSet allRelationships)
    {
        ObjectChangeTracker changeTracker = changedEntity.ChangeTracker;

        if (changeTracker.State == ObjectState.Added)
        {
            // Beziehungen sollten hinzugefügt bleiben. Entfernen Sie sie daher aus der Liste von allRelationships
            foreach (object relatedEntity in relatedEnd)
            {
                ObjectStateEntry addedRelationshipEntry =
                            context.ObjectStateManager.ChangeRelationshipState(
                                changedEntity,
                                relatedEntity,
                                navProp.Name,
                                EntityState.Added);

                allRelationships.Remove(addedRelationshipEntry);
            }
        }
        else
        {
            if (navProp.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many)
            {
                //Entfernungsvorgang für FixupCollections behandeln
                ObjectList collectionPropertyChanges = null;
                if (changeTracker.ObjectsRemovedFromCollectionProperties.TryGetValue(navProp.Name, out 

collectionPropertyChanges))
                {
                    foreach (var removedEntityFromAssociation in collectionPropertyChanges)
                    {
                        ObjectStateEntry deletedRelationshipEntry =
                            context.ObjectStateManager.ChangeRelationshipState(
                                changedEntity,
                                removedEntityFromAssociation,
                                navProp.Name,
                                EntityState.Deleted);

                        allRelationships.Remove(deletedRelationshipEntry);
                    }
                }

                //Hinzufügevorgang für FixupCollection behandeln
                if (changeTracker.ObjectsAddedToCollectionProperties.TryGetValue(navProp.Name, out 

collectionPropertyChanges))
                {
                    foreach (var addedEntityFromAssociation in collectionPropertyChanges)
                    {
                        ObjectStateEntry addedRelationshipEntry =
                            context.ObjectStateManager.ChangeRelationshipState(
                                changedEntity,
                                addedEntityFromAssociation,
                                navProp.Name,
                                EntityState.Added);

                        allRelationships.Remove(addedRelationshipEntry);
                    }
                }
            }
            else
            {

                // Ursprüngliche Beziehungswerte behandeln
                object originalReferenceValue;
                if (changeTracker.OriginalValues.TryGetValue(navProp.Name, out originalReferenceValue))
                {
                    if (originalReferenceValue != null)
                    {
                        //Löschen der Zuordnung erfassen
                        ObjectStateEntry deletedRelationshipEntry =
                            context.ObjectStateManager.ChangeRelationshipState(
                                entry.Entity,
                                originalReferenceValue,
                                navProp.Name,
                                EntityState.Deleted);

                        allRelationships.Remove(deletedRelationshipEntry);
                    }

                    //Hinzufügen der Zuordnung erfassen
                    object currentReferenceValue = null;
                    foreach (object o in relatedEnd)
                    {
                        currentReferenceValue = o;
                        break;
                    }
                    if (currentReferenceValue != null)
                    {
                        ObjectStateEntry addedRelationshipEntry =
                            context.ObjectStateManager.ChangeRelationshipState(
                                changedEntity,
                                currentReferenceValue,
                                navProp.Name,
                                EntityState.Added);

                        allRelationships.Remove(addedRelationshipEntry);
                    }
                    // wenn der aktuelle Wert des Verweises NULL ist, muss der Benutzer den Entitätsverweis auf NULL 

festlegen
                    // der bereits vom Löschen der Beziehung behandelt wird
                }
            }
        }
    }

    // Extrahiert die Beziehungsschlüsselinformationen aus den ExtendedProperties- und OriginalValues-Datensätzen jedes 

ObjectChangeTracker.
    // Dies wird folgendermaßen ausgeführt:
    //  1. Erstellen einer vorhandenen Beziehung, die in den ExtendedProperties angegeben wird
    //  2. Bestimmen, ob eine vorherige Beziehung vorhanden war, und ob eine gelöschte Beziehung zwischen der Entität und 

der vorherigen Entität oder des Schlüsselwerts erstellt wurde
    private static void HandleRelationshipKeys(ObjectContext context, EntityIndex entityIndex, RelationshipSet 

allRelationships, IObjectWithChangeTracker entity)
    {
        ObjectChangeTracker changeTracker = entity.ChangeTracker;
        if (changeTracker.State == ObjectState.Unchanged ||
            changeTracker.State == ObjectState.Modified ||
            changeTracker.State == ObjectState.Deleted)
        {
            ObjectStateEntry entry = context.ObjectStateManager.GetObjectStateEntry(entity);
            EntityType entityType = context.MetadataWorkspace.GetCSpaceEntityType(entity.GetType());
            RelationshipManager relationshipManager = context.ObjectStateManager.GetRelationshipManager(entity);

            foreach (var entityReference in EnumerateSaveReferences(relationshipManager))
            {
                AssociationSet associationSet = ((AssociationSet)entityReference.RelationshipSet);
                AssociationEndMember fromEnd = associationSet.AssociationSetEnds

[entityReference.SourceRoleName].CorrespondingAssociationEndMember;
                AssociationEndMember toEnd = associationSet.AssociationSetEnds

[entityReference.TargetRoleName].CorrespondingAssociationEndMember;

                // Feststellen, ob für den Kandidat eine NavigationProperty vorhanden ist
                NavigationProperty navigationProperty = entityType.NavigationProperties.
                                           SingleOrDefault(x => x.RelationshipType == associationSet.ElementType &&
                                                           x.FromEndMember == fromEnd &&
                                                           x.ToEndMember == toEnd);

                // Nur Beziehungsschlüssel in einem dieser Fälle behandeln
                // 1. Es ist keine Navigationseigenschaft vorhanden
                // 2. Die Navigationseigenschaft besitzt einen aktuellen NULL-Verweiswert, und es finden keine 

Entfernungs- oder Hinzufügevorgänge statt
                // 3. Die Navigationseigenschaft besitzt einen aktuellen Verweiswert, doch es findet kein 

Entfernungsvorgang statt

                EntityKey currentKey = GetSavedReferenceKey(entityIndex, entityReference, entity, navigationProperty, 

changeTracker.ExtendedProperties);

                // Jeden ursprünglichen Wert aus den Änderungsverfolgungsinformationen abrufen
                object originalValue = null;
                EntityKey originalKey = null;
                bool hasOriginalValue = false;
                if (changeTracker.OriginalValues != null)
                {
                    // Zuerst den ursprünglichen Wert von der NavigationProperty abrufen
                    if (navigationProperty != null)
                    {
                        hasOriginalValue = changeTracker.OriginalValues.TryGetValue(navigationProperty.Name, out 

originalValue);
                    }
                    // Den ursprünglichen Wert im zweiten Schritt vom Verweisschlüssel abrufen
                    if (!hasOriginalValue || originalValue == null)
                    {
                        originalKey = GetSavedReferenceKey(entityIndex, entityReference, entity, navigationProperty, 

changeTracker.OriginalValues);
                    }
                }

                // Aktuelle Beziehung erstellen
                if (currentKey != null)
                {
                    // Wenn der Schlüssel für eine gelöschte Entität ist, verschieben Sie diesen Schlüssel zu einem 

originalValue, und beheben Sie die Werte des Entitätenschlüssels
                    // Erstellen Sie andernfalls eine neue Beziehung
                    ObjectStateEntry currentEntry;
                    if (context.ObjectStateManager.TryGetObjectStateEntry(currentKey, out currentEntry) &&
                       currentEntry.Entity != null &&
                       currentEntry.State == EntityState.Deleted)
                    {
                        entityReference.EntityKey = null;
                        MoveSavedReferenceKey(entityReference, entity, navigationProperty, 

changeTracker.ExtendedProperties, changeTracker.OriginalValues);
                        originalKey = currentKey;
                    }
                    else
                    {
                        CreateRelationship(context, entityReference, entry.EntityKey, currentKey, originalKey == null ? 

EntityState.Unchanged : EntityState.Added);
                    }
                }
                else
                {
                    // Aktuellen Schlüssel suchen
                    // Der EntityKey kann nicht direkt abgerufen werden, da er NULL ist, wenn er auf eine Entität mit dem 

Namen "Hinzugefügt" zeigt
                    currentKey = entityReference.GetCurrentEntityKey(context);
                }

                // Ursprüngliche Beziehung erstellen
                if (originalKey != null)
                {
                    // Wenn der Schlüssel für eine gelöschte Entität ist, denken Sie daran, eine gelöschte Beziehung zu 

erstellen.
                    // Verwenden Sie andernfalls entityReference, um die gelöschte Beziehung einzurichten.
                    ObjectStateEntry originalEntry = null;
                    ObjectStateEntry deletedRelationshipEntry = null;
                    if (context.ObjectStateManager.TryGetObjectStateEntry(originalKey, out originalEntry) &&
                       originalEntry.Entity != null &&
                       originalEntry.State == EntityState.Deleted)
                    {
                        allRelationships.Add(entityReference, entry.Entity, originalEntry.Entity, EntityState.Deleted);
                    }
                    else
                    {
                        // Wenn Sie eine gelöschte Beziehung für einen Schlüssel erstellen möchten, trennen Sie zuerst die 

vorhandene Beziehung zwischen "entry" und "currentKey"
                        EntityState currentRelationshipState = DetachRelationship(context, entityReference, entry, 

currentKey);

                        // Wenn die Beziehung 1 bis 0..1 ist, trennen Sie die Beziehung von currentKey bis zu ihrem Ziel 

(targetKey)
                        EntityState targetRelationshipState = EntityState.Detached;
                        EntityReference targetReference = null;
                        EntityKey targetKey = null;
                        if (originalEntry != null &&
                            originalEntry.Entity != null &&
                            originalEntry.RelationshipManager != null &&
                            associationSet.AssociationSetEnds

[fromEnd.Name].CorrespondingAssociationEndMember.RelationshipMultiplicity != RelationshipMultiplicity.Many)
                        {
                            targetReference = originalEntry.RelationshipManager.GetRelatedEnd

(entityReference.RelationshipName, entityReference.SourceRoleName) as EntityReference;
                            targetKey = targetReference.GetCurrentEntityKey(context);
                            if (targetKey != null)
                            {
                                targetRelationshipState = DetachRelationship(context, targetReference, originalEntry, 

targetKey);
                            }
                        }


                        // Erstellen Sie die gelöschte Beziehung zwischen "entry" und "originalKey"
                        deletedRelationshipEntry = CreateRelationship(context, entityReference, entry.EntityKey, 

originalKey, EntityState.Deleted);

                        // Setzen Sie die vorherige Beziehung zwischen entry und currentKey zurück
                        CreateRelationship(context, entityReference, entry.EntityKey, currentKey, 

currentRelationshipState);

                        // Setzen Sie die vorherige Beziehung zwischen originalEntry und targetKey zurück
                        if (targetKey != null)
                        {
                            CreateRelationship(context, targetReference, originalEntry.EntityKey, targetKey, 

targetRelationshipState);
                        }
                    }
                    if (deletedRelationshipEntry != null)
                    {
                        // Entfernen Sie die gelöschte Beziehung aus denen, die später in ApplyChanges verarbeitet werden 

müssen
                        allRelationships.Remove(deletedRelationshipEntry);
                    }
                }
                else if (currentKey == null && originalValue != null && 

entityReference.IsDependentEndOfReferentialConstraint())
                {
                    // dies wird nicht in das Diagramm eingebunden, da kein aktueller Wert vorhanden ist. Es ist jedoch 

ein ursprünglicher Wert vorhanden,
                    // sodass der Code für die Beziehungsverarbeitung eine Beziehung löschen möchte.
                    // Dies kann hinzugefügt werden, damit eine Beziehung in den Status "Gelöscht" geändert wird.
                    context.ObjectStateManager.ChangeRelationshipState(
                                                        entry.Entity,
                                                        originalValue,
                                                        entityReference.RelationshipName,
                                                        entityReference.TargetRoleName,
                                                        EntityState.Added);
                }
            }
        }
    }

    private static ObjectStateEntry CreateRelationship(ObjectContext context, EntityReference entityReference, EntityKey 

fromKey, EntityKey toKey, EntityState state)
    {
        if (state != EntityState.Detached)
        {
            AssociationSet associationSet = ((AssociationSet)entityReference.RelationshipSet);
            AssociationEndMember fromEnd = associationSet.AssociationSetEnds

[entityReference.SourceRoleName].CorrespondingAssociationEndMember;
            AssociationEndMember toEnd = associationSet.AssociationSetEnds

[entityReference.TargetRoleName].CorrespondingAssociationEndMember;

            // die Beziehung auf die ursprüngliche Beziehung im Status "Unverändert" festlegen
            Debug.Assert(toKey != null, "Warum/Wie wird ein Löschvorgang mit einem originalKey gleich NULL ausgeführt?");

            if (toKey.IsTemporary)
            {
                // Alle vorhandenen Beziehungen löschen
                entityReference.EntityKey = null;

                // Wenn die Zielentität "Hinzugefügt" ist, verwenden Sie "Hinzufügen" für RelatedEnd
                ObjectStateEntry targetEntry;
                context.ObjectStateManager.TryGetObjectStateEntry(toKey, out targetEntry);
                Debug.Assert(targetEntry != null, "Hätte den Zustandseintrag finden sollen");
                ((IRelatedEnd)entityReference).Add(targetEntry.Entity);
            }
            else
            {
                entityReference.EntityKey = toKey;
            }

            ObjectStateEntry relationshipEntry;
            bool found = context.TryGetObjectStateEntry(fromKey, toKey, associationSet, fromEnd, toEnd, out 

relationshipEntry);
            Debug.Assert(found, "Die erstellte Beziehung wurde nicht gefunden.");

            switch (state)
            {
                case EntityState.Added:
                    break;
                case EntityState.Unchanged:
                    relationshipEntry.AcceptChanges();
                    break;
                case EntityState.Deleted:
                    relationshipEntry.AcceptChanges();
                    entityReference.EntityKey = null;
                    break;
            }
            return relationshipEntry;
        }
        return null;
    }

    private static EntityState DetachRelationship(ObjectContext context, EntityReference entityReference, ObjectStateEntry 

fromEntry, EntityKey toKey)
    {
        EntityState currentRelationshipState = EntityState.Detached;

        if (toKey != null)
        {
            AssociationSet associationSet = ((AssociationSet)entityReference.RelationshipSet);
            AssociationEndMember fromEnd = associationSet.AssociationSetEnds

[entityReference.SourceRoleName].CorrespondingAssociationEndMember;
            AssociationEndMember toEnd = associationSet.AssociationSetEnds

[entityReference.TargetRoleName].CorrespondingAssociationEndMember;

            ObjectStateEntry currentRelationshipEntry = null;

            if (context.TryGetObjectStateEntry(fromEntry.EntityKey, toKey, associationSet, fromEnd, toEnd, out 

currentRelationshipEntry))
            {
                currentRelationshipState = currentRelationshipEntry.State;

                entityReference.EntityKey = null;
                if (currentRelationshipEntry.State == EntityState.Deleted)
                {
                    currentRelationshipEntry.AcceptChanges();
                }
                Debug.Assert(currentRelationshipEntry.State == EntityState.Detached, "Die Beziehung wurde nicht 

getrennt.");
            }
        }
        return currentRelationshipState;
    }

    private static string CreateReferenceKeyLookup(string keyMemberName, EntityReference reference, NavigationProperty 

navigationProperty)
    {
        // verwenden Sie den benutzerfreundlicheren Navigationseigenschaftsnamen, um den Member zu qualifizieren
        // falls verfügbar
        if (navigationProperty != null)
        {
            return String.Format(CultureInfo.InvariantCulture, "{0}.{1}", navigationProperty.Name, keyMemberName);
        }
        else
        {
            return String.Format(CultureInfo.InvariantCulture, "Navigate({0}.{1}).{2}", 

reference.RelationshipSet.ElementType.FullName, reference.TargetRoleName, keyMemberName);
        }
    }

    // ruft den Schlüssel ab, der dem übergebenen EntityReference entspricht
    // diese Schlüssel können während des ObjectMaterialized-Ereignisses oder durch Beziehungs-Fixup festgelegt werden
    private static EntityKey GetSavedReferenceKey(EntityIndex entityIndex, EntityReference reference, object entity, 

NavigationProperty navigationProperty, IDictionary<string, object> values)
    {
        Debug.Assert(navigationProperty == null || reference.RelationshipSet.ElementType == 

navigationProperty.RelationshipType, "der Verweis und die navigationProperty sollten sich entsprechen");

        EntitySet entitySet = ((AssociationSet)reference.RelationshipSet).AssociationSetEnds

[reference.TargetRoleName].EntitySet;

        List<EntityKeyMember> foundKeyMembers = new List<EntityKeyMember>(1);
        bool foundNone = true;
        bool missingSome = false;
        foreach (var keyMember in entitySet.ElementType.KeyMembers)
        {
            string lookupKey = CreateReferenceKeyLookup(keyMember.Name, reference, navigationProperty);
            object value;
            if (values.TryGetValue(lookupKey, out value))
            {
                foundKeyMembers.Add(new EntityKeyMember(keyMember.Name, value));
                foundNone = false;
            }
            else
            {
                missingSome = true;
            }
        }

        if (foundNone)
        {
            // es wurde kein Schlüssel gefunden
            return null;
        }
        else if (missingSome)
        {
            throw new InvalidOperationException(
                String.Format(
                    CultureInfo.CurrentCulture,
                    "Die OriginalValues-Auflistung oder ExtendedProperties-Auflistung für den Typ '{0}' enthielt nur einen 

Teilschlüssel, um der Beziehung '{1}' zu entsprechen, die auf die Rolle '{2}' abzielt.",
                    entity.GetType().FullName,
                    reference.RelationshipName,
                    reference.TargetRoleName));
        }

        EntityKey key = entityIndex.ConvertEntityKey(new EntityKey(reference.GetEntitySetName(), foundKeyMembers));
        return key;
    }

    // Verschiebt den Schlüssel, der dem übergebenen EntityReference entspricht, von einer Quellauflistung in eine 

Zielauflistung
    private static void MoveSavedReferenceKey(EntityReference reference, object entity, NavigationProperty 

navigationProperty, IDictionary<string, object> sourceValues, IDictionary<string, object> targetValues)
    {
        Debug.Assert(navigationProperty == null || reference.RelationshipSet.ElementType == 

navigationProperty.RelationshipType, " reference und navigationProperty sollten einander entsprechen");

        EntitySet entitySet = ((AssociationSet)reference.RelationshipSet).AssociationSetEnds

[reference.TargetRoleName].EntitySet;

        bool missingSome = false;
        foreach (var keyMember in entitySet.ElementType.KeyMembers)
        {
            string lookupKey = CreateReferenceKeyLookup(keyMember.Name, reference, navigationProperty);
            object value;
            if (sourceValues.TryGetValue(lookupKey, out value))
            {
                if (targetValues.ContainsKey(lookupKey))
                {
                    targetValues[lookupKey] = value;
                }
                else
                {
                    targetValues.Add(lookupKey, value);
                }
                sourceValues.Remove(lookupKey);
            }
            else
            {
                missingSome = true;
            }
        }

        if (missingSome)
        {
            throw new InvalidOperationException(
                String.Format(
                    CultureInfo.CurrentCulture,
                    " Die OriginalValues-Auflistung oder ExtendedProperties-Auflistung für den Typ '{0}' enthielt nur 

einen Teilschlüssel, um der Beziehung '{1}' zu entsprechen, die auf die Rolle '{2}' abzielt.",
                    entity.GetType().FullName,
                    reference.RelationshipName,
                    reference.TargetRoleName));
        }
    }

    private static IEnumerable<EntityReference> EnumerateSaveReferences(RelationshipManager manager)
    {
        return manager.GetAllRelatedEnds().OfType<EntityReference>()
                .Where(er => er.RelationshipSet.ElementType.RelationshipEndMembers

[er.SourceRoleName].RelationshipMultiplicity != RelationshipMultiplicity.One &&
                    !((AssociationSet)er.RelationshipSet).ElementType.IsForeignKey);
    }

    internal static void StoreReferenceKeyValues(this ObjectContext context, IObjectWithChangeTracker entity)
    {
        if(entity == null)
        {
            throw new ArgumentNullException("entity");
        }

        ObjectStateEntry entry;
        if (!context.ObjectStateManager.TryGetObjectStateEntry(entity, out entry))
        {
            // muss eine nicht nachverfolgungsbezogene Abfrage sein. Die Informationen zum Verweisschlüssel sind nicht 

verfügbar
            return;
        }

        var relationshipManager = entry.RelationshipManager;
        EntityType entityType = context.MetadataWorkspace.GetCSpaceEntityType(entity.GetType());
        foreach (EntityReference entityReference in EnumerateSaveReferences(relationshipManager))
        {
            NavigationProperty navigationProperty = entityType.NavigationProperties.FirstOrDefault(n => n.RelationshipType 

== entityReference.RelationshipSet.ElementType &&
                    n.FromEndMember.Name == entityReference.SourceRoleName &&
                    n.ToEndMember.Name == entityReference.TargetRoleName);

            object value = entityReference.GetValue();
            if ((navigationProperty == null || value == null) && entityReference.EntityKey != null)
            {
                foreach (var item in entityReference.EntityKey.EntityKeyValues)
                {
                    string key = CreateReferenceKeyLookup(item.Key, entityReference, navigationProperty);
                    entity.ChangeTracker.ExtendedProperties.Add(key, item.Value);
                }
            }
        }
    }

    private static void HandleEntity(ObjectContext context, EntityIndex entityIndex, RelationshipSet allRelationships, 

IObjectWithChangeTracker entity)
    {
        ChangeEntityStateBasedOnObjectState(context, entity);
        HandleRelationshipKeys(context, entityIndex, allRelationships, entity);
        UpdateOriginalValues(context, entity);
    }

    private static void HandleDeletedEntity(ObjectContext context, EntityIndex entityIndex, RelationshipSet 

allRelationships, IObjectWithChangeTracker entity)
    {
        HandleRelationshipKeys(context, entityIndex, allRelationships, entity);
        ChangeEntityStateBasedOnObjectState(context, entity);
        UpdateOriginalValues(context, entity);
    }

    private static void UpdateOriginalValues(ObjectContext context, IObjectWithChangeTracker entity)
    {
        if (entity.ChangeTracker.State == ObjectState.Unchanged ||
            entity.ChangeTracker.State == ObjectState.Added ||
            entity.ChangeTracker.OriginalValues == null)
        {
            // keine Schritte erforderlich
            return;
        }

        // es müssen nur skalare und komplexe Eigenschaften bearbeitet werden

        ObjectStateEntry entry = context.ObjectStateManager.GetObjectStateEntry(entity);
        OriginalValueRecord originalValueRecord = entry.GetUpdatableOriginalValues();
        EntityType entityType = context.MetadataWorkspace.GetCSpaceEntityType(entity.GetType());

        // jede Eigenschaft überprüfen und feststellen, ob ein ursprünglicher Wert dafür vorhanden ist
        // Wird in diesem Fall festgelegt. Legen Sie in den ComplexType-Eigenschaften ursprüngliche Werte fest
        // auch für jedes Element
        //
        // es wird erwartet, dass die ursprünglichen Werte eine geringe Dichte aufweisen, da versucht wird,
        // nur die ursprünglichen Werte für die erforderlichen Objekte zu erfassen (Parallelität, Prozedur, Bedingung, 

zusätzliche?)
        foreach(EdmProperty property in entityType.Properties)
        {
            object value;
            if(property.TypeUsage.EdmType is PrimitiveType && entity.ChangeTracker.OriginalValues.TryGetValue

(property.Name, out value))
            {
                originalValueRecord.SetValue(property, value);
            }
            else if(property.TypeUsage.EdmType is ComplexType)
            {
                OriginalValueRecord complexOriginalValues = originalValueRecord.GetOriginalValueRecord(property.Name);
                UpdateOriginalValues((ComplexType)property.TypeUsage.EdmType, entity.GetType().FullName, property.Name, 

entity.ChangeTracker.OriginalValues, complexOriginalValues);
            }
        }
    }

    private static void UpdateOriginalValues(ComplexType complexType, string entityTypeName, string propertyPathToType, 

IDictionary<string, object> originalValueSource, OriginalValueRecord complexOriginalValueRecord)
    {
        // complexOriginalValueRecord ist möglicherweise NULL
        // ein complexOriginalValueRecord mit dem Wert NULL tritt nur auf, wenn ein NULL-Verweis
        // einer ComplexType-Eigenschaft zugewiesen und anschließend an ApplyChanges übergeben wird.
        //
        // jede Eigenschaft überprüfen und feststellen, ob ein ursprünglicher Wert dafür vorhanden ist
        // Wird in diesem Fall festgelegt. Legen Sie in den ComplexType-Eigenschaften ursprüngliche Werte fest
        // auch für jedes Element
        foreach (EdmProperty property in complexType.Properties)
        {
            object value;
            string propertyPath = String.Format(CultureInfo.InvariantCulture, "{0}.{1}", propertyPathToType, 

property.Name);
            if (property.TypeUsage.EdmType is PrimitiveType && originalValueSource.TryGetValue(propertyPath, out value))
            {
                if (complexOriginalValueRecord != null)
                {
                    complexOriginalValueRecord.SetValue(property, value);
                }
                else if (value != null)
                {
                    Debug.Assert(complexOriginalValueRecord == null, "wird nur ausgelöst, wenn der Wert nicht NULL und der 

Datensatz NULL ist");
                    throw new InvalidOperationException(
                        String.Format(
                        CultureInfo.CurrentCulture,
                        "Der ursprüngliche Wert für das in der Eigenschaft '{0}' für den Typ '{1}' gespeicherte Objekt 

kann nicht festgelegt werden, da die Eigenschaft NULL ist.",
                        propertyPathToType,
                        entityTypeName));
                }
            }
            else if (property.TypeUsage.EdmType is ComplexType)
            {
                OriginalValueRecord nestedOriginalValueRecord = null;
                if (complexOriginalValueRecord != null)
                {
                    nestedOriginalValueRecord = complexOriginalValueRecord.GetOriginalValueRecord(property.Name);
                }
                // Kette der komplexen Typen durchlaufen...
                UpdateOriginalValues((ComplexType)property.TypeUsage.EdmType, entityTypeName, propertyPath, 

originalValueSource, nestedOriginalValueRecord);
            }
        }
    }

    private static OriginalValueRecord GetOriginalValueRecord(this OriginalValueRecord record, string name)
    {
        int ordinal = record.GetOrdinal(name);
        if (!record.IsDBNull(ordinal))
        {
            return record.GetDataRecord(ordinal) as OriginalValueRecord;
        }
        else
        {
            return null;
        }
    }

    private static void SetValue(this OriginalValueRecord record, EdmProperty edmProperty, object value)
    {
        if (value == null)
        {
            Type entityClrType = ((PrimitiveType)edmProperty.TypeUsage.EdmType).ClrEquivalentType;
            if (entityClrType.IsValueType &&
                !(entityClrType.IsGenericType && typeof(Nullable<>) == entityClrType.GetGenericTypeDefinition()))
            {
                // Überspringen Sie das Festlegen von NULL für ursprüngliche Werte in nicht nullbaren CLR-Typen, da 

ObjectStateEntry dies nicht zulässt.
                return;
            }
        }

        int ordinal = record.GetOrdinal(edmProperty.Name);
        record.SetValue(ordinal, value);
    }


    private static void ChangeEntityStateBasedOnObjectState(ObjectContext context, IObjectWithChangeTracker entity)
    {
        switch (entity.ChangeTracker.State)
        {
            case (ObjectState.Added):
                // No-op: Der Zustandseintrag ist bereits als hinzugefügt markiert
                Debug.Assert(context.ObjectStateManager.GetObjectStateEntry(entity).State == EntityState.Added, "Der 

Zustand hätte 'Hinzugefügt' sein sollen");
                break;
            case (ObjectState.Unchanged):
                context.ObjectStateManager.ChangeObjectState(entity, EntityState.Unchanged);
                break;
            case (ObjectState.Modified):
                context.ObjectStateManager.ChangeObjectState(entity, EntityState.Modified);
                break;
            case (ObjectState.Deleted):
                context.ObjectStateManager.ChangeObjectState(entity, EntityState.Deleted);
                break;

        }
    }

    private static EntityType GetCSpaceEntityType(this MetadataWorkspace workspace, Type type)
    {
        EntityType ospaceEntityType = null;
        StructuralType cspaceEntityType = null;
        EntityType entityType = null;
        if (workspace.TryGetItem<EntityType>(
            type.FullName,
            DataSpace.OSpace,
            out ospaceEntityType))
        {
            if (workspace.TryGetEdmSpaceType(
                ospaceEntityType,
                out cspaceEntityType))
            {
                entityType = cspaceEntityType as EntityType;
            }
        }
        if(entityType == null)
        {
            throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, "Für den Typ '{0}' wurde kein CSpace-Typ 

gefunden.", type.FullName));
        }
        return entityType;
    }

    private static object GetValue(this System.Data.Objects.DataClasses.EntityReference entityReference)
    {
        foreach (object value in entityReference)
        {
            return value;
        }
        return null;
    }

    private static EntityKey GetCurrentEntityKey(this System.Data.Objects.DataClasses.EntityReference entityReference, 

ObjectContext context)
    {
        EntityKey currentKey = null;
        object currentValue = entityReference.GetValue();
        if (currentValue != null)
        {
            ObjectStateEntry relatedEntry = context.ObjectStateManager.GetObjectStateEntry(currentValue);
            currentKey = relatedEntry.EntityKey;
        }
        else
        {
            currentKey = entityReference.EntityKey;
        }
        return currentKey;
    }

    private static RelatedEnd GetRelatedEnd(this ObjectStateEntry entry, string navigationPropertyIdentity)
    {
        NavigationProperty navigationProperty =
                        GetNavigationProperty(entry.ObjectStateManager.MetadataWorkspace.GetCSpaceEntityType

(entry.Entity.GetType()), navigationPropertyIdentity);
        return entry.RelationshipManager.GetRelatedEnd(
            navigationProperty.RelationshipType.FullName, navigationProperty.ToEndMember.Name) as RelatedEnd;
    }

    private static NavigationProperty GetNavigationProperty(this EntityType entityType, string navigationPropertyIdentity)
    {
        NavigationProperty navigationProperty;
        if (!entityType.NavigationProperties.TryGetValue(navigationPropertyIdentity, false, out navigationProperty))
        {
            throw new InvalidOperationException(
                String.Format(
                    CultureInfo.CurrentCulture,
                    "Die Navigationseigenschaft '{0}' in EntityType '{1}' wurde nicht gefunden.",
                    navigationPropertyIdentity,
                    entityType.FullName));
        }
        return navigationProperty;
    }

    private static string GetEntitySetName(this RelatedEnd relatedEnd)
    {
        EntitySet entitySet = ((AssociationSet)relatedEnd.RelationshipSet).AssociationSetEnds

[relatedEnd.TargetRoleName].EntitySet;
        return entitySet.EntityContainer.Name + "." + entitySet.Name;
    }

    private static bool IsDependentEndOfReferentialConstraint(this RelatedEnd relatedEnd)
    {
        if (null != relatedEnd.RelationshipSet)
        {
            // HINWEIS: Die Auflistung für referenzielle Einschränkungen enthält normalerweise 0 oder 1 Element,
            // Leistungsprobleme sind hier nicht zu erwarten
            foreach (ReferentialConstraint constraint in ((AssociationType)

relatedEnd.RelationshipSet.ElementType).ReferentialConstraints)
            {
                if (constraint.ToRole.Name == relatedEnd.SourceRoleName)
                {
                    // Beispiel:
                    //    Client<C_ID> --- Order<O_ID, Client_ID>
                    //    RI-Einschränkung: Prinzipal/Von <Client.C_ID>, Abhängig/Bis <Order.Client_ID>
                    // Wenn das aktuelle RelatedEnd ein CollectionOrReference in Reihenfolgenbeziehungen ist,
                    // constarint.ToRole == this._fromEndProperty == Order
                    return true;
                }
            }
        }
        return false;
    }

    private static bool TryGetObjectStateEntry(this ObjectContext context, EntityKey from, EntityKey to, AssociationSet 

associationSet, AssociationEndMember fromEnd, AssociationEndMember toEnd, out ObjectStateEntry entry)
    {
        entry = null;
        foreach (var relationshipEntry in (from e in context.ObjectStateManager.GetObjectStateEntries(EntityState.Added | 

EntityState.Unchanged)
                                           where e.IsRelationship && e.EntitySet == associationSet
                                           select e))
        {
            CurrentValueRecord currentValues = relationshipEntry.CurrentValues;
            int fromOrdinal = currentValues.GetOrdinal(fromEnd.Name);
            int toOrdinal = currentValues.GetOrdinal(toEnd.Name);
            if (((EntityKey)currentValues.GetValue(fromOrdinal)) == from &&
                ((EntityKey)currentValues.GetValue(toOrdinal)) == to)
            {
                entry = relationshipEntry;
                return true;
            }
        }
        return false;
    }

    private sealed class AddHelper
    {
        private readonly ObjectContext _context;
        private readonly EntityIndex _entityIndex;

        // Wird während der Verarbeitung von Hinzufügevorgängen verwendet
        private readonly Queue<Tuple<string, IObjectWithChangeTracker>> _entitiesToAdd;
        private readonly Queue<Tuple<ObjectStateEntry, string, IEnumerable<object>>> _entitiesDuringAdd;

        public static EntityIndex AddAllEntities(ObjectContext context, string entitySetName, IObjectWithChangeTracker 

entity)
        {
            AddHelper addHelper = new AddHelper(context);

            try
            {
                // Das Stammelement einschließen, um den Übernahmevorgang zu starten
                addHelper.QueueAdd(entitySetName, entity);

                // Alles hinzufügen
                while (addHelper.HasMore)
                {
                    Tuple<string, IObjectWithChangeTracker> entityInSet = addHelper.NextAdd();
                    // Objekt nur hinzufügen, wenn es nicht bereits im Kontext enthalten ist
                    ObjectStateEntry entry = null;
                    if (!context.ObjectStateManager.TryGetObjectStateEntry(entityInSet.Item2, out entry))
                    {
                        context.AddObject(entityInSet.Item1, entityInSet.Item2);
                    }
                }
            }
            finally
            {
                addHelper.Detach();
            }
            return addHelper.EntityIndex;
        }

        private AddHelper(ObjectContext context)
        {
            _context = context;
            _context.ObjectStateManager.ObjectStateManagerChanged += this.HandleStateManagerChange;

            _entityIndex = new EntityIndex(context);
            _entitiesToAdd = new Queue<Tuple<string, IObjectWithChangeTracker>>();
            _entitiesDuringAdd = new Queue<Tuple<ObjectStateEntry, string, IEnumerable<object>>>();
        }

        private void Detach()
        {
            _context.ObjectStateManager.ObjectStateManagerChanged -= this.HandleStateManagerChange;
        }

        private void HandleStateManagerChange(object sender, CollectionChangeEventArgs args)
        {
            if (args.Action == CollectionChangeAction.Add)
            {
                IObjectWithChangeTracker entity = args.Element as IObjectWithChangeTracker;
                ObjectStateEntry entry = _context.ObjectStateManager.GetObjectStateEntry(entity);
                ObjectChangeTracker changeTracker = entity.ChangeTracker;

                changeTracker.ChangeTrackingEnabled = false;
                _entityIndex.Add(entry, changeTracker);

                // Aus der Warteschlange entfernte Referenzwerte
                var navPropNames = _context.MetadataWorkspace.GetCSpaceEntityType(entity.GetType

()).NavigationProperties.Select(n => n.Name);
                var entityRefOriginalValues = changeTracker.OriginalValues.Where(kvp => navPropNames.Contains(kvp.Key));
                foreach (KeyValuePair<string, object> originalValueWithName in entityRefOriginalValues)
                {
                    if (originalValueWithName.Value != null)
                    {
                        _entitiesDuringAdd.Enqueue(new Tuple<ObjectStateEntry, string, IEnumerable<object>>(
                            entry,
                            originalValueWithName.Key,
                            new object[] { originalValueWithName.Value }));
                    }
                }

                // Aus der Warteschlange entfernte Auflistungswerte
                foreach (KeyValuePair<string, ObjectList> collectionPropertyChangesWithName in 

changeTracker.ObjectsRemovedFromCollectionProperties)
                {
                    _entitiesDuringAdd.Enqueue(new Tuple<ObjectStateEntry, string, IEnumerable<object>>(
                        entry,
                        collectionPropertyChangesWithName.Key,
                        collectionPropertyChangesWithName.Value));
                }
            }
        }

        private EntityIndex EntityIndex
        {
            get { return _entityIndex; }
        }

        private bool HasMore
        {
            get { ProcessNewAdds(); return _entitiesToAdd.Count > 0; }
        }

        private void QueueAdd(string entitySetName, IObjectWithChangeTracker entity)
        {
            if (!_entityIndex.Contains(entity))
            {
                // Entität in die Warteschlange setzen, damit die Elemente der "entfernten Auflistung" hinzugefügt werden 

können
                _entitiesToAdd.Enqueue(new Tuple<string, IObjectWithChangeTracker>(entitySetName, entity));
            }
        }

        private Tuple<string, IObjectWithChangeTracker> NextAdd()
        {
            ProcessNewAdds();
            return _entitiesToAdd.Dequeue();
        }

        private void ProcessNewAdds()
        {
            while (_entitiesDuringAdd.Count > 0)
            {
                Tuple<ObjectStateEntry, string, IEnumerable<object>> relatedEntities = _entitiesDuringAdd.Dequeue();
                RelatedEnd relatedEnd = relatedEntities.Item1.GetRelatedEnd(relatedEntities.Item2);
                string entitySetName = relatedEnd.GetEntitySetName();

                foreach (var targetEntity in relatedEntities.Item3)
                {
                    QueueAdd(entitySetName, targetEntity as IObjectWithChangeTracker);
                }
            }
        }
    }

    private sealed class EntityIndex
    {
        private readonly ObjectContext _context;

        // Gruppe aller Entitäten
        private readonly HashSet<IObjectWithChangeTracker> _allEntities;

        // Index des letzten Schlüssels, der im Kontext verwendet wird (kann echt für nicht hinzugefügte Elemente und 

temporär für hinzugefügte Elemente sein)
        // für den anfänglichen temporären Schlüssel
        private readonly Dictionary<EntityKey, EntityKey> _temporaryKeyMap;

        public EntityIndex(ObjectContext context)
        {
            _context = context;

            _allEntities = new HashSet<IObjectWithChangeTracker>();
            _temporaryKeyMap = new Dictionary<EntityKey, EntityKey>();
        }

        public void Add(ObjectStateEntry entry, ObjectChangeTracker changeTracker)
        {
            EntityKey temporaryKey = entry.EntityKey;
            EntityKey finalKey;

            if (!_allEntities.Contains(entry.Entity))
            {
                // Nachverfolgen, dass die Entität durch diese Übernahme behandelt wird
                _allEntities.Add(entry.Entity as IObjectWithChangeTracker);
            }

            if (changeTracker.State == ObjectState.Added)
            {
                finalKey = temporaryKey;
            }
            else
            {
                finalKey = _context.CreateEntityKey(temporaryKey.EntityContainerName + "." + temporaryKey.EntitySetName, 

entry.Entity);
            }
            if (!_temporaryKeyMap.ContainsKey(finalKey))
            {
                _temporaryKeyMap.Add(finalKey, temporaryKey);
            }
        }

        public bool Contains(object entity)
        {
            return _allEntities.Contains(entity);
        }

        public IEnumerable<IObjectWithChangeTracker> AllEntities
        {
            get { return _allEntities; }
        }

        // Konvertiert den übergebenen EntityKey in den EntityKey, der vom aktuellen Zustand von ApplyChanges verwendet 

werden kann
        public EntityKey ConvertEntityKey(EntityKey targetKey)
        {
            ObjectStateEntry targetEntry;
            if (!_context.ObjectStateManager.TryGetObjectStateEntry(targetKey, out targetEntry))
            {
                // Falls kein Eintrag vorhanden ist, führen Sie einen der folgenden Schritte aus:
                // 1. Hierbei handelt es sich um einen EntityKey, der in der Entitätsgruppe, die während des 

Übernahmevorgangs bearbeitet wird, nicht dargestellt ist
                // 2. Dies ist ein EntityKey, der einen der noch zu verarbeitenden hinzugefügten Einträge darstellt (daher 

nachschlagen)
                EntityKey temporaryKey;
                if (_temporaryKeyMap.TryGetValue(targetKey, out temporaryKey))
                {
                    targetKey = temporaryKey;
                }
            }
            return targetKey;
        }
    }

    // Vom RelationshipSet wird eine Liste aller Beziehungen von einer
    // Anfangsgruppe von Entitäten erstellt
    private sealed class RelationshipSet : IEnumerable<RelationshipWrapper>
    {
        private readonly HashSet<RelationshipWrapper> _relationships;
        private readonly ObjectContext _context;

        public RelationshipSet(ObjectContext context, IEnumerable<object> allEntities)
        {
            _context = context;
            _relationships = new HashSet<RelationshipWrapper>();
            foreach (object entity in allEntities)
            {
                ObjectStateEntry entry = context.ObjectStateManager.GetObjectStateEntry(entity);
                foreach (IRelatedEnd relatedEnd in entry.RelationshipManager.GetAllRelatedEnds())
                {
                    if (!((AssociationType)relatedEnd.RelationshipSet.ElementType).IsForeignKey)
                    {
                        foreach (object targetEntity in relatedEnd)
                        {
                            Add(relatedEnd, entity, targetEntity, EntityState.Unchanged);
                        }
                    }
                }
            }
        }

        // Fügt einen Indexeintrag auf Grundlage von IRelatedEnd hinzu.
        public void Add(IRelatedEnd relatedEnd, object sourceEntity, object targetEntity, EntityState state)
        {
            RelationshipWrapper wrapper = new RelationshipWrapper(
                                (AssociationSet)relatedEnd.RelationshipSet,
                                relatedEnd.SourceRoleName,
                                sourceEntity,
                                relatedEnd.TargetRoleName,
                                targetEntity,
                                state);
            if (!_relationships.Contains(wrapper))
            {
                _relationships.Add(wrapper);
            }
        }

        // Entfernt einen Eintrag aus dem Index auf Basis eines beziehungsbezogenen ObjectStateEntry
        public void Remove(ObjectStateEntry relationshipEntry)
        {
            Debug.Assert(relationshipEntry.IsRelationship);
            AssociationSet associationSet = (AssociationSet)relationshipEntry.EntitySet;
            DbDataRecord values = relationshipEntry.State == EntityState.Deleted ? relationshipEntry.OriginalValues : 

relationshipEntry.CurrentValues;
            int fromOridinal = values.GetOrdinal(associationSet.ElementType.AssociationEndMembers[0].Name);
            object fromEntity = _context.ObjectStateManager.GetObjectStateEntry((EntityKey)values.GetValue

(fromOridinal)).Entity;
            int toOridinal = values.GetOrdinal(associationSet.ElementType.AssociationEndMembers[1].Name);
            object toEntity = _context.ObjectStateManager.GetObjectStateEntry((EntityKey)values.GetValue

(toOridinal)).Entity;

            if (fromEntity != null && toEntity != null)
            {
                RelationshipWrapper wrapper = new RelationshipWrapper(
                    associationSet,
                    associationSet.ElementType.AssociationEndMembers[0].Name,
                    fromEntity,
                    associationSet.ElementType.AssociationEndMembers[1].Name,
                    toEntity,
                    EntityState.Unchanged);

                _relationships.Remove(wrapper);
            }
        }

        #region IEnumerable<RelationshipWrapper>

        public IEnumerator<RelationshipWrapper> GetEnumerator()
        {
            return _relationships.GetEnumerator();
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return _relationships.GetEnumerator();
        }

        #endregion
    }

    // Ein RelationshipWrapper dient zur Identifizierung einer Beziehung zwischen zwei Entitäten
    // Die Beziehung wird durch den AssociationSet und die Reihenfolge der Entitäten auf Basis der
    // Rollen, die von ihnen übernommen werden, gekennzeichnet (über AssociationEndMember)
    private sealed class RelationshipWrapper : IEquatable<RelationshipWrapper>
    {
        internal readonly AssociationSet AssociationSet;
        internal readonly object End0;
        internal readonly object End1;
        internal readonly EntityState State;

        internal RelationshipWrapper(AssociationSet extent,
                                     string role0, object end0,
                                     string role1, object end1,
                                     EntityState state)
        {
            Debug.Assert(null != extent, "null AssociationSet");
            Debug.Assert(null != (object)end0, "null end0");
            Debug.Assert(null != (object)end1, "null end1");

            AssociationSet = extent;
            Debug.Assert(extent.ElementType.AssociationEndMembers.Count == 2, "Nur zwei Enden werden unterstützt");

            State = state;

            if (extent.ElementType.AssociationEndMembers[0].Name == role0)
            {
                Debug.Assert(extent.ElementType.AssociationEndMembers[1].Name == role1, "a)Unterschied in roleAndKey1-

Name");
                End0 = end0;
                End1 = end1;
            }
            else
            {
                Debug.Assert(extent.ElementType.AssociationEndMembers[0].Name == role1, "b)Unterschied in roleAndKey1-

Name");
                Debug.Assert(extent.ElementType.AssociationEndMembers[1].Name == role0, "b)Unterschied in roleAndKey0-

Name");
                End0 = end1;
                End1 = end0;
            }
        }

        internal ReadOnlyMetadataCollection<AssociationEndMember> AssociationEndMembers
        {
            get { return this.AssociationSet.ElementType.AssociationEndMembers; }
        }

        public override int GetHashCode()
        {
            return this.AssociationSet.Name.GetHashCode() ^ (this.End0.GetHashCode() + this.End1.GetHashCode());
        }

        public override bool Equals(object obj)
        {
            return Equals(obj as RelationshipWrapper);
        }

        public bool Equals(RelationshipWrapper wrapper)
        {
            return (Object.ReferenceEquals(this, wrapper) ||
                    ((null != wrapper) &&
                     Object.ReferenceEquals(this.AssociationSet, wrapper.AssociationSet) &&
                     Object.ReferenceEquals(this.End0, wrapper.End0) &&
                     Object.ReferenceEquals(this.End1, wrapper.End1)));
        }
    }
}
<#+
}
#>

Der Hauptunterschied liegt nun in den Konstanten, die in der neuen Vorlage durch den Inhalt ersetzt und korrigiert wurden. Diese Elemente werden nun in einem ZIP-File gepackt und die neue Vorlage ist fertig. Jetzt muss diese nur noch ins ItemTemplate – Verzeichnis kopiert werden. Wenn ich jetzt eine fehlerfreie Selbstnachverfolgung haben will, wähle ich "ADO.NET Entitäs Selbstnachverfolgung (Workaround)" aus der Liste der Codegenerierungsvorlagen aus.

Abbildung 3 Neue Codegenerierungsvorlage "ADO.NET Entitäs Selbstnachverfolgung (Workaround)" als Auswahlmöglichkeit
Abbildung 3

Dieser Weg zeigt das Vorgehen auf manuelle Weise, die vollständige Vorlage kann hier heruntergeladen werden. Dieser Ausflug hat meinen Appetit auf die T4-Vorlagen geweckt, mal sehen welche Ideen damit möglich werden und wo die Grenzen liegen.

T4 Templates - Der Nachteil einer Textdatei

Gerade wollte ich mir die Zeit nehmen und bei der deutschen Version von Visual Studio 2010 das Codegenerierungselement Self Tracking Entities testen. In der Englischen Umgebung habe ich bisher keine Probleme mit der Basisfunktionalität gehabt. Nun bei der deutschen Version hat wohl Murphy seine Finger im Spiel.

Abbildung 1 Auswahl Enititätsklassen mit Selbstnachverfolgung
Abbildung 1

Das erste was mich nach der Auswahl erwartet sind 4 Fehler im T4 - Templatecode.

Abbildung 2 Fehler nach Auswahl des Codegenerierungselement zur Selbstverfolgung
Abbildung 2

Wenn ich zu den Code in das Template springe, ist folgender Code-Block ersichtlich:

C# TT - CodeBlock-Ausschnitt
    // <#=String.Format(CultureInfo.CurrentCulture, "Zeichnet die ursprünglichen Werte für die komplexe Eigenschaft "{0}" auf.", edmProperty.Name)#>
    private void Handle<#=edmProperty.Name#>Changing(object sender, EventArgs args)
    {
        if (ChangeTracker.State != ObjectState.Added && ChangeTracker.State != ObjectState.Deleted)
        {
            ChangeTracker.State = ObjectState.Modified;
        }
<#
        if (originalValueMembers.IsOriginalValueMember(edmProperty))
        {
#>
        <#=code.Escape(edmProperty.TypeUsage)#>.RecordComplexOriginalValues("<#=edmProperty.Name#>", this.<#=code.Escape(edmProperty)#>, ChangeTracker);
<#
        }
#>
    }

Wer die erste Zeile (String.Format) sieht, erkennt bestimmt die Anführungsstriche, die nicht entwertet sind und den Fehler verursachen. Wenn ich "{0}" mit '{0}' ersetze oder alternativ die Anführungsstriche entwerte und die Solution neu kompiliere sind diese Fehler verschwunden und die Entitäten werden fehlerfrei generiert, der Kontext jedoch nicht (79 Fehler). Das liegt daran, dass im Context-T4 Template das gleiche Problem lauert.

Ein Vorteil dieser T4 - Templates, es ist wirklich schnell korrigiert.

Das eigentliche Problem sitzt jedoch in den lokalisierten Ressourcen wie zum Beispiel $Localized_STECtx_Comment_630$, in diesen werden die Anführungsstriche nicht entwertet.

Translate this page

Kategorien