Bienvenue à Blogs CodeS-SourceS Identification | Inscription | Aide

Abonnements

Self Tracking Entities performance

Prenons une base avec deux tables : Books et Authors.

  • Books:
    • Id (int, PK)
    • Title (nvarchar)
    • AuthorId (int, FK)
  • Authors:
    • Id (int, PK)
    • Name (nvarchar)

Notre base contient 10 000 livres, tous pour le même auteur.

Pour requêter, nous utiliserons le code suivant:

using (var context = new BooksEntities())
{
   
foreach (Author a in
context.Authors)
        ;
    foreach (Book b in
context.Books)
        ;
}

Ce code nécessite 5,5 secondes pour s’exécuter avec les STE contre 0,3 secondes avec les EntityObject…

WTF?

En fait, il y a plusieurs optimisations possibles sur le template T4 STE.

La principale explication de cette différence est la méthode Contains. En effet, avec le template T4 STE, les navigation properties de type collection sont des TrackableCollection<T> qui héritent d’ObservableCollection<T>, ce qui signifie que leur perf est en O(n).

Pour améliorer les perfs, il est préférable d’utiliser un HashSet<T>. Cependant, nous avons besoin de l’interface IList et INotifyCollectionChanged.

J’ai donc changé la classe TrackableCollection<T> don’t voici la version originale :

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);
        }
    }
}

en :

public class TrackableCollection<T> : IList<T>, INotifyCollectionChanged
{
   
private ObservableCollection<T> _observableCollection = new ObservableCollection
<T>();
   
private HashSet<T> _hashSet = new HashSet
<T>();

   
public bool
Contains(T item)
    {
       
return
_hashSet.Contains(item);
    }

   
public int
IndexOf(T item)
    {
       
return
_observableCollection.IndexOf(item);
    }

   
public void Insert(int
index, T item)
    {
        _observableCollection.Insert(index, item);
        _hashSet.Add(item);
    }

   
public void RemoveAt(int
index)
    {
        T item = _observableCollection[index];
        _observableCollection.RemoveAt(index);
        _hashSet.Remove(item);
    }

   
public T this[int
index]
    {
       
get { return
_observableCollection[index]; }
       
set
        {
            _hashSet.Remove(_observableCollection[index]);
            _observableCollection[index] =
value
;
            _hashSet.Add(
value
);
        }
    }

   
public void
Add(T item)
    {
        _observableCollection.Add(item);
        _hashSet.Add(item);
    }

   
public void
Clear()
    {
       
int
count = _observableCollection.Count;
       
for (int
index = 0 ; index < count ; index ++)
            RemoveAt(index);
    }

   
public void CopyTo(T[] array, int
arrayIndex)
    {
        _observableCollection.CopyTo(array, arrayIndex);
    }

   
public int
Count
    {
       
get { return
_observableCollection.Count; }
    }

   
bool ICollection
<T>.IsReadOnly
    {
       
get { return ((ICollection
<T>)_observableCollection).IsReadOnly; }
    }

   
public bool
Remove(T item)
    {
       
bool
value = _observableCollection.Remove(item);
        _hashSet.Remove(item);
       
return
value;
    }

   
public IEnumerator
<T> GetEnumerator()
    {
       
return
_observableCollection.GetEnumerator();
    }
   
IEnumerator IEnumerable
.GetEnumerator()
    {
       
return
GetEnumerator();
    }

   
public event NotifyCollectionChangedEventHandler
CollectionChanged
    {
       
add { _observableCollection.CollectionChanged += value
; }
       
remove { _observableCollection.CollectionChanged -= value; }
    }
}

Dans ce cas, notre code tourne en 3 secondes ce qui est mieux mais reste très insuffisant.

En effet, si le Contains est plus rapide avec le HashShet, le Add lui est plus long.

Il faut donc améliorer cela.

Pour cela, nous allons tenter une approche avec TPL.

public class TrackableCollection<T> : IList<T>, INotifyCollectionChanged
{
   
private ObservableCollection<T> _observableCollection = new ObservableCollection
<T>();
   
private ConcurrentDictionary<T, object> _concurrentDico = new ConcurrentDictionary<T, object
>();

   
private void
AddDico(T item)
    {
       
Task t = new Task(() => _concurrentDico.TryAdd(item, null
));
        t.Start();
    }

   
public bool
Contains(T item)
    {
       
return
_concurrentDico.ContainsKey(item);
    }

   
public int
IndexOf(T item)
    {
       
return
_observableCollection.IndexOf(item);
    }

   
public void Insert(int
index, T item)
    {
        _observableCollection.Insert(index, item);
        AddDico(item);
    }

   
public void RemoveAt(int
index)
    {
        T item = _observableCollection[index];
        _observableCollection.RemoveAt(index);
       
object
o;
        _concurrentDico.TryRemove(item,
out
o);
    }

   
public T this[int
index]
    {
       
get { return
_observableCollection[index]; }
       
set
        {
           
object
o;
            _concurrentDico.TryRemove(_observableCollection[index],
out
o);
            _observableCollection[index] =
value
;
            AddDico(
value
);
        }
    }

   
public void
Add(T item)
    {
        _observableCollection.Add(item);
        AddDico(item);
    }

   
public void
Clear()
    {
       
int
count = _observableCollection.Count;
       
for (int
index = 0 ; index < count ; index ++)
            RemoveAt(index);
    }

   
public void CopyTo(T[] array, int
arrayIndex)
    {
        _observableCollection.CopyTo(array, arrayIndex);
    }

   
public int
Count
    {
       
get { return
_observableCollection.Count; }
    }

   
bool ICollection
<T>.IsReadOnly
    {
       
get { return ((ICollection
<T>)_observableCollection).IsReadOnly; }
    }

   
public bool
Remove(T item)
    {
       
bool
value = _observableCollection.Remove(item);
       
object
o;
        _concurrentDico.TryRemove(item,
out
o);
       
return
value;
    }

   
public IEnumerator
<T> GetEnumerator()
    {
       
return
_observableCollection.GetEnumerator();
    }
   
IEnumerator IEnumerable
.GetEnumerator()
    {
       
return
GetEnumerator();
    }

   
public event NotifyCollectionChangedEventHandler
CollectionChanged
    {
       
add { _observableCollection.CollectionChanged += value
; }
       
remove { _observableCollection.CollectionChanged -= value; }
    }
}

Ce code s’exécute en 2.5 seconds.

Avec ce code, on peut avoir un bug avec la méthode Contains. Cependant ce n’est pas un problème ici car la méthode Contains n’est utilisé que pour savoir s’il faut ou non ajouter un item et la méthode TryAdd du ConcurrentDictionary fait le boulot pour vous.

Que peut-on faire maintenant ?

Je pense que ce que nous faisons est vraiment stupide. En effet, on est en train de charger des livres et au fûr et à mesure, la méthode Contains est de plus en plus longue puisque la collection grossit.

Nous pouvons appliquer la méthode Contains que sur les entités déjà chargées (avant l’exécution de la requête).

Nous pouvons donc modifier le template T4 pour avoir le code suivant :

public interface IEditableEntity
{
   
void
BeginEdit();
   
void EndEdit();
}
public class TrackableCollection<T> : IList<T>, INotifyCollectionChanged
{
    private ObservableCollection<T> _observableCollection;
    private ConcurrentDictionary<T, object> _concurrentDico = new ConcurrentDictionary<T, object>();
   
    public TrackableCollection()
    {
        InitializeObservableCollection(new T[0]);
    }
   
    private void InitializeObservableCollection(IEnumerable<T> items)
    {
        NotifyCollectionChangedEventHandler collectionChanged = (sender, e) => OnCollectionChanged(e);
        if (_observableCollection != null)
            _observableCollection.CollectionChanged -= collectionChanged;
        _observableCollection = new ObservableCollection<T>(((IEnumerable<T>)_observableCollection ?? new T[0]).Union(items));
        if (items.Any())
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, items.ToList()));
        _observableCollection.CollectionChanged += collectionChanged;
    }
   
    private bool _isEditing;
    private List<T> _tmpList;
    private Task _tmpTask;
    public void BeginEdit()
    {
        if (_tmpTask != null)
            _tmpTask.Wait();
        _isEditing = true;
        _tmpList = new List<T>();
    }
    public void EndEdit()
    {
        _tmpTask = new Task(() =>
        {
            foreach (T item in _tmpList)
                _concurrentDico.TryAdd(item, null);
        });
        _tmpTask.Start();
        InitializeObservableCollection(_tmpList);
        _isEditing = false;
    }
   
    private void AddDico(T item)
    {
        Task t = new Task(() => _concurrentDico.TryAdd(item, null));
        t.Start();
    }
   
    public bool Contains(T item)
    {
        if (_isEditing && _tmpTask != null)
            _tmpTask.Wait();
        return _concurrentDico.ContainsKey(item);
    }
   
    public int IndexOf(T item)
    {
        return _observableCollection.IndexOf(item);
    }
   
    public void Insert(int index, T item)
    {
        _observableCollection.Insert(index, item);
        AddDico(item);
    }
   
    public void RemoveAt(int index)
    {
        T item = _observableCollection[index];
        _observableCollection.RemoveAt(index);
        object o;
        _concurrentDico.TryRemove(item, out o);
    }
   
    public T this[int index]
    {
        get { return _observableCollection[index]; }
        set
        {
            object o;
            _concurrentDico.TryRemove(_observableCollection[index], out o);
            _observableCollection[index] = value;
            AddDico(value);
        }
    }
   
    public void Add(T item)
    {
        if (Contains(item))
            return;
        if (_isEditing)
            _tmpList.Add(item);
        else
        {
            _observableCollection.Add(item);
            AddDico(item);
        }
    }
   
    public void Clear()
    {
        int count = _observableCollection.Count;
        for (int index = 0 ; index < count ; index ++)
            RemoveAt(0);
    }
   
    public void CopyTo(T[] array, int arrayIndex)
    {
        _observableCollection.CopyTo(array, arrayIndex);
    }
   
    public int Count
    {
        get { return _observableCollection.Count; }
    }
   
    bool ICollection<T>.IsReadOnly
    {
        get { return ((ICollection<T>)_observableCollection).IsReadOnly; }
    }
   
    public bool Remove(T item)
    {
        bool value = _observableCollection.Remove(item);
        object o;
        _concurrentDico.TryRemove(item, out o);
        return value;
    }
   
    public IEnumerator<T> GetEnumerator()
    {
        return _observableCollection.GetEnumerator();
    }
    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
   
    private void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        if (CollectionChanged != null)
            CollectionChanged(this, e);
    }
    public event NotifyCollectionChangedEventHandler CollectionChanged;
}


public partial class Author: IObjectWithChangeTracker, INotifyPropertyChanged, IEditableEntity
{
    #region
Primitive Properties
//…
    #endregion
    #region
Navigation Properties
//…
    #endregion
    void IEditableEntity
.BeginEdit()
    {
          Books.BeginEdit();

    }
   
void IEditableEntity
.EndEdit()
    {
          Books.EndEdit();
    }
    #region
ChangeTracking
   
//…
    #endregion
    #region
Association Fixup
    //…

    #endregion
}

Then, I use it with the following code:

using (var context = new BooksEntities())
{
    foreach (Author a in context.Authors)
       
;

    List<IEditableEntity> entities =
        context.ObjectStateManager.GetObjectStateEntries(
EntityState.Added | EntityState.Deleted | EntityState.Modified | EntityState
.Unchanged).
        Select(ose => ose.Entity).OfType<
IEditableEntity
>().ToList();
   
foreach (var e in
entities)
        e.BeginEdit();
   
foreach (Book b in
context.Books)
        ;
   
foreach (var e in
entities)
        e.EndEdit();
}

Ce code s’exécute en 0.7s… Il y a probablement d’autre pistes d’améliorations mais c’est tout pour aujourd’hui. Ce n’est déjà pas si mal comparé aux 5.5 seconds…

 

 

Mon template T4 STE modifié est le suivant :

 

<#@ 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 = @"Model1.edmx";
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);

// Write out support code to primary template output file
WriteHeader(fileManager);
BeginNamespace(namespaceName, code);
WriteObjectChangeTracker();
WriteIObjectWithChangeTracker();
WriteCustomObservableCollection();
WriteIEditableEntity();
WriteINotifyComplexPropertyChanging();
WriteEqualityComparer();
EndNamespace(namespaceName);

// Emit Entity Types
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, IEditableEntity
{
<#
    region.Begin("Primitive Properties");

    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("The property '{0}' is part of the object's key and cannot be changed. Changes to key properties can only be made when the object is not being tracked or is in the Added state.", 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("Complex Properties");

    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();

    ////////
    //////// Write Navigation properties -------------------------------------------------------------------------------------------
    ////////

    region.Begin("Navigation Properties");

    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("Cannot set the FixupChangeTrackingCollection when ChangeTracking is enabled");
                }
                if (<#=code.FieldName(navProperty)#> != null)
                {
                    <#=code.FieldName(navProperty)#>.CollectionChanged -= Fixup<#=navProperty.Name#>;
<#
        if (ef.IsCascadeDeletePrincipal(navProperty))
        {
#>
                    // This is the principal end in an association that performs cascade deletes.
                    // Remove the cascade delete event handler for any entities in the current collection.
                    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))
        {
#>
                    // This is the principal end in an association that performs cascade deletes.
                    // Add the cascade delete event handler for any entities that are already in the new collection.
                    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))
            {
<#
            // If this is the dependent end of an identifying relationship, the principal end can only be changed if the dependent is in the Added state and the principal's key matches the foreign key on the dependent
            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 = dependentsIdea;
                    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);
                }
#>
                    // This the dependent end of an identifying relationship, so the principal end cannot be changed if it is already set,
                    // otherwise it can only be set to an entity with a primary key that is the same value as the dependent's foreign key.
                    if (<#=keyMatchCondition.ToString()#>)
                    {
                        throw new InvalidOperationException("The principal end of an identifying relationship can only be changed when the dependent end is in the Added state.");
                    }
                }
<#
            }
#>
                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();
   
#>
    void IEditableEntity.BeginEdit()
    {
<#
List<NavigationProperty> navPropsMany = entity.NavigationProperties.Where(np => np.DeclaringType == entity &&

np.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many).ToList();
foreach (NavigationProperty navProperty in navPropsMany)
{
#>
        <#=code.Escape(navProperty)#>.BeginEdit();
<#
}
#>
    }
    void IEditableEntity.EndEdit()
    {
<#
foreach (NavigationProperty navProperty in navPropsMany)
{
#>
        <#=code.Escape(navProperty)#>.EndEdit();
<#
}
#>
    }
<#


    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();
        }
    }
<#
    // If this entity type participates in any relationships where the other end has an OnDelete
    // cascade delete defined, or if it is the dependent in any identifying relationships, it needs
    // an event handler to handle notifications that are fired when the parent is deleted.
    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())
    {
#>

    // This entity type is the dependent end in at least one association that performs cascade deletes.
    // This event handler will process notifications that occur when the principal end is deleted.
    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, "Records the original values for the complex property {0}", 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.AssociationEndMembersIdea.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("Association Fixup");

    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))
        {
#>
        // This is the principal end in an association that performs cascade deletes.
        // Update the event listener to refer to the new dependent.
        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))
        {
#>
        // This is the dependent end in an association that performs cascade deletes.
        // Update the principal's event listener to refer to the new dependent.
        // This is a unidirectional relationship from the dependent to the principal, so the dependent end is
        // responsible for managing the cascade delete event handler. In all other cases the principal end will manage it.
        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))
        {
#>
                // This is the principal end of an identifying association, so the dependent must be deleted when the relationship is removed.
                // If the current state of the dependent is Added, the relationship can be changed without causing the dependent to be deleted.
                if (previousValue != null && previousValue.ChangeTracker.State != ObjectState.Added)
                {
                    previousValue.MarkAsDeleted();
                }
<#
        }
        else if (inverse == null && ef.IsPrincipalEndOfIdentifyingRelationship((AssociationEndMember)navProperty.ToEndMember))
        {
#>
                // This is the dependent end of an identifying association, so it must be deleted when the relationship is
                // removed. If the current state is Added, the relationship can be changed without causing the dependent to be deleted.
                // This is a unidirectional relationship from the dependent to the principal, so the dependent end is
                // responsible for cascading the delete. In all other cases the principal end will manage it.
                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))
                {
#>
                // This is the principal end in an association that performs cascade deletes.
                // Update the event listener to refer to the new dependent.
                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))
                {
#>
                    // Delete the dependent end of this identifying association. If the current state is Added,
                    // allow the relationship to be changed without causing the dependent to be deleted.
                    if (item.ChangeTracker.State != ObjectState.Added)
                    {
                        item.MarkAsDeleted();
                    }
<#
                }
#>
                }
<#
                if (ef.IsCascadeDeletePrincipal(navProperty))
                {
#>
                // This is the principal end in an association that performs cascade deletes.
                // Remove the previous dependent from the event listener.
                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 Properties");

    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("Complex Properties");

    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)
    {
        // Bubble the event to all listeners because something changed in a nested complex property
        OnComplexPropertyChanging();
    }
<#
    }
#>

    public static void RecordComplexOriginalValues(String parentPropertyName, <#=code.Escape(complex)#> complexObject, ObjectChangeTracker changeTracker)
    {
        if (String.IsNullOrEmpty(parentPropertyName))
        {
            throw new ArgumentException("String parameter cannot be null or empty.", "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>
//     This code was generated from a template.
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Runtime.Serialization;
using System.Threading.Tasks;
<#=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 &&         // Target is a reference
           navProperty.FromEndMember.RelationshipMultiplicity != RelationshipMultiplicity.One;          // Source is nullable (i.e. not a 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 WriteIEditableEntity()
{
#>

public interface IEditableEntity
{
    void BeginEdit();
    void EndEdit();
}
<#+
}

void WriteCustomObservableCollection()
{
#>

// An System.Collections.ObjectModel.ObservableCollection that raises
// individual item removal notifications on clear and prevents adding duplicates.

public class TrackableCollection<T> : IList<T>, INotifyCollectionChanged
{
    private ObservableCollection<T> _observableCollection;
    private ConcurrentDictionary<T, object> _concurrentDico = new ConcurrentDictionary<T, object>();
    
    public TrackableCollection()
    {
        InitializeObservableCollection(new T[0]);
    }
    
    private void InitializeObservableCollection(IEnumerable<T> items)
    {
        NotifyCollectionChangedEventHandler collectionChanged = (sender, e) => OnCollectionChanged(e);
        if (_observableCollection != null)
            _observableCollection.CollectionChanged -= collectionChanged;
        _observableCollection = new ObservableCollection<T>(((IEnumerable<T>)_observableCollection ?? new T[0]).Union(items));
        if (items.Any())
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, items.ToList()));
        _observableCollection.CollectionChanged += collectionChanged;
    }
    
    private bool _isEditing;
    private List<T> _tmpList;
    private Task _tmpTask;
    public void BeginEdit()
    {
        if (_tmpTask != null)
            _tmpTask.Wait();
        _isEditing = true;
        _tmpList = new List<T>();
    }
    public void EndEdit()
    {
        _tmpTask = new Task(() =>
        {
            foreach (T item in _tmpList)
                _concurrentDico.TryAdd(item, null);
        });
        _tmpTask.Start();
        InitializeObservableCollection(_tmpList);
        _isEditing = false;
    }
    
    private void AddDico(T item)
    {
        Task t = new Task(() => _concurrentDico.TryAdd(item, null));
        t.Start();
    }
    
    public bool Contains(T item)
    {
        if (_isEditing && _tmpTask != null)
            _tmpTask.Wait();
        return _concurrentDico.ContainsKey(item);
    }
    
    public int IndexOf(T item)
    {
        return _observableCollection.IndexOf(item);
    }
    
    public void Insert(int index, T item)
    {
        _observableCollection.Insert(index, item);
        AddDico(item);
    }
    
    public void RemoveAt(int index)
    {
        T item = _observableCollection[index];
        _observableCollection.RemoveAt(index);
        object o;
        _concurrentDico.TryRemove(item, out o);
    }
    
    public T this[int index]
    {
        get { return _observableCollection[index]; }
        set
        {
            object o;
            _concurrentDico.TryRemove(_observableCollection[index], out o);
            _observableCollection[index] = value;
            AddDico(value);
        }
    }
    
    public void Add(T item)
    {
        if (Contains(item))
            return;
        if (_isEditing)
            _tmpList.Add(item);
        else
        {
            _observableCollection.Add(item);
            AddDico(item);
        }
    }
    
    public void Clear()
    {
        int count = _observableCollection.Count;
        for (int index = 0 ; index < count ; index ++)
            RemoveAt(0);
    }
    
    public void CopyTo(T[] array, int arrayIndex)
    {
        _observableCollection.CopyTo(array, arrayIndex);
    }
    
    public int Count
    {
        get { return _observableCollection.Count; }
    }
    
    bool ICollection<T>.IsReadOnly
    {
        get { return ((ICollection<T>)_observableCollection).IsReadOnly; }
    }
    
    public bool Remove(T item)
    {
        bool value = _observableCollection.Remove(item);
        object o;
        _concurrentDico.TryRemove(item, out o);
        return value;
    }
    
    public IEnumerator<T> GetEnumerator()
    {
        return _observableCollection.GetEnumerator();
    }
    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
    
    private void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        if (CollectionChanged != null)
            CollectionChanged(this, e);
    }
    public event NotifyCollectionChangedEventHandler CollectionChanged;
}

<#+
}

void WriteObjectChangeTracker()
{
#>
// Helper class that captures most of the change tracking work that needs to be done
// for self tracking entities.
[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; }
    }

    // Returns the removed objects to collection valued properties that were changed.
    [DataMember]
    public ObjectsRemovedFromCollectionProperties ObjectsRemovedFromCollectionProperties
    {
        get
        {
            if (_objectsRemovedFromCollections == null)
            {
                _objectsRemovedFromCollections = new ObjectsRemovedFromCollectionProperties();
            }
            return _objectsRemovedFromCollections;
        }
    }

    // Returns the original values for properties that were changed.
    [DataMember]
    public OriginalValuesDictionary OriginalValues
    {
        get
        {
            if (_originalValues == null)
            {
                _originalValues = new OriginalValuesDictionary();
            }
            return _originalValues;
        }
    }

    // Returns the extended property values.
    // This includes key values for independent associations that are needed for the
    // concurrency model in the Entity Framework
    [DataMember]
    public ExtendedPropertiesDictionary ExtendedProperties
    {
        get
        {
            if (_extendedProperties == null)
            {
                _extendedProperties = new ExtendedPropertiesDictionary();
            }
            return _extendedProperties;
        }
    }

    // Returns the added objects to collection valued properties that were changed.
    [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;
    }

    // Resets the ObjectChangeTracker to the Unchanged state and
    // clears the original values as well as the record of changes
    // to collection properties
    public void AcceptChanges()
    {
        OnObjectStateChanging(ObjectState.Unchanged);
        OriginalValues.Clear();
        ObjectsAddedToCollectionProperties.Clear();
        ObjectsRemovedFromCollectionProperties.Clear();
        ChangeTrackingEnabled = true;
        _objectState = ObjectState.Unchanged;
    }

    // Captures the original value for a property that is changing.
    internal void RecordOriginalValue(string propertyName, object value)
    {
        if (_changeTrackingEnabled && _objectState != ObjectState.Added)
        {
            if (!OriginalValues.ContainsKey(propertyName))
            {
                OriginalValues[propertyName] = value;
            }
        }
    }

    // Records an addition to collection valued properties on SelfTracking Entities.
    internal void RecordAdditionToCollectionProperties(string propertyName, object value)
    {
        if (_changeTrackingEnabled)
        {
            // Add the entity back after deleting it, we should do nothing here then
            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);
            }
        }
    }

    // Records a removal to collection valued properties on SelfTracking Entities.
    internal void RecordRemovalFromCollectionProperties(string propertyName, object value)
    {
        if (_changeTrackingEnabled)
        {
            // Delete the entity back after adding it, we should do nothing here then
            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()
{
#>

// An interface that provides an event that fires when complex properties change.
// Changes can be the replacement of a complex property with a new complex type instance or
// a change to a scalar property within a complex type instance.
public interface INotifyComplexPropertyChanging
{
    event EventHandler ComplexPropertyChanging;
}
<#+
}

void WriteIObjectWithChangeTracker()
{
#>
// The interface is implemented by the self tracking entities that EF will generate.
// We will have an Adapter that converts this interface to the interface that the EF expects.
// The Adapter will live on the server side.
public interface IObjectWithChangeTracker
{
    // Has all the change tracking information for the subgraph of a given object.
    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
{
    // Helper method to determine if two byte arrays are the same value even if they are different object references
    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 (array1Idea != array2Idea)
                {
                    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, "This template does not support types that differ only by case, the types {0} are not supported", type.FullName));
            return false;
        }
        else
        {
            alreadySeen.Add(type.FullName, true);
        }

    }

    return true;
}

// True if the association for the specified navigation property is an identifying relationship or a foreign key relationship.
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 if the specified association is an identifying relationship or a foreign key relationship.
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);
}

// Set recordRequiredOriginalValuesOnly to false in the OriginalValueMembers constructor in order to always record all original values
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 will not always return foreign key properties, but they are required
                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)
            {
                // If any exceptions occur, fall back to always recording original values for all properties
                _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);
        }
    }
}
#>

Ce post vous a plu ? Ajoutez le dans vos favoris pour ne pas perdre de temps à le retrouver le jour où vous en aurez besoin :

Publié mardi 8 février 2011 01:38 par Matthieu MEZIL

Commentaires

# re: Self Tracking Entities performance @ mardi 8 février 2011 09:47

J'ai le même genre de problème de performance avec le T4 poco avec fixup.

En effet les fixupcollection héritent aussi d'ObservableCollections et lors de l'ajout d'un élément on fait un contains sur la collection pour eviter les doublons.

Le problème n'apparait pas si on utilise les EntityObjects evidemment...

oleveau

# re: Self Tracking Entities performance @ mercredi 9 février 2011 10:26

5,5s et 0,3...

Ah oui cette modification est à lire à tête reposée.

Merci pour le code ;)

JeremyJeanson

# re: Self Tracking Entities performance @ mardi 15 février 2011 09:01

J'ai essayé d'utiliser votre template T4 pour profiter des améliorations de performances.

Malheureusement, j'ai un message d'erreur si je tente de générer le code avec votre template :

"Compiling transformation: Le nom 'WriteIEditableEntity' n'existe pas dans le contexte actuel".

Je vois pas trop comment résoudre ce problème. Auriez vous une idée ? Merci d'avance.

Steven62

Les commentaires anonymes sont désactivés

Les 10 derniers blogs postés

- Merci par Blog de Jérémy Jeanson le 10-01-2019, 20:47

- Office 365: Script PowerShell pour auditer l’usage des Office Groups de votre tenant par Blog Technique de Romelard Fabrice le 04-26-2019, 11:02

- Office 365: Script PowerShell pour auditer l’usage de Microsoft Teams de votre tenant par Blog Technique de Romelard Fabrice le 04-26-2019, 10:39

- Office 365: Script PowerShell pour auditer l’usage de OneDrive for Business de votre tenant par Blog Technique de Romelard Fabrice le 04-25-2019, 15:13

- Office 365: Script PowerShell pour auditer l’usage de SharePoint Online de votre tenant par Blog Technique de Romelard Fabrice le 02-27-2019, 13:39

- Office 365: Script PowerShell pour auditer l’usage d’Exchange Online de votre tenant par Blog Technique de Romelard Fabrice le 02-25-2019, 15:07

- Office 365: Script PowerShell pour auditer le contenu de son Office 365 Stream Portal par Blog Technique de Romelard Fabrice le 02-21-2019, 17:56

- Office 365: Script PowerShell pour auditer le contenu de son Office 365 Video Portal par Blog Technique de Romelard Fabrice le 02-18-2019, 18:56

- Office 365: Script PowerShell pour extraire les Audit Log basés sur des filtres fournis par Blog Technique de Romelard Fabrice le 01-28-2019, 16:13

- SharePoint Online: Script PowerShell pour désactiver l’Option IRM des sites SPO non autorisés par Blog Technique de Romelard Fabrice le 12-14-2018, 13:01