Bienvenue à Blogs CodeS-SourceS Identification | Inscription | Aide

Abonnements

L’après-midi du dev EF

Les webcasts de l’après-midi du dev sur EF que j’avais animé avec Michel sont en ligne sur http://msdn.microsoft.com/fr-fr/netframework/hh134069.aspx

Posté le mercredi 18 mai 2011 21:49 par Matthieu MEZIL | 0 commentaire(s)

Et si on parlait un peu de T4

Julien Dollon m’a récemment invité dans la webtv supinfo pour parler de T4 dans le cadre d’une série d’émission sur .NET.

Vous pouvez dès à présent visualiser la video ici.

Posté le mardi 3 mai 2011 11:19 par Matthieu MEZIL | 1 commentaire(s)

Session EF TechDays Genève

J’ai animé la semaine dernière une session sur Entity Framework ciblée DBA avec Christian Robert.

Vous pouvez dès à présent revoir cette session sur channel 9.

Posté le mercredi 13 avril 2011 00:39 par Matthieu MEZIL | 0 commentaire(s)

EF 4.1 RTW

EF 4.1 est désormais disponible en version RTW. Plus d’infos sur http://blogs.msdn.com/b/adonet/archive/2011/04/11/ef-4-1-released.aspx.

Posté le mercredi 13 avril 2011 00:36 par Matthieu MEZIL | 0 commentaire(s)

Classé sous : , ,

Session EF TechDays 2011
Tout est dans le titre : http://www.microsoft.com/france/mstechdays/showcase/player.aspx?uuid=813119d8-244f-4fc2-b5f7-1a328571c20b&parcours=TD11_DEV_OUTILS

Posté le samedi 19 mars 2011 09:45 par Matthieu MEZIL | 0 commentaire(s)

EF4.1 RC

L’équipe EF a publié la RC (go live) d’EF 4.1. Par rapport à la CTP5, on regrettera l’abandon (temporaire) des conventions customs.

Vous trouverez plus d’infos sur EF 4.1 Release Candidate Available, ef-4-1-code-first-walkthrough et ef-4-1-model-amp-database-first-walkthrough.

Le timming est parfait, juste avant l’après-midi du dev Entity Framework où j’espère vous voir nombreux.

Posté le jeudi 17 mars 2011 21:47 par Matthieu MEZIL | 6 commentaire(s)

L’art de compresser son code

Avant tout, je précise que ce post est complètement useless.

Comment juger de la qualité d’un code ?

En jugeant de sa rapidité d’exécution, de sa consommation en mémoire, parfois de la taille de celui-ci une fois compilé, très souvent de sa lisibilité.

Le problème avec ce dernier point est que la lisibilité est très relative. J’ai d’ailleurs un avis là-dessus très différent de celui de la plus part des développeurs que je cotoie. Certaines personnes (peut-être de mauvaise foie j’en conviens) vous expliqueront que plus un code est compact, plus il est rapide à relire donc plus il est lisible.

Ceci n’est pas uniquement le fruit de ma folie et / ou d’un moment de pure délire programmatique avec Roch. Des outils comme Resharper ont par exemple certaines règles qui vont en ce sens et que beaucoup trouveront hérétiques (comme l’utilisation systématique de var).

Prenons donc un exemple. Nous voulons afficher les 10 premiers multiples d’un nombre et la somme de ceux-ci :

using System;
 
class Program
{
     static void Main(string[] args)
     {
         int i = int.Parse(Console.ReadLine());
         int s = 0;
         for (int j = 1; j < 11; j++)
         {
             int s1 = i * j;
             Console.WriteLine(s1);
             s += s1;
         }
         Console.WriteLine(s);
     } }

Ce code pourrait être condensé de la façon suivante :

using i = System.Int32;
using c = System.Console;
 
class Program
{
     static void Main(string[] args)
     {
         i i=i.Parse(c.ReadLine()),s=0,s1;
         for(i j=1;j<11;c.WriteLine((s1=j++*i)),s+=s1);
         c.WriteLine(s);
     } }

De là à dire que c’est plus lisible, il y a un pas que je ne franchirai pas… Sourire

Un point intéressant maintenant : regardons l’IL généré.

Dans le second cas, nous avons :

.locals init
(
   [0]
int32 i,
   [1]
int32 s,
   [2]
int32 s1,
   [3]
int32 j,
   [4]
bool
CS$4$0000
)
L_0000: nop
L_0001: call string [mscorlib]System.Console::ReadLine()
L_0006: call int32 [mscorlib]System.Int32::Parse(string)
L_000b:
stloc.0
L_000c:
ldc.i4.0
L_000d:
stloc.1
L_000e:
ldc.i4.1
L_000f:
stloc.3
L_0010:
br.s L_0025
L_0012:
ldloc.3
L_0013:
dup
L_0014:
ldc.i4.1
L_0015:
add
L_0016:
stloc.3
L_0017:
ldloc.0
L_0018:
mul
L_0019:
dup
L_001a:
stloc.2
L_001b:
call void [mscorlib]System.Console::WriteLine(int32)
L_0020:
nop
L_0021:
ldloc.1
L_0022:
ldloc.2
L_0023:
add
L_0024:
stloc.1
L_0025:
ldloc.3
L_0026:
ldc.i4.s 11
L_0028:
clt
L_002a:
stloc.s CS$4$0000
L_002c:
ldloc.s CS$4$0000
L_002e:
brtrue.s L_0012
L_0030:
ldloc.1
L_0031:
call void [mscorlib]System.Console::WriteLine(int32)
L_0036:
nop
L_0037:
ret

Ce qui correspond à :

int i = int.Parse(Console.ReadLine());
int s = 0;
int j = 1;
while (j < 11)
{
     int s1;
     Console.WriteLine(s1 = j++ * i);
     s += s1; } Console.WriteLine(s);

Posté le mardi 8 mars 2011 23:09 par Matthieu MEZIL | 2 commentaire(s)

Classé sous : ,

Après-midi du dev : Entity Framework

J’aurai le plaisir d’animer avec Michel Perfetti une session sur Entity Framework le 18 mars après-midi. Vous pouvez vous inscrire ici.

Posté le dimanche 6 mars 2011 21:14 par Matthieu MEZIL | 0 commentaire(s)

Qu’est-ce qu’apporte la CTP5 d’EF ?

En décembre, MS a sorti la CTP5 d’EF.

La question est : qu’apporte-t-elle ?

Si vous avez un peu suivi l’actualité récente, vous vous en doutez probablement, elle apporte tout d’abord des améliorations à l’approche Code First.

Cependant, cela va beaucoup plus loin. Elle simplifie les usages avec EF, notament sur la gestion du cache, du tracking, des relations…

L’ADO.NET team a publié tout une série de posts sur ces améliorations dans le sommaire se trouve ici.

A titre personnel, j’ai un certain plaisir voire une pointe de fierté quand je vois certaines des idées que j’avais soumise implémentées par MS… :)

Posté le samedi 12 février 2011 11:52 par Matthieu MEZIL | 0 commentaire(s)

Classé sous : , ,

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

Posté le mardi 8 février 2011 01:38 par Matthieu MEZIL | 3 commentaire(s)

TechDays

Pour la quatrième fois consécutive, j’animerai une session aux TechDays sur Entity Framework.

Vous avez été plusieurs à regretter de ne pas voir de sessions avancées sur EF aux TechDays. Je n’ai malheureusement pas pu avoir un deuxième slot pour cela. Cependant, n’hésitez pas à me soliciter sur mon adresse mail mmezil@access-it.fr afin que l’on puisse se retrouver pendant l’évènement et je me ferai une joie de vous parler d’Entity Framework.

Posté le jeudi 3 février 2011 16:00 par Matthieu MEZIL | 0 commentaire(s)

Classé sous : , ,

Intellitrace : attention danger !!!

Je ne comprenais pas, j’avais beau désinstaller des programmes, j’avais toujours de moins en moins de place sur mon disque C jusqu’à obtenir 0 byte ce matin ! WTF ?

Réponse : l’intellitrace !!!

Dans le répertoire C:\ProgramData\Microsoft Visual Studio\10.0\TraceDebugging, j’ai 24 Go de fichiers .iTrace !!!

Posté le mercredi 2 février 2011 12:02 par Matthieu MEZIL | 2 commentaire(s)

Classé sous :

Urban Turtle

Mario Cardinal, l'animateur du Visual Studio Talk Show, est très heureux ces jours-ci. Il travaille avec l'équipe d'Urban Turtle et ils viennent de recevoir un appui de taille de Microsoft. Brian Harry, le Product Unit Manager de Team Foundation Server, a publié un billet élogieux à propos d'Urban Turtle qui dit : "...awesome Scrum experience for TFS." Vous pouvez lire le billet de Brian Harry à l'URL suivante : http://urbanturtle.com/awesome.

Posté le mardi 11 janvier 2011 20:10 par Matthieu MEZIL | 0 commentaire(s)

RIA Services, résolution des conflits

Avec Entity Framework, vous pouvez utiliser le comportement par défaut des accès concurrents : le dernier qui écrit a raison. Vous pouvez également récupérer une exception lorsque l’enregistrement a entre-temps était modifié par un autre utilisateur.

Avec RIA Services, si vous optez pour cette deuxième solution, vous pouvez gérer les conflits en décidant d’une mesure systématique : le dernier qui écrit a raison (dans ce cas c’est inutile de faire lever une exception à Entity Framework), soit lui dire de rafraîchir l’entité à partir de la base.

Pour cela, il suffit de surcharger la méthode ResolveConflicts du DomainService.

protected override bool ResolveConflicts(IEnumerable<ObjectStateEntry> conflicts)
{
    foreach (ObjectStateEntry conflict in conflicts)
        ObjectContext.Refresh(RefreshMode.StoreWins, conflict.Entity);
    return base.ResolveConflicts(conflicts);
}

Cependant, quand on veut aller un peu plus loin, cette solution n’est pas suffisante.

Imaginons qu’en cas de conflit, on veuille demander à l’utilisateur de choisir entre sa version et celle de la base.

Comment faire cela ?

Histoire de factoriser un peu le code, j’ai écrit une classe de base :

public abstract class ViewModelBase<DomainContextType, EntityType>
    where DomainContextType : DomainContext, new()
    where EntityType : Entity
{
    private DomainContextType _domainContext = new DomainContextType();
    private Action<ComparaisonViewModel<EntityType>> _compareEntities;
    private Func<EntityType, bool> _deleteCreation;

    public ViewModelBase(Action<ComparaisonViewModel<EntityType>> compareEntities, Func<EntityType, bool> deleteCreation, Func<DomainContextType, EntitySet<EntityType>> getEntitySet)
    {
        _compareEntities = compareEntities;
        _deleteCreation = deleteCreation;
        EntitySet = getEntitySet(_domainContext);
    }

    protected DomainContextType DomainContext
    {
        get { return _domainContext; }
    }

    private EntitySet<EntityType> _entitySet;
    protected EntitySet<EntityType> EntitySet
    {
        get { return _entitySet; }
        private set { _entitySet = value; }
    }

    private ICommand _saveCommand;
    public ICommand SaveCommand
    {
        get
        {
            return _saveCommand ?? (_saveCommand = new FuncCommand(() =>
            {
                DomainContext.SubmitChanges(so =>
                {
                    if (so.HasError)
                    {
                        Application.Current.RootVisual.Dispatcher.BeginInvoke((Action)(() =>
                        {
                            foreach (EntityType entity in so.EntitiesInError)
                            {
                                if (entity.EntityConflict.IsDeleted)
                                {
                                    EntitySet.Detach(entity);
                                    if (_deleteCreation(entity))
                                        EntitySet.Add(entity);
                                }
                                else
                                {
                                    EntityType dbEntity = (EntityType)entity.EntityConflict.StoreEntity;
                                    ComparaisonViewModel<EntityType> comparaisonviewModel = new ComparaisonViewModel<EntityType>() { Entity1 = entity, Entity2 = dbEntity };
                                    _compareEntities(comparaisonviewModel);
                                    comparaisonviewModel.Validated += () =>
                                    {
                                        if (comparaisonviewModel.SelectedEntity == entity)
                                            entity.EntityConflict.Resolve();
                                        else
                                        {
                                            EntitySet.Detach(entity);
                                            EntitySet.Attach(dbEntity);
                                        }
                                    };
                                }
                            }
                        }));
                        so.MarkErrorAsHandled();
                    }
                }, null);
            }));
        }
    }
}

 
public class ComparaisonViewModel<EntityType>
{
    public EntityType Entity1 { get; set; }
    public EntityType Entity2 { get; set; }

    public IEnumerable<EntityType> Entities
    {
        get
        {
            yield return Entity1;
            yield return Entity2;
        }
    }

    public EntityType SelectedEntity { get; set; }

    private ICommand _okCommand;
    public ICommand OkCommand
    {
        get
        {
            return _okCommand ?? (_okCommand = new FuncCommand(() =>
                {
                    if (Validated != null)
                        Validated();
                }));
        }
    }

    public event Action Validated;
}

On peut ensuite hériter de la classe ViewModelBase :

public class ProductViewModel : ViewModelBase<NorthwindDomainContext, Product>
{
    public ProductViewModel(Action<ComparaisonViewModel<Product>> compareProducts, Func<Product, bool> deleteCreation)
        : base(compareProducts, deleteCreation, northwindDomainContext => northwindDomainContext.Products)
    {
        DomainContext.Load<Product>(DomainContext.GetProductsQuery());
    }

    public EntitySet<Product> Products
    {
        get { return DomainContext.Products; }
    }
}

Le choix par l’utilisateur devant être fait par la vue qui ne doit pas être référencée par le ViewModel, on passe par les délégués :

DataContext = new ProductViewModel(
    productComparaisonViewModel =>
    {
        ProductComparaison productComparaison = new ProductComparaison(productComparaisonViewModel);
        productComparaison.Show();
    },
    product => MessageBox.Show(string.Format("The product {0} was deleted, do you want to create it again?", product.ProductName), "", MessageBoxButton.OKCancel) ==
        MessageBoxResult.OK);

Hope that helps !

Posté le lundi 13 décembre 2010 23:50 par Matthieu MEZIL | 1 commentaire(s)

EF4 CTP5

Avec la sortie d’EF4 CTP5, on a plusieurs améliorations. Pour ma part, je pense que les principales sont :

· L’amélioration du mode Code First

· L’ajout d’attribut de validation des données

· L’intégration d’un T4 pour générer un DBContext

· Une propriété Local sur les DBSet afin de récupérer une ObservableCollection sur les entités du cache. Ainsi, on peut accéder directement et beaucoup plus facilement aux entités déjà chargées dans le cache sans effectuer de requête en base

· L’ajout de l’extension method AsNoTracking sur les IQueryable<T> ce qui évite de caster les IQueryable<T> en ObjectQuery

· Une amélioration de la gestion des conflits de modifications concurrentiels

Pour plus d’info, je vous renvoie sur les posts de l’ADO.NET Team, de Scott Gu et de Julie :

http://blogs.msdn.com/b/adonet/archive/2010/12/06/ef-feature-ctp5-released.aspx

http://blogs.msdn.com/b/adonet/archive/2010/12/06/ef-feature-ctp5-code-first-walkthrough.aspx

http://blogs.msdn.com/b/adonet/archive/2010/12/06/ef-feature-ctp5-model-amp-database-first-with-dbcontext.aspx

http://blogs.msdn.com/b/adonet/archive/2010/12/06/ef-feature-ctp5-fluent-api-samples.aspx

http://weblogs.asp.net/scottgu/archive/2010/12/08/announcing-entity-framework-code-first-ctp5-release.aspx

http://thedatafarm.com/blog/data-access/looking-at-ef4-ctp5-in-parts-part-1-a-new-t4-template

http://thedatafarm.com/blog/data-access/looking-at-ef4-ctp5-in-parts-part-2-ndash-internal-validation

http://thedatafarm.com/blog/data-access/looking-at-ef4-ctp5-in-parts-part-3-easy-access-to-in-memory-entities

http://thedatafarm.com/blog/data-access/looking-at-ef4-ctp5-in-parts-part-4-ndash-working-with-new-and-existing-databases-in-code-first

Posté le mercredi 8 décembre 2010 11:38 par Matthieu MEZIL | 0 commentaire(s)

Classé sous : , ,

Générer votre service WCF à partir de votre edmx en utilisant T4

Cela fait maintenant plus d’un an que j’ai écrit ces T4 (que j’avais présenté lors de ma session aux techdays et au MVP Summit) cependant, étant indécis sur ce que je devais en faire + le fait que le passage de la Beta2 à la RTM avait rajouté des bugs (go live ? :p) que j’avais jamais pris le temps de corriger et mes T4 “prenaient la poussière au fond de mon disque dur”….

Vous étiez pourtant nombreux à être intéressé par ma solution.

De mon point de vue, je la trouve particulièrement intéressante, notamment en comparaison avec WCF Data Services et WCF RIA Services :

  • elle permet de garder la souplesse du meta-code en opposition avec la rigidité d’une technologie
  • elle offre un vrai contexte côté client
  • elle gère les relations many to many (ce que ne supporte pas encore WCF RIA Services)
  • etc.

J’ai donc pris le temps cette nuit de corriger les bugs et je viens de les partager sur codeplex http://edmwcf.codeplex.com/

Enjoy Sourire

Posté le samedi 27 novembre 2010 09:24 par Matthieu MEZIL | 4 commentaire(s)

EDMDesigner v2 : PRISM et Unity

Suite aux remarques de Thomas et John, ce post a été mis à jour.

Certains d’entre vous le savent, je suis en train de redévelopper mon EDM Designer.

Tout d’abord, la première version était mon premier projet WPF et si je devais le faire aujourd’hui, il est évident que je ne procèderais pas de la même façon (à l’époque, je n’avais pas utilisé MVVM par ex).

De plus, ma première version n’était pas autonome. Il fallait dans un premier temps récupérer l’edmx généré à partir d’une base de données avec VS et ensuite seulement on pouvait travailler avec mon designer

A cela, il faut rajouter que ma version se basait sur EF v1 (EF4 n’existant pas encore à l’époque).

J’ai donc décidé de repartir d’une feuille blanche et de proposer de nombreuses nouvelles fonctionnalités qui, je suis sûr, ne manqueront pas de vous ravir mais dont je garde le secret pour l’instant. Clignement d'œil // A ce propos, n’hésitez pas à me soumettre vos idées.

Parce que je trouve certains points de mon dev intéressants mais également parce que je suis friand de critiques constructives, j’ai décidé de ne pas développer seul dans mon coin mais de faire une série de post afin de vous faire partager les choix que j’ai pu faire et recueillir vos avis sur ceux-ci.

L’idée que je vais développer dans ce post est de rendre mon EDM Designer le plus modulaire possible afin de pouvoir

  • développer un module par version d’Entity Framework sans tout redévelopper à chaque nouvelle version
  • permettre de choisir dans l’application la version d’Entity Framework que l’on souhaite utiliser
  • donner la possibilité d’intégrer d’autre base de données que SQL Server
  • permettre d’externaliser la gestion du pluralize / singularize
  • etc.

Pour l’aspect modulaire, j’ai utilisé PRISM couplé avec Unity.

Plusieurs personnes m’ont dit que PRISM et Unity c’était des trucs pour la “branlette intellectuelle” d’architecte. Je vais donc tenter à travers cet article de leur prouver le contraire.

Unity est particulièrement utile pour mocker nos classes. Cependant, dans mon cas, je ne vais pas l’utiliser pour cela.

Avant de regarder plus en détail la solution que je propose, voici la vision du solution explorer :

image

Dans mon cas je vais utiliser PRISM pour définir les versions d’Entity Framework disponible dans mon application : en l’occurrence, EF v1 et EF 4.

Pour cela, je vais définir les modules que je veux utiliser dans mon App.config :

  
    
      
        <
      
    
    
      
        configuration
      
    
  
  
    
      
        >
        
  <
configSections
>
    <
section   name = "modules"
             type="Microsoft.Practices.Composite.Modularity.ModulesConfigurationSection, Microsoft.Practices.Composite"
/>
  </
configSections
>
  <
modules
>
    <
module   assemblyFile = "EDM.v1.dll"
            moduleType="EDM.v1.Module, EDM.v1"
            moduleName="EDM.v1"
/>
    <
module   assemblyFile = "EDM.v4.dll"
            moduleType="EDM.v4.Module, EDM.v4"
            moduleName="EDM.v4"
/>
  </
modules
>
</
configuration
>

Dans le constructeur de la classe App, je vais faire appel à mon Bootstrapper :

  
    
      
        public
      
    
     App()
{
    new Bootstrapper().Run();
}

Dans mon Bootstrapper, j’utilise le UnityBootstrapper fournit par PRISM afin d’instancier chacun des différent modules puis de créer mon Shell et l’afficher :

  
    
      
        public
      
    
     class Bootstrapper : UnityBootstrapper
{
    protected override DependencyObject CreateShell()
    {
        ConfigurationModuleCatalog moduleCatalog = new ConfigurationModuleCatalog();
        Container.RegisterInstance<IModuleCatalog>(moduleCatalog);

        Container.RegisterType<IModuleManager, ModuleManager>();
        IModuleManager moduleManager = Container.Resolve<IModuleManager>();
        moduleManager.Run();



  
  
  
    
      
        Container.RegisterType<IShellViewModel, ShellViewModel>();
        Shell shell = Container.Resolve<Shell>();
        shell.Show();

        return shell;
    }
}

J’utilise l’approche View First (ie : c’est la vue qui instancie le ViewModel). On pourrait croire que je fais du ViewModel first en voyant le code mais en réalité ce n’est pas le cas parce que j’utilise Unity.

  
    
      
        public
      
    
     partial class Shell : Window
{
    public Shell(IShellViewModel shellViewModel)
    {
        InitializeComponent();
        DataContext = shellViewModel;
    }
}

Dans mon cas, IShellViewModel étant associé avec ShellViewModel dans mon UnityContainer, lors de l’instanciation de mon Shell, unity va m’instancier un ShellViewModel pour le passer en paramètre.

Mon ShellViewModel va me retourner la liste des modules disponibles afin que je puisse les afficher dans une ListBox.

  
    
      
        <
      
    
    
      
        ListBox
      
      
         x
      
      
        :
      
      
        Name
      
      
        =
        "modulesLB"
      
      
        ItemsSource="{Binding Modules}"
        DisplayMemberPath="Name"
/>

  
    
      
        public
      
    
     interface IShellViewModel
{
    IEnumerable<EDMModuleBase> Modules { get; }
}

Comment récupérer la liste des modules ?

Le code que j’utilise est le suivant :

  
    
      
        public
      
    
     class ShellViewModel : IShellViewModel
{
    public ShellViewModel(Func<IEnumerable<IEDMModule>> modules)
    {
        Modules = modules();
    }

    public IEnumerable<IEDMModule> Modules { get; private set; }
}

Pour pouvoir l’utiliser il faut donc au préalable enregistrer les différents modules dans le container unity.

Je vais effectuer cette action dans le constructeur de mon module :

  
    
      
        public
      
    
     class Module : EDMModuleBase
{
    public Module(IUnityContainer unityContainer)
        : base(unityContainer)
    {
    }

    public override string Name
    {
        get { return "EDM v1"; }
    }
}

  
    
      
        public
      
    
     abstract class EDMModuleBase : IEDMModule
{
    public EDMModuleBase(IUnityContainer unityContainer)
    {
        unityContainer.RegisterInstance<EDMModuleBase>(Name, this);
    }

    public abstract string Name { get; }

    void IModule.Initialize()
    {
    }
}

  
    
      
        public
      
    
     interface IEDMModule : IModule
{
    string Name { get; }
}

Ainsi mes deux modules sont bien ajoutés dans le UnityContainer  lors de leur instanciation dans le Bootstrapper ce qui permet à mon ViewModel de les retrouver.

Maintenant, allons un peu plus loin.

La structure d’un EDM (avec une partie SSDL, une partie CSDL et partie MSL) est la même pour les versions 1 et 4 d’Entity Framework.

Aussi, je n’ai pas envie de redéfinir la classe EDM pour EF4. En revanche, le CSDL lui s’est enrichi avec EF4 (ajout des CSDL Functions). Du coup, je veux que ma classe EDM définie dans EDM.Base m’instancie un EDM.v4.CSDL quand j’utilise le Module v4 et un EDM.Base.CSDL quand j’utilise la v1.

Comment faire cela ?

Unity va bien nous aider.

En partant du UnityContainer principal, je vais définir un ChildContainer dans chacun des modules. Dans le ChildContainer du Module v4, je vais associer le type EDM.Base.CSDL avec le type EDM.v4.CSDL puis je vais rajouter une propriété EDM dans mon module qui sera créé par mon ChildContainer et la classe EDM va utiliser ce UnityContainer pour instancier le CSDL :

public  interface IEDMModule : IModule
{
    string Name { get; }
    EDM.Base.EDM EDM { get; }
}

  
    
      
        public
      
    
     abstract class EDMModuleBase : IModule
{
    public EDMModuleBase(IUnityContainer unityContainer)
    {
        unityContainer.RegisterInstance<EDMModuleBase>(Name, this);
        IUnityContainer childContainer = unityContainer.CreateChildContainer();
        Init(childContainer);
        EDM = childContainer.Resolve<EDM.Base.EDM>();
    }

    protected virtual void Init(IUnityContainer unityContainer)
    {
    }

    public abstract string Name { get; }

    public EDM.Base.EDM EDM { get; private set; }

    void IModule.Initialize()
    {
    }
}

  
    
      
        namespace
      
    
     EDM.v4
{
    public class Module : EDMModuleBase
    {
        public Module(IUnityContainer unityContainer)
            : base(unityContainer)
        {
        }

        protected override void Init(IUnityContainer unityContainer)
        {
            base.Init(unityContainer);
            unityContainer.RegisterType<Base.CSDL, CSDL>();
        }

        public override string Name
        {
            get { return "EDM v4"; }
        }
    }
}

  
    
      
        public
      
    
     class EDM
{
    public EDM(SSDL ssdl, CSDL csdl, MSL msl)
    {
        SSDL = ssdl;
        CSDL = csdl;
        MSL = msl;
    }

    public SSDL SSDL { get; private set; }
    public CSDL CSDL { get; private set; }
    public MSL MSL { get; private set; }

    public virtual IEnumerable<IEDMElement> EDMElements
    {
        get
        {
            yield return SSDL;
            yield return CSDL;
            yield return MSL;
        }
    }
}

Vérifions avec un petit exemple :

  
    
      
        <
      
    
    
      
        Grid
      
      
        >
      
      
    < Grid.ColumnDefinitions >
        < ColumnDefinition />
        < ColumnDefinition />
    </ Grid.ColumnDefinitions >
    < ListBox x : Name = "modulesLB"
            ItemsSource="{Binding Modules}"
            DisplayMemberPath="Name" />
    <Grid Grid.Column="1">
        <ListBox ItemsSource="{Binding SelectedItem.EDM.EDMElements, ElementName=modulesLB}" />
    </Grid
>
</
Grid
>

image

image

Posté le samedi 27 novembre 2010 00:42 par Matthieu MEZIL | 10 commentaire(s)

Classé sous : , , , ,

Pourquoi utiliser Entity Framework ?

Certains l’auront remarqué, j’ai encore pris un an de plus. Et j’ai décidé qu’il était temps pour moi d’écouter un peu les vieux :p (je pense particulièrement à Mitsu et Redo en l’occurrence que je remercie par la même occasion pour me faire profiter de leur expérience)

A travers cet article, je vais tenter de rester à un niveau 100/200 (ce qui constitue un vrai challenge pour moi que m’a fixé Redo ;)) afin de vous convaincre des bienfaits de l’Entity Framework.

Pour cela, nous allons partir d’un exemple simple et nous allons étudier l’implémentation avec les DataReaders, LINQ To SQL et Entity Framework.

Dans notre exemple, la base est la suivante :

image

La méthode que je veux implémenter doit retourner les 10 meilleurs membres  (par rapport au total qu’ils ont dépensés) avec leurs propriétés Points, CompanyName, ContactName et le montant qu’ils ont dépensé.

Pour cela, nous utiliserons une classe MemberInfo :

public class MemberInfo
{
   
public string CustomerId { get; set
; }
   
public string CompanyName { get; set
; }
   
public string ContactName { get; set
; }
   
public int Points { get; set
; }
   
public double Amount { get; set; }
}
 

DataReader


Commençons avec l’implémentation utilisant les DataReaders.

public static List<MemberInfo> GetMembers()
{
    List<MemberInfo> value = new List<MemberInfo>();
    using (SqlConnection connection = new SqlConnection(ConfigurationManager.ConnectionStrings["Northwind"].ConnectionString))
    {
        connection.Open();
        using (SqlCommand command = new SqlCommand("SELECT TOP 10 MI.CustomerID, MI.CompanyName, MI.ContactName, MI.Points, COALESCE(MI.Amount,0) AS Amount FROM ( " +
            "SELECT C.CustomerID, C.CompanyName, C.ContactName, M.Points, ( SELECT SUM(OD.UnitPrice * OD.Quantity * (1 - OD.Discount)) FROM Orders O " +
            "INNER JOIN OrderDetails OD ON OD.OrderID = O.OrderID WHERE O.CustomerID = C.CustomerID) AS Amount FROM Customers C " +
            "INNER JOIN Members M on M.CustomerID = C.CustomerID ) MI ORDER BY MI.Amount DESC", connection))
        {
            using (SqlDataReader dataReader = command.ExecuteReader())
            {
                while (dataReader.Read())
                    value.Add(new MemberInfo { CustomerId = dataReader.GetString(0), CompanyName = dataReader.GetString(1),
                        ContactName = dataReader.IsDBNull(2) ? null : dataReader.GetString(2), Points = dataReader.GetInt32(3), Amount = dataReader.GetDouble(4) });
            }
        }
    } return value;
}

 

Ce code est un peu pénible à écrire à cause de la gestion de la Connection, de la Command et du DataReader.  

De plus, le fait de passer la requête en chaîne de caractères n’est évidemment pas l’idéal : difficile à relire, avec potentiellement des fautes de frappe qui ne seront visibles qu’à l’exécution sous la forme d’une exception.

Enfin, notre code est lié à SQL Server.

LINQ To SQL


Implémentons maintenant ce même exemple avec LINQ To SQL.

image

Avec LINQ To SQL, le code est le suivant :

public static List<MemberInfo> GetMembers()
{
    
using (NorthwindDataContext context = new NorthwindDataContext
())
     {
        
return (from m in
context.Members
                
let
c = m.Customer
                
let amount = (double?)c.Orders.SelectMany(o => o.OrderDetails).Sum(od => (double)od.Quantity * (double
)od.UnitPrice * (1 - od.Discount))
                
orderby amount descending                  select new MemberInfo { CustomerId = c.CustomerID, CompanyName = c.CompanyName, ContactName = c.ContactName, Points = m.Points, Amount = amount ??
0 }).Take(10)
                .ToList();
     }
}
 

Reprenons les points évoqués précédemment :

Ce code est beaucoup moins peu pénible à écrire : pas besoin de gérer la Connection, la Command et le DataReader.

De plus, en utilisant une requête LINQ (LINQ To SQL ici), nous bénéficions de l’intellisense et des navigation properties ce qui augmente notre productivité. Nous n’avons pas besoin de connaître et C# (ou VB .NET) et T-SQL, C# ou VB.Net étant suffisants. De plus, la coloration syntaxique augmente la lisibilité de la requête. Enfin, la requête est compilée ce qui veut dire qu’on élimine les risques de faute de frappe visible qu’à l’exécution. Si on fait une faute de frappe, notre code ne compilera simplement pas.

En revanche, notre code reste lié à SQL Server.

Entity Framework


Contrairement à LINQ To SQL, Entity Framework possède un vrai modèle de mapping permettant d’envisager une conception des entités différentes de la conception de la base. Cela est très important. En effet, les entités étant conçues (dans le cas de C# ou VB.NET par exemple) avec une conception orientée objet alors que la base sera conçue avec une conception relationnelle. Entre ces deux conceptions les contraintes ne sont pas les mêmes (l’exemple le plus simple qui me vienne à l’esprit est le cas des relations many to many qui n’existent pas dans une conception relationnelle obligeant le passage par une 3ème table). De ce fait, les conceptions optimales des entités et de la base peuvent ainsi être relativement différente qui impose parfois un mapping complexe entre les deux. Une des grandes forces d’Entity Framework repose dans le fait que toute la complexité de ce mapping est gérée par le Framework.

Dans le cas de notre exemple, nous ne travaillons que sur les Members il n’est donc pas judicieux de gérer à la fois une entité Member et une entité Customer.

Aussi dans notre cas, nous allons définir l’Entity Data Model suivant :

image

Cela va ainsi nous permettre de profiter des propriétés CustomerID, CompanyName, ContactName et Points directement sur la même entité : Member.

Cela va donc également simplifier la requête LINQ (LINQ To Entities pour Entity Framework) :

public static List<MemberInfo> GetMembers()
{
    
using (NorthwindEntities context = new NorthwindEntities
())
     {
        
return (from m in
context.Members
                
let amount = (double?)m.Orders.SelectMany(o => o.OrderDetails).Sum(od => (double)od.Quantity * (double
)od.UnitPrice * (1 - od.Discount))
                
orderby amount descending
                 select new MemberInfo { CustomerId = m.CustomerID, CompanyName = m.CompanyName, ContactName = m.ContactName, Points = m.Points, Amount = amount ??
0 }).Take(10)
                .ToList();
     }
}
 

Entity Framework n’étant pas limité à SQL Server, nous avons ainsi pu résoudre l’ensemble des problématiques que nous avions évoquées avec la première implémentation (en utilisant les DataReaders)

Conlusion


LINQ nous a permis de :

·         Gagner en productivité et en lisibilité

·         Nous abstraire du SQL  en exécutant nos requêtes directement sur nos entités


Entity Framework nous a permis de :

·         Nous abstraire du provider de base de données. Dans notre cas, il est par exemple possible de passer d’une Oracle à une base SQL Server sans toucher une seule ligne de code !

·         Nous abstraire de la base lors de la conception des entités de façon  à nous permettre de concevoir celles-ci en fonction de leur utilisation et non en fonction des contraintes liées au modèle de persistance.


S’il fallait encore vous convaincre, je rajouterai le fait qu’Entity Framework s’inscrit vraiment, de mon point de vue, comme LA techno d’accès aux données dans la stratégie de Microsoft. Il suffit d’utiliser WCF RIA Services ou WCF Data Services pour s’en convaincre. Ces technos peuvent être utilisées sans Entity Framework mais la productivité est telle quand on les associe à Entity Framework qu’il serait dommage de s’en passer.

 

Posté le jeudi 25 novembre 2010 00:59 par Matthieu MEZIL | 12 commentaire(s)

WCF RIA Services ne doit pas vous faire oublier les bonnes pratiques d’Entity Framework

Suite à la pertinente remarque de Simon, j’ai modifié le code ci-dessous par rapport à la version originale.

Les démos WCF RIA Services utilisent quasiment toutes Entity Framework Sourire mais en l’utilisant de façon ultra basique et donc pas toujours de façon optimale. Triste Je pense que ça peut se comprendre puisque l’idée est de faire une démo de WCF RIA Services mais que ça a un côté dangereux car, sortie de l’aspect démo, les utilisateurs de la “vraie vie” vont souvent l’utiliser de la même façon.

Comme je l’avais expliqué dans ce post, la méthode Include est sympa mais pourrie en perf dans sa version actuelle.

Avec WCF RIA Services, il est possible d’utiliser l’attribut Include sur les metadatas pour récupérer les entités liées en même temps que l’entité qu’on requête.

Je vous invite à regarder le post d’Audrey sur le sujet pour en savoir plus.

Maintenant côté code, la manière basique de la démo serait d’écrire ceci :

public IQueryable<Customer> GetCustomers()
{
    return this.ObjectContext.Customers;
}

public IQueryable<Customer> GetCustomersWithOrders()
{
    return this.ObjectContext.Customers.Include("Orders");
}

Cependant, il est beaucoup plus performant d’écrire ceci de charger les orders séparément :

public IQueryable<Customer> GetCustomers()
{
    return this.ObjectContext.Customers;
}

public IQueryable<Customer> GetCustomersWithOrders()
{
    return this.ObjectContext.Customers;
}

Du coup, je surcharge la méthode Query :

public override IEnumerable Query(QueryDescription queryDescription, out IEnumerable<ValidationResult> validationErrors, out int totalCount)
{
    var value = base.Query(queryDescription, out validationErrors, out totalCount);
    if (queryDescription.Method.Name == ReflectionUtil.GetMethodName(() => GetCustomersWithOrders()))
    {
        IEnumerable<string> customerIds = value.Cast<Customer>().Select(c => c.CustomerID);
        foreach (var order in ObjectContext.Orders.Where(o => customerIds.Contains(o.CustomerID))) ;
    }
    return value;
}

avec le code suivant pour ma classe ReflectionUtil:

public static class ReflectionUtil
{
    public static string GetMethodName(Expression<Action> action)
    {
        MethodCallExpression methodCallexpression = action.Body as MethodCallExpression;
        if (methodCallexpression != null)
            return methodCallexpression.Method.Name;
        return null;
    }
}

Posté le mardi 16 novembre 2010 11:31 par Matthieu MEZIL | 12 commentaire(s)

async et await : l’asynchrone avec C#5 ça surmégapoutre !

Il y a plein de bonnes raisons de faire de l’asynchrone. Parfois c’est même obligatoire (dans SL pour appeler des services par exemple).

Cependant, la gestion de l’asynchronisme en C# implique souvent une perte de lisibilité et de maintenabilité du code.

Prenons un exemple très simple :

On veut afficher un texte via une Uri :

public void AsyncIntroSingleBefore()
{
    var client = new WebClient();

    client.DownloadStringCompleted += AsyncIntroSingleBefore_DownloadStringCompleted;
    client.DownloadStringAsync(new Uri("http://www.weather.gov"));
}

private void AsyncIntroSingleBefore_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
    Foo(e.Result);
}

Avec C#5, on a la possibilité de remplacer ce code par :

public async void AsyncIntroSingle()
{
    Foo(await new WebClient().DownloadStringTaskAsync(new Uri("http://www.weather.gov")));
}

L’intérêt est encore plus flagrant avec l’enchainement séquentiel des appels :

public void AsyncIntroSerialBefore()
{
    var client = new WebClient();

    client.DownloadStringCompleted += AsyncIntroSerialBefore_DownloadStringCompleted_1;
    client.DownloadStringAsync(new Uri("http://www.weather.gov"));
}

void AsyncIntroSerialBefore_DownloadStringCompleted_1(object sender, DownloadStringCompletedEventArgs e)
{
    Foo(e.Result);

    var client = new WebClient();

    client.DownloadStringCompleted += AsyncIntroSerialBefore_DownloadStringCompleted_2;
    client.DownloadStringAsync(new Uri("http://www.weather.gov/climate/"));
}

void AsyncIntroSerialBefore_DownloadStringCompleted_2(object sender, DownloadStringCompletedEventArgs e)
{
    Foo(e.Result);

    var client = new WebClient();

    client.DownloadStringCompleted += AsyncIntroSerialBefore_DownloadStringCompleted_3;
    client.DownloadStringAsync(new Uri("http://www.weather.gov/rss/"));
}

void AsyncIntroSerialBefore_DownloadStringCompleted_3(object sender, DownloadStringCompletedEventArgs e)
{
    Foo(e.Result);
}

qui devient tout simplement :

public async void AsyncIntroSerial()
{
    var client = new WebClient();

    Foo(await client.DownloadStringTaskAsync(new Uri("http://www.weather.gov")));
    Foo(await client.DownloadStringTaskAsync(new Uri("http://www.weather.gov/climate/")));
    Foo(await client.DownloadStringTaskAsync(new Uri("http://www.weather.gov/rss/")));
}

Perso je trouve que ça surmégapoutre !!!

Autre nouveauté : l’introduction de la notion de Task pour Silverlight.

Plus d’infos sur http://blogs.msdn.com/b/ericlippert/archive/2010/10/28/asynchrony-in-c-5-part-one.aspx, sur la video de Toub ToubTaskAsyncDeepDive_ch9.wmv (619 MB) et celle d’Anders http://channel9.msdn.com/Blogs/Charles/Anders-Hejlsberg-Introducing-Async.

Posté le jeudi 28 octobre 2010 22:02 par Matthieu MEZIL | 12 commentaire(s)



Les 10 derniers blogs postés

- Etendre le Team Web Access de TFS 2012 – Step 0 par Philippe Didiergeorges Aka Philess le 05-23-2013, 23:48

- Simuler facilement l’envoi de mail par Blog de Jérémy Jeanson le 05-22-2013, 12:52

- ProcDump 6.0 : support du filtrage sur messages d'exceptions .NET, des filtres multiples et du ciblage par nom de service par CoqBlog le 05-20-2013, 14:50

- Votez pour le TOP 10 des influenceurs SharePoint francophones ! par Le blog de Patrick [MVP SharePoint] le 05-20-2013, 12:59

- [Conf’SharePoint] Dernier rappel ! :-) par Le blog de Patrick [MVP SharePoint] le 05-20-2013, 09:09

- [ #SharePoint 2013 ] les modèles de sites standards… par Le blog de Patrick [MVP SharePoint] le 05-20-2013, 09:03

- 10 erreurs de compréhension concernant SharePoint… par Le blog de Patrick [MVP SharePoint] le 05-20-2013, 08:27

- Conf’SharePoint : 10 bonnes raisons pour ne pas la rater par Le petit blog de Pierre / Pierre's little blog le 05-14-2013, 02:24

- [Event] Soirée de lancement Agile .NET France à Lyon par Blog Agile/ALM de Vincent THAVONEKHAM le 05-13-2013, 01:29

- .NET / Debug : inspection de la mémoire d'applications .NET (dump ou processus live) : première livraison d'une librairie .NET par Microsoft par CoqBlog le 05-11-2013, 22:21