Bienvenue à Blogs CodeS-SourceS Identification | Inscription | Aide

Abonnements

Roslyn : générer des InvokeActivity avec T4

Le titre peut paraître bizarre mais c’est un vrai besoin rencontré chez un client pour lequel je vais utiliser Roslyn.

Imaginez le scénario suivant : dans un projet, il y a des développeurs et des experts fonctionnels. Les experts fonctionnels doivent "développer" des Workflows. Les développeurs vont développer des méthodes qui seront utilisées dans les workflows.

Le problème avec ça c’est les InvokeActivity. Pour les utiliser, ils doivent connaîtrent la signature de la méthode pour spécifier les bons types des paramètres / résultat. Un autre problème c’est qu’il faut écrire manuellement le nom de la méthode avec potentiellement le risque de faute de frappe / mise à jour.

Les experts fonctionnels préfèreraient faire du drag and drop des activités dans le workflow.

Donc mon idée est d’encapsuler les InvokeActivity dans des Activity. Maintenant, ça peut être très pénible pour les développeurs de faire ces activités.

Pour cela, il serait plus simple que les développeurs décorent les méthodes en utilisant un Attribut et ils pourraient utiliser un T4 pour générer ces activités.

Le T4 utilise Roslyn pour récupérer les informations sur les méthodes.

Ce T4 va utiliser le ttinclude d’Entity Framework EF.Utility.CS.ttinclude qui intègre déjà la création de plusieurs fichiers depuis un T4 (dans mon casje veux un fichier par méthode).

<#@ include file="EF.Utility.CS.ttinclude"#>

Ensuite, le T4 référence les assemblies de Roslyn :

<#@ assembly name="C:\Program Files (x86)\Reference Assemblies\Microsoft\Roslyn\v1.0\Roslyn.Compilers.dll"#>

<#@ assembly name="C:\Program Files (x86)\Reference Assemblies\Microsoft\Roslyn\v1.0\Roslyn.Compilers.CSharp.dll"#>

<#@ assembly name="C:\Program Files (x86)\Reference Assemblies\Microsoft\Roslyn\v1.0\Roslyn.Services.dll"#>

<#@ assembly name="C:\Program Files (x86)\Reference Assemblies\Microsoft\Roslyn\v1.0\Roslyn.Services.CSharp.dll"#>

<#@ assembly name="C:\Program Files (x86)\Reference Assemblies\Microsoft\Roslyn\v1.0\Roslyn.Services.VisualBasic.dll"#>

Le chemin vers la solution et le nom de projet sont spécifiés au début du T4. Donc maintenant, on peut charger la solution en utilisant Roslyn et récupérer les méthodes décorées avec l’attribut dans le projet spécifié.

var solution = Solution.Load(solutionPath);

var project = solution.Projects.First(p => p.AssemblyName == projectAssemblyName);

foreach (var document in project.Documents)

{

    //…

}

Ensuite, pour identifier les méthodes, on utilisera un SyntaxVisitor:

public class InvokeActivityAttributeVisitor : SyntaxVisitor<object>

{

    protected override object VisitCompilationUnit(CompilationUnitSyntax node)

    {

        foreach (var n in node.ChildNodes())

            Visit(n);

        return null;

    }

 

    protected override object VisitNamespaceDeclaration(NamespaceDeclarationSyntax node)

    {

        foreach (var n in node.ChildNodes())

            Visit(n);

        return null;

    }

 

    protected override object VisitTypeDeclaration(TypeDeclarationSyntax node)

    {

        foreach (var n in node.ChildNodes())

            Visit(n);

        return null;

    }

 

    protected override object VisitMethodDeclaration(MethodDeclarationSyntax node)

    {

        if (node.Modifiers.Any(st => st.Kind == SyntaxKind.PublicKeyword) && node.Attributes.Any(a => a.Attributes.Any(a2 => Regex.IsMatch(a2.Name.GetFullText(), @"^(Roslyn.WF.ActivityGenerator.)?InvokeActivity$"))))

        {

            string methodName = node.Identifier.GetText();
            var returnTypeAsPredefinedTypeSyntax = node.ReturnType as PredefinedTypeSyntax;
            if (returnTypeAsPredefinedTypeSyntax == null || returnTypeAsPredefinedTypeSyntax.Keyword.Kind != SyntaxKind.VoidKeyword)
            {
                //…       
            }
            foreach (var parameter in node.ParameterList.Parameters)
            {
                //…       
            }

        }

    }

}

Maintenant le point important est de connaître le namespace et l’assembly des types utilisés dans les méthodes. Pour cela, on utilisera les symbols de compilation :

_syntaxTree = (SyntaxTree)_document.GetSyntaxTree();
_semanticModel = _document.Project.GetCompilation().GetSemanticModel(_syntaxTree);
var returnTypeSymbol = _semanticModel.GetSemanticInfo(node.ReturnType).Symbol;
string returnTypeAssemblyName = returnTypeSymbol.ContainingAssembly.AssemblyName.Name;
string returnTypeNamespaceName = returnTypeSymbol.ContainingNamespace.ToString();

Il faut également savoir si les paramètres sont en In/Out/InOut:

parameter.Modifiers.Any(st => st.Kind == SyntaxKind.RefKeyword) ? Direction.InOut : (parameter.Modifiers.Any(st => st.Kind == SyntaxKind.OutKeyword) ? Direction.Out : Direction.In)

Le reste du code est basique, utilisé pour générer les activités.

Vous pouvez télécharger le code ici.

Posté le jeudi 20 octobre 2011 09:02 par Matthieu MEZIL | 0 commentaire(s)

Classé sous : , ,

SL5 : ICustomTypeProvider

On a souvent le problème suivant avec SL4 : dans le Model on récupère une collection d’entités et dans le ViewModel, on veut récupérer cette collection en y ajoutant de l’intelligence (par exemple des propriétés calculées).

Dans ce cas, il faut gérer deux collections et propager les modifications dans les deux sens. Une bonne pratique consiste à utiliser des "RelayCollection" pour s’occuper de cette propagation.

En .NET, on peut ajouter de "fausses" propriétés utilisées pour le binding en implémentant l’interface ICustomTypeDescriptor mais celle-ci n’existe pas dans Silverlight.

Cependant, avec SL5, on peut utiliser l’interface ICustomTypeProvider.

J’ai repris le code d’Alexandra et j’ai tenté de l’améliorer.

Cela nous donne le code suivant :

public abstract class DynamicBaseType
{
   
public abstract object GetPropertyValue(string
propertyName);
   
public abstract void SetPropertyValue(string propertyName, object
value);
}
public abstract class DynamicBaseType<T> : DynamicBaseType, ICustomTypeProvider, INotifyPropertyChanged
    where T : DynamicBaseType
<T> {
   
private static List<CustomPropertyInfo> _customProperties = new List<CustomPropertyInfo
>();
   
private Dictionary<string, object
> _customPropertyValues;
   
private CustomType
_customtype;

   
protected
DynamicBaseType()
    {
        _customPropertyValues =
new Dictionary<string, object
>();
       
foreach (var property in
_customProperties)
            _customPropertyValues.Add(property.Name,
null
);
    }

   
public static void AddProperty(string name, Type type, object value = null, List<Attribute> attributes = null
)
    {
       
if
(!CheckIfNameExists(name))
            _customProperties.Add(
new CustomPropertyInfo
(name, type, value, attributes));
    }

   
public static void AddProperty<V>(string name, Func<T, V> get, Action<T, V> set = null, List<Attribute> attributes = null, string[] properties = null
)
    {
       
if
(!CheckIfNameExists(name))
            _customProperties.Add(
new CustomPropertyInfo
<V>(name, get, set, attributes, properties));
    }

   
private static bool CheckIfNameExists(string
name)
    {
       
if (_customProperties.Select(p => p.Name).Contains(name) || typeof
(T).GetProperties().Select(p => p.Name).Contains(name))
           
throw new Exception("The property with this name already exists: "
+ name);
       
return false
;
    }

   
private bool ValidateValueType(object value, Type
type)
    {
       
if (value == null
)
        {
           
if
(!type.IsValueType)
               
return true
;
           
return (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable
<>));
        }
       
return
type.IsAssignableFrom(value.GetType());
    }

   
public override object GetPropertyValue(string
propertyName)
    {
       
object
customPropertyValue;
       
if (_customPropertyValues.TryGetValue(propertyName, out
customPropertyValue))
           
return customPropertyValue ?? _customProperties.First(p => p.Name == propertyName).GetDefaultValue(this
);
       
throw new Exception("There is no property "
+ propertyName);
    }

   
public override void SetPropertyValue(string propertyName, object
value)
    {
       
CustomPropertyInfo
propertyInfo = _customProperties.FirstOrDefault(prop => prop.Name == propertyName);
       
object
customPropertyValue;
       
if (!_customPropertyValues.TryGetValue(propertyName, out
customPropertyValue))
           
throw new Exception("There is no property "
+ propertyName);
       
if
(ValidateValueType(value, propertyInfo.PropertyType))
        {
           
if
(customPropertyValue != value)
            {
                _customPropertyValues[propertyName] = value;
                OnPropertyChanged(propertyName);
            }
        }
       
else throw new Exception("Value is of the wrong type or null for a non-nullable type."
);
    }

   
public PropertyInfo
[] GetProperties()
    {
       
return this
.GetCustomType().GetProperties();
    }

   
public Type
GetCustomType()
    {
       
return _customtype ?? (_customtype = new CustomType(typeof
(T)));
    }

   
protected virtual void OnPropertyChanged(string
propertyName)
    {
       
if (PropertyChanged != null
)
        {
            PropertyChanged(
this, new PropertyChangedEventArgs
(propertyName));
           
foreach (var dependantCustomPropertyInfo in _customProperties.OfType<IDependantCustomPropertyInfo
>().Where(dcpi => dcpi.Properties.Contains(propertyName)))
                PropertyChanged(
this, new PropertyChangedEventArgs
(dependantCustomPropertyInfo.Name));
        }
    }
   
public event PropertyChangedEventHandler
PropertyChanged;

   
private class CustomType : Type
    {
       
Type
_baseType;
       
public CustomType(Type
delegatingType)
        {
            _baseType = delegatingType;
        }
       
public override Assembly
Assembly
        {
           
get { return
_baseType.Assembly; }
        }

       
public override string
AssemblyQualifiedName
        {
           
get { return
_baseType.AssemblyQualifiedName; }
        }

       
public override Type
BaseType
        {
           
get { return
_baseType.BaseType; }
        }

       
public override string
FullName
        {
           
get { return
_baseType.FullName; }
        }

       
public override Guid
GUID
        {
           
get { return
_baseType.GUID; }
        }

       
protected override TypeAttributes
GetAttributeFlagsImpl()
        {
           
throw new NotImplementedException
();
        }

       
protected override ConstructorInfo GetConstructorImpl(BindingFlags bindingAttr, Binder binder, CallingConventions callConvention, Type[] types, ParameterModifier
[] modifiers)
        {
           
throw new NotImplementedException
();
        }

       
public override ConstructorInfo[] GetConstructors(BindingFlags
bindingAttr)
        {
           
return
_baseType.GetConstructors(bindingAttr);
        }

       
public override Type
GetElementType()
        {
           
return
_baseType.GetElementType();
        }

       
public override EventInfo GetEvent(string name, BindingFlags
bindingAttr)
        {
           
return
_baseType.GetEvent(name, bindingAttr);
        }

       
public override EventInfo[] GetEvents(BindingFlags
bindingAttr)
        {
           
return
_baseType.GetEvents(bindingAttr);
        }

       
public override FieldInfo GetField(string name, BindingFlags
bindingAttr)
        {
           
return
_baseType.GetField(name, bindingAttr);
        }

       
public override FieldInfo[] GetFields(BindingFlags
bindingAttr)
        {
           
return
_baseType.GetFields(bindingAttr);
        }

       
public override Type GetInterface(string name, bool
ignoreCase)
        {
           
return
_baseType.GetInterface(name, ignoreCase);
        }

       
public override Type
[] GetInterfaces()
        {
           
return
_baseType.GetInterfaces();
        }

       
public override MemberInfo[] GetMembers(BindingFlags
bindingAttr)
        {
           
return
_baseType.GetMembers(bindingAttr);
        }

       
protected override MethodInfo GetMethodImpl(string name, BindingFlags bindingAttr, Binder binder, CallingConventions callConvention, Type[] types, ParameterModifier
[] modifiers)
        {
           
throw new NotImplementedException
();
        }

       
public override MethodInfo[] GetMethods(BindingFlags
bindingAttr)
        {
           
return
_baseType.GetMethods(bindingAttr);
        }

       
public override Type GetNestedType(string name, BindingFlags
bindingAttr)
        {
           
return
_baseType.GetNestedType(name, bindingAttr);
        }

       
public override Type[] GetNestedTypes(BindingFlags
bindingAttr)
        {
           
return
_baseType.GetNestedTypes(bindingAttr);
        }

       
public override PropertyInfo[] GetProperties(BindingFlags
bindingAttr)
        {
           
PropertyInfo
[] clrProperties = _baseType.GetProperties(bindingAttr);
           
if (clrProperties != null
)
               
return
clrProperties.Concat(_customProperties).ToArray();
           
return
_customProperties.ToArray();
        }

       
protected override PropertyInfo GetPropertyImpl(string name, BindingFlags bindingAttr, Binder binder, Type returnType, Type[] types, ParameterModifier
[] modifiers)
        {
           
return
GetProperties(bindingAttr).FirstOrDefault(prop => prop.Name == name) ?? _customProperties.FirstOrDefault(prop => prop.Name == name);
        }

       
protected override bool
HasElementTypeImpl()
        {
           
throw new NotImplementedException
();
        }

       
public override object InvokeMember(string name, BindingFlags invokeAttr, Binder binder, object target, object[] args, ParameterModifier[] modifiers, System.Globalization.CultureInfo culture, string
[] namedParameters)
        {
           
return
_baseType.InvokeMember(name, invokeAttr, binder, target, args, modifiers, culture, namedParameters);
        }

       
protected override bool
IsArrayImpl()
        {
           
throw new NotImplementedException
();
        }

       
protected override bool
IsByRefImpl()
        {
           
throw new NotImplementedException
();
        }

       
protected override bool
IsCOMObjectImpl()
        {
           
throw new NotImplementedException
();
        }

       
protected override bool
IsPointerImpl()
        {
           
throw new NotImplementedException
();
        }

       
protected override bool
IsPrimitiveImpl()
        {
           
return
_baseType.IsPrimitive;
        }

       
public override Module
Module
        {
           
get { return
_baseType.Module; }
        }

       
public override string
Namespace
        {
           
get { return
_baseType.Namespace; }
        }

       
public override Type
UnderlyingSystemType
        {
           
get { return
_baseType.UnderlyingSystemType; }
        }

       
public override object[] GetCustomAttributes(Type attributeType, bool
inherit)
        {
           
return
_baseType.GetCustomAttributes(attributeType, inherit);
        }

       
public override object[] GetCustomAttributes(bool
inherit)
        {
           
return
_baseType.GetCustomAttributes(inherit);
        }

       
public override bool IsDefined(Type attributeType, bool
inherit)
        {
           
return
_baseType.IsDefined(attributeType, inherit);
        }

       
public override string
Name
        {
           
get { return
_baseType.Name; }
        }
    }

   
private class CustomPropertyInfo : PropertyInfo
    {
       
private string
_name;
       
private Type
_type;
       
private object
_defaultValue;
       
private List<Attribute
> _attributes;

       
public CustomPropertyInfo(string name, Type type, object defaultValue = null, List<Attribute> attributes = null
)
            :
this
(name, type, attributes)
        {
            _defaultValue = defaultValue;
        }

       
protected CustomPropertyInfo(string name, Type type, List<Attribute> attributes = null
)
        {
            _name = name;
            _type = type;
            _attributes = attributes;
        }

       
public virtual object GetDefaultValue(DynamicBaseType
entity)
        {
           
return
_defaultValue;
        }

       
public override PropertyAttributes
Attributes
        {
           
get { throw new NotImplementedException
(); }
        }

       
public override bool
CanRead
        {
           
get { return true
; }
        }

       
public override bool
CanWrite
        {
           
get { return true
; }
        }

       
public override MethodInfo[] GetAccessors(bool
nonPublic)
        {
           
throw new NotImplementedException
();
        }

       
public override MethodInfo GetGetMethod(bool
nonPublic)
        {
           
throw new NotImplementedException
();
        }

       
public override ParameterInfo
[] GetIndexParameters()
        {
           
throw new NotImplementedException
();
        }

       
public override MethodInfo GetSetMethod(bool
nonPublic)
        {
           
throw new NotImplementedException
();
        }

       
public override object GetValue(object obj, BindingFlags invokeAttr, Binder binder, object[] index, System.Globalization.CultureInfo
culture)
        {
           
return ((DynamicBaseType
)obj).GetPropertyValue(_name);
        }

       
public override void SetValue(object obj, object value, BindingFlags invokeAttr, Binder binder, object[] index, System.Globalization.CultureInfo
culture)
        {
            ((
DynamicBaseType
)obj).SetPropertyValue(_name, value);
        }

       
public override Type
PropertyType
        {
           
get { return
_type; }
        }

       
public override Type
DeclaringType
        {
           
get { throw new NotImplementedException
(); }
        }

       
public override object[] GetCustomAttributes(Type attributeType, bool
inherit)
        {
           
return _attributes == null ? new object
[0] : _attributes.Where(a => a.GetType() == attributeType).ToArray();
        }

       
public override object[] GetCustomAttributes(bool
inherit)
        {
           
return _attributes == null ? new object
[0] : _attributes.ToArray();
        }

       
public override bool IsDefined(Type attributeType, bool
inherit)
        {
           
throw new NotImplementedException
();
        }

       
public override string
Name
        {
           
get { return
_name; }
        }

       
public override Type
ReflectedType
        {
           
get { throw new NotImplementedException
(); }
        }
    }

   
private interface IDependantCustomPropertyInfo
    {
       
string Name { get
; }
       
string[] Properties { get
; }
    }

   
private class CustomPropertyInfo<V> : CustomPropertyInfo, IDependantCustomPropertyInfo
    {
       
private Func
<T, V> _get;
       
private Action
<T, V> _set;
       
private string
[] _properties;

       
public CustomPropertyInfo(string name, Func<T, V> get, Action<T, V> set = null, List<Attribute> attributes = null, string[] properties = null
)
            :
base(name, typeof
(V), attributes)
        {
            _get = get;
            _set = set;
            _properties = properties;
        }

       
public string
[] Properties
        {
           
get { return
_properties; }
        }

       
public override object GetDefaultValue(DynamicBaseType
entity)
        {
           
return
_get((T)entity);
        }

       
public override bool
CanWrite
        {
           
get { return _set != null
; }
        }

       
public override object GetValue(object obj, BindingFlags invokeAttr, Binder binder, object[] index, System.Globalization.CultureInfo
culture)
        {
           
return
_get((T)obj);
        }

       
public override void SetValue(object obj, object value, BindingFlags invokeAttr, Binder binder, object[] index, System.Globalization.CultureInfo
culture)
        {
           
if (_set == null
)
               
throw new InvalidOperationException();
            _set((T)obj, (V)value);
        }
    }
}

Que je peux utiliser très facilement.

Avec ma classe Customer par exemple :

public class Customer : DynamicBaseType<Customer>
{
   
private String
firstName;
   
public String
FirstName
    {
       
get
{ return firstName; }
       
set
        {
            firstName =
value
;
            OnPropertyChanged(
"FirstName"
);
        }
    }

   
private String
lastName;
   
public String
LastName
    {
       
get
{ return lastName; }
       
set
        {
            lastName =
value
;
            OnPropertyChanged(
"LastName");
        }
    }
}

Dans le ViewModel, je peux désormais utiliser le code suivant :

Customer.AddProperty("Age", typeof(int));
Customer.AddProperty("Married", typeof(bool));
Customer.AddProperty("FullName", c => string.Format("{0} {1}", c.LastName, c.FirstName), properties:new string[] { "LastName", "FirstName" });
 
customers[0].SetPropertyValue("Age", 40);
customers[0].SetPropertyValue("Married", true);
 
customers[1].SetPropertyValue("Age", 45);
customers[1].SetPropertyValue("Married", true);

Ce qui est nouveau dans ma solution par rapport au code de départ c’est que FullName est calculé à partir de d’autres propriétés. Si on change la propriété LastName ou FirstName d’un Customer, celui-ci lèvera également l’évènement PropertyChanged avec FullName.

Posté le lundi 12 septembre 2011 23:58 par Matthieu MEZIL | 0 commentaire(s)

Classé sous :

EF et récursivité

A travers ce post, je vais vous présenter un cas concret de mauvaise utilisation d’EF observée récemment chez un client.

Mon modèle est le suivant :

image

Nous voulons simplement récupérer un arbre avec une contrainte : si le type du noeud est "G" et qu’il n’a pas d’enfant, il ne doit pas apparaître dans l’arborescence.

Comment faire ceci.

Voici le code que j’ai pu observer :

public static List<Node> GetTree()
{
using (var context = new TreeEntities())
{
List<Node> nodes = context.Nodes.Where(n => n.Parent == null).ToList();
foreach (var n in nodes)
LoadChildren(context, n);
foreach (var n in nodes.Where(n => n.Type == "G" && n.Children.Count == 0).ToList())
nodes.Remove(n);
return nodes;
} } public static void LoadChildren(TreeEntities context, Node parentNode) {
List<Node> nodes = context.Nodes.Where(n => n.ParentId == parentNode.Id).ToList();
     foreach (var n in nodes)
         LoadChildren(context, n);
     foreach (var n in nodes.Where(n => n.Type == "G" && n.Children.Count == 0).ToList())
         context.Nodes.Detach(n); }

J’ai effectué un test avec le jeu d’entités suivant :

using (var context = new TreeEntities())
{
     for (int i0 = 0; i0 < 10; i0++)
     {
         Node n0 = new Node { Name = i0.ToString(), Type = i0 == 9 ? null : "G" };
         context.Nodes.AddObject(n0);
         if (i0 > 7)
             break;
         for (int i1 = 0; i1 < 10; i1++)
         {
             Node n1 = new Node { Name = n0.Name + i1.ToString(), Type = i1 == 9 ? null : "G", Parent = n0 };
             context.Nodes.AddObject(n1);
             if (i1 > 7)
                 break;
             for (int i2 = 0; i2 < 10; i2++)
             {
                 Node n2 = new Node { Name = n1.Name + i2.ToString(), Type = i2 == 9 ? null : "G", Parent = n1 };
                 context.Nodes.AddObject(n2);
                 if (i2 > 7)
                     break;
                 for (int i3 = 0; i3 < 10; i3++)
                 {
                     Node n3 = new Node { Name = n2.Name + i3.ToString(), Type = i3 == 9 ? null : "G", Parent = n2 };
                     context.Nodes.AddObject(n3);
                     if (i3 > 7)
                         break;
                     for (int i4 = 0; i4 < 10; i4++)
                         context.Nodes.AddObject(new Node { Name = n3.Name + i4.ToString(), Parent = n3 });
                 }
             }
         }
     }
     context.SaveChanges(); }

Pour cette première version, ce code s’exécute en 5 minutes 41 secondes et génère 46 226 requêtes en base !

 

Il n’y a pas besoin d’être DBA pour savoir que si le nombre de requêtes dépend du nombre de datarows, le code ne supporte pas la charge.

Comment améliorer les choses ?

 

Un des gros avantages d’EF est le fait qu’il fait tout seul les relations. C’est d’ailleurs pour cela que ce code fonctionne. A aucun moment on a affecté les enfants / parents d’un des noeuds. EF l’a fait pour nous.

 

On pourrait être tenter par le code suivant :

public static List<Node> GetTree()
{
using (var context = new TreeEntities())
{


return context.Nodes.Where(n => n.Type != "G" || n.Children.Any()).AsEnumerable().Where(n => n.ParentId == null).ToList();
} }

Petite remarque avant de continuer : attention à bien utiliser ParentId != null et pas Parent != null. En effet, au moment où on énumère sur les entités, rien ne nous indique que le parent a bien été chargé. Si ce n’est pas le cas, ParentId sera différent de null alors que Parent sera égal à null.

D’autre part, ce code ne fonctionne pas comme il devrait. En effet, si j’ai un noeud de type G avec un enfant de type G sans enfant, le premier noeud doit être supprimé puisque son seul enfant doit être supprimé. Or avec mon système à un seul niveau, ce ne sera pas le cas.

 

SQL n’est cependant pas réputé pour la récursivité.

Je vais donc tenter deux approches :

  • une première dans laquelle je vais faire cette récursivité dans mon code. A noter que cela implique de charger trop d’entités (comme c’est le cas avec la première version).
  • une deuxième dans laquelle c’est SQL Server qui va se charger de la récursivité

 

Ma première solution est la suivante :

public static IEnumerable<T> GetEntitiesInCache<T>(this ObjectContext context, EntityState entityState = EntityState.Unchanged | EntityState.Modified | EntityState.Added)
{
    return context.ObjectStateManager.GetObjectStateEntries(entityState).Select(ose => ose.Entity).OfType<T>(); } public static List<Node> GetTree(TreeEntities context) {
using (var context = new TreeEntities())
{
   foreach (var n in context.Nodes);

bool continueLoop;
do
{
continueLoop = false;
foreach (var n in context.GetEntitiesInCache<Node>().Where(n => n.Type == "G" && n.Children.Count == 0).ToList())
{
continueLoop = true;
context.Nodes.Detach(n);
}
} while (continueLoop);

return context.GetEntitiesInCache<Node>().Where(n => n.ParentId == null).ToList();
} }

Dans ce cas, je suis passé de plus de 5 minutes 41 secondes à 1 seconde avec une seule requête exécutée en base.

A noter que je travaille avec une base locale. Dans le cas contraire l’écart de temps aurait été encore plus important.

Remarquez également le

       foreach (var n in context.Nodes);

qui permet de charger l’ensemble des noeuds.

 

Pour la récursivité avec SQL server, cela fera l’objet d’un post futur.

Posté le jeudi 28 juillet 2011 21:02 par Matthieu MEZIL | 3 commentaire(s)

Classé sous : , ,

EF nouvelle CTP

Une nouvelle CTP d’EF et de WCF Data Services est disponible.

Contrairement aux CTPs précédentes qui se concentraient essentiellement sur l’ajout d’une nouvelle approche, Code-First, celle-ci apporte des améliorations sur le moteur même d’Entity Framework.

Le designer a également eu droit à son lot de nouveautés / améliorations :

  • le support des enums, types spatial et Table Valued Functions
  • la possibilité d’avoir plusieurs diagrammes du même edmx avec les petites options qui vont bien pour intégrer les entités liées par exemple
  • la possibilité de choisir la couleur de chaque entité
  • Le report automatique du StoreGeneratedPattern du CSDL dans le SSDL
  • La possibilité d’importer les procédures stockées dans le CSDL directement lors de l’import depuis la base
  • L’amélioration de l’association visuelle entre association et navigation properties
  • La possibilité d’externaliser dans un fichier séparé les infos relatives aux diagrammes, ce qui a le mérite de simplifier considérablement les problématiques de merge d’edmx avec un contrôleur de sources.

Mes tests m’ont amené à identifier un bug avec les Table Valued Functions : il n’est pas possible, dans la version actuelle, de placer le résultat d’une Table Valued Functions dans un let.

Plus d’info sur Announcing the Microsoft Entity Framework June 2011 CTP.

Posté le vendredi 1 juillet 2011 02:07 par Matthieu MEZIL | 4 commentaire(s)

L2E : impact sur le SQL

Comme je l’écrivais hier,

“Derrière la magie d’Entity Framework se cache la réalité du SQL. Je persiste et je signe, si vous ne connaissez pas le SQL, il sera très compliqué d’écrire des requêtes L2E optimales.

J’ai trouvé une bonne illustrations avec les fonctions de grouping.”

Hier, je vous ai montré comment exécuter un Count et un Max en une seule requête LINQ.

Maintenant imaginons que je veuille toujours récupérer le nombre de commandes et la date de la dernière commande mais par client.

Comment récupérer cela?

Si vous êtes sûr que chaque client à au moins une commande ou que vous voulez ignorer les clients sans commande, la meilleure façon d’écrire cette requête est probablement celle-ci:

from o in context.Orders
group o by o.Customer into g
select new { Customer = g.Key, OrdersCount = g.Count(), LastOrderDate = g.Max(o => o.OrderDate) };

Dans ce cas, le SQL est très proche de celui de la requête L2E :

SELECT

[GroupBy1].[K1] AS [CustomerId],

[GroupBy1].[K2] AS [LastName],

[GroupBy1].[K3] AS [FirstName],

[GroupBy1].[K4] AS [BirthDay],

[GroupBy1].[K5] AS [AddressLine],

[GroupBy1].[K6] AS [City],

[GroupBy1].[K7] AS [PostalCode],

[GroupBy1].[K8] AS [Region],

[GroupBy1].[K9] AS [Country],

[GroupBy1].[A1] AS [C1],

[GroupBy1].[A2] AS [C2]

FROM ( SELECT

[Extent2].[CustomerId] AS [K1],

[Extent2].[LastName] AS [K2],

[Extent2].[FirstName] AS [K3],

[Extent2].[BirthDay] AS [K4],

[Extent2].[AddressLine] AS [K5],

[Extent2].[City] AS [K6],

[Extent2].[PostalCode] AS [K7],

[Extent2].[Region] AS [K8],

[Extent2].[Country] AS [K9],

COUNT(1) AS [A1],

MAX([Extent1].[OrderDate]) AS [A2]

FROM [dbo].[Orders] AS [Extent1]

INNER JOIN [dbo].[Customers] AS [Extent2] ON [Extent1].[CustomerId] = [Extent2].[CustomerId]

GROUP BY [Extent2].[CustomerId], [Extent2].[LastName], [Extent2].[FirstName], [Extent2].[BirthDay], [Extent2].[AddressLine], [Extent2].[City], [Extent2].[PostalCode], [Extent2].[Region], [Extent2].[Country]

) AS [GroupBy1]

Avec un plan d’exécution assez simple :

image_thumb[7]

Si vous pouvez avoir des clients sans commandes, la requêtes devient plus compliquée.

Une idée assez simple serait d’utiliser la requête précédente union les clients sans commandes :

(from o in context.Orders
 group o by o.Customer into g
 select new { Customer = g.Key, OrdersCount = g.Count(), LastOrderDate = (DateTime?)g.Max(o => o.OrderDate) }).Union(
from c in context.Customers
where !c.Orders.Any()
select new { Customer = c, OrdersCount = 0, LastOrderDate = (DateTime?)null });

Dans ce cas, la requête SQL n’est pas performante :

SELECT

[Distinct1].[C1] AS [C1],

[Distinct1].[C2] AS [C2],

[Distinct1].[C3] AS [C3],

[Distinct1].[C4] AS [C4],

[Distinct1].[C5] AS [C5],

[Distinct1].[C6] AS [C6],

[Distinct1].[C7] AS [C7],

[Distinct1].[C8] AS [C8],

[Distinct1].[C9] AS [C9],

[Distinct1].[C10] AS [C10],

[Distinct1].[C11] AS [C11],

[Distinct1].[C12] AS [C12]

FROM ( SELECT DISTINCT

[UnionAll1].[C1] AS [C1],

[UnionAll1].[CustomerId] AS [C2],

[UnionAll1].[LastName] AS [C3],

[UnionAll1].[FirstName] AS [C4],

[UnionAll1].[BirthDay] AS [C5],

[UnionAll1].[AddressLine] AS [C6],

[UnionAll1].[City] AS [C7],

[UnionAll1].[PostalCode] AS [C8],

[UnionAll1].[Region] AS [C9],

[UnionAll1].[Country] AS [C10],

[UnionAll1].[C2] AS [C11],

[UnionAll1].[C3] AS [C12]

FROM (SELECT

1 AS [C1],

[GroupBy1].[K1] AS [CustomerId],

[GroupBy1].[K2] AS [LastName],

[GroupBy1].[K3] AS [FirstName],

[GroupBy1].[K4] AS [BirthDay],

[GroupBy1].[K5] AS [AddressLine],

[GroupBy1].[K6] AS [City],

[GroupBy1].[K7] AS [PostalCode],

[GroupBy1].[K8] AS [Region],

[GroupBy1].[K9] AS [Country],

[GroupBy1].[A1] AS [C2],

CAST( [GroupBy1].[A2] AS datetime2) AS [C3]

FROM ( SELECT

[Extent2].[CustomerId] AS [K1],

[Extent2].[LastName] AS [K2],

[Extent2].[FirstName] AS [K3],

[Extent2].[BirthDay] AS [K4],

[Extent2].[AddressLine] AS [K5],

[Extent2].[City] AS [K6],

[Extent2].[PostalCode] AS [K7],

[Extent2].[Region] AS [K8],

[Extent2].[Country] AS [K9],

COUNT(1) AS [A1],

MAX([Extent1].[OrderDate]) AS [A2]

FROM [dbo].[Orders] AS [Extent1]

INNER JOIN [dbo].[Customers] AS [Extent2] ON [Extent1].[CustomerId] = [Extent2].[CustomerId]

GROUP BY [Extent2].[CustomerId], [Extent2].[LastName], [Extent2].[FirstName], [Extent2].[BirthDay], [Extent2].[AddressLine], [Extent2].[City], [Extent2].[PostalCode], [Extent2].[Region], [Extent2].[Country]

) AS [GroupBy1]

UNION ALL

SELECT

1 AS [C1],

[Extent3].[CustomerId] AS [CustomerId],

[Extent3].[LastName] AS [LastName],

[Extent3].[FirstName] AS [FirstName],

[Extent3].[BirthDay] AS [BirthDay],

[Extent3].[AddressLine] AS [AddressLine],

[Extent3].[City] AS [City],

[Extent3].[PostalCode] AS [PostalCode],

[Extent3].[Region] AS [Region],

[Extent3].[Country] AS [Country],

0 AS [C2],

CAST(NULL AS datetime2) AS [C3]

FROM [dbo].[Customers] AS [Extent3]

WHERE NOT EXISTS (SELECT

1 AS [C1]

FROM [dbo].[Orders] AS [Extent4]

WHERE [Extent3].[CustomerId] = [Extent4].[CustomerId]

)) AS [UnionAll1]

) AS [Distinct1]

image_thumb[10]

Vous vous en doutez, j’espère, ce n’est probablement pas le meilleur moyen d’écrire cette requête…

Un autre serait d’utiliser ce que j’ai présenté hier : un group by sur une constant pour récupérer le count et le max sur c.Orders:

from c in context.Customers
let ordersInfo = (from o in c.Orders
                  group o by 1 into g
                  select new { OrdersCount = g.Count(), LastOrderDate = g.Max(o => o.OrderDate) }).FirstOrDefault()
select new { Customer = c, OrdersCount = (int?)ordersInfo.OrdersCount ?? 0, OrderDate = (DateTime?)ordersInfo.LastOrderDate };

Remarquez le cast en int? ou DateTime?. Contrairement à .NET, SQL retourne null pour OrdersCount et OrderDate si un client n’a pas de commande.

Si cette requête est bonne en L2O, elle ne l’est pas en L2E au vu du SQL généré !

SELECT

[Extent1].[CustomerId] AS [CustomerId],

[Extent1].[LastName] AS [LastName],

[Extent1].[FirstName] AS [FirstName],

[Extent1].[BirthDay] AS [BirthDay],

[Extent1].[AddressLine] AS [AddressLine],

[Extent1].[City] AS [City],

[Extent1].[PostalCode] AS [PostalCode],

[Extent1].[Region] AS [Region],

[Extent1].[Country] AS [Country],

CASE WHEN ([Limit1].[C1] IS NULL) THEN 0 ELSE [Limit1].[C1] END AS [C1],

CAST( [Limit1].[C2] AS datetime2) AS [C2]

FROM [dbo].[Customers] AS [Extent1]

OUTER APPLY (SELECT TOP (1)

[GroupBy1].[A1] AS [C1],

[GroupBy1].[A2] AS [C2]

FROM ( SELECT

[Project1].[K1] AS [K1],

COUNT([Project1].[A1]) AS [A1],

MAX([Project1].[A2]) AS [A2]

FROM ( SELECT

1 AS [K1],

1 AS [A1],

[Project1].[OrderDate] AS [A2]

FROM ( SELECT

[Extent2].[OrderDate] AS [OrderDate]

FROM [dbo].[Orders] AS [Extent2]

WHERE [Extent1].[CustomerId] = [Extent2].[CustomerId]

) AS [Project1]

) AS [Project1]

GROUP BY [K1]

) AS [GroupBy1] ) AS [Limit1]

image_thumb[12]

Comme vous pouvez le voir, cette requête est pire que la précédente. Donc contrairement à mon post d’hier, le group par une constant n’est pas une bonne idée ici.

On peut également essayer comme ceci :

from c in context.Customers
let orders = c.Orders
select new { Customer = c, OrdersCount = (int?)orders.Count ?? 0, LastOrderDate = (DateTime?)orders.Max(o => o.OrderDate) };

Mais le SQL généré n’est pas meilleur :

SELECT

[Project3].[CustomerId] AS [CustomerId],

[Project3].[LastName] AS [LastName],

[Project3].[FirstName] AS [FirstName],

[Project3].[BirthDay] AS [BirthDay],

[Project3].[AddressLine] AS [AddressLine],

[Project3].[City] AS [City],

[Project3].[PostalCode] AS [PostalCode],

[Project3].[Region] AS [Region],

[Project3].[Country] AS [Country],

CASE WHEN ([Project3].[C1] IS NULL) THEN 0 ELSE [Project3].[C2] END AS [C1],

CAST( [Project3].[C3] AS datetime2) AS [C2]

FROM ( SELECT

[Project2].[CustomerId] AS [CustomerId],

[Project2].[LastName] AS [LastName],

[Project2].[FirstName] AS [FirstName],

[Project2].[BirthDay] AS [BirthDay],

[Project2].[AddressLine] AS [AddressLine],

[Project2].[City] AS [City],

[Project2].[PostalCode] AS [PostalCode],

[Project2].[Region] AS [Region],

[Project2].[Country] AS [Country],

[Project2].[C1] AS [C1],

[Project2].[C2] AS [C2],

(SELECT

MAX([Extent4].[OrderDate]) AS [A1]

FROM [dbo].[Orders] AS [Extent4]

WHERE [Project2].[CustomerId] = [Extent4].[CustomerId]) AS [C3]

FROM ( SELECT

[Project1].[CustomerId] AS [CustomerId],

[Project1].[LastName] AS [LastName],

[Project1].[FirstName] AS [FirstName],

[Project1].[BirthDay] AS [BirthDay],

[Project1].[AddressLine] AS [AddressLine],

[Project1].[City] AS [City],

[Project1].[PostalCode] AS [PostalCode],

[Project1].[Region] AS [Region],

[Project1].[Country] AS [Country],

[Project1].[C1] AS [C1],

(SELECT

COUNT(1) AS [A1]

FROM [dbo].[Orders] AS [Extent3]

WHERE [Project1].[CustomerId] = [Extent3].[CustomerId]) AS [C2]

FROM ( SELECT

[Extent1].[CustomerId] AS [CustomerId],

[Extent1].[LastName] AS [LastName],

[Extent1].[FirstName] AS [FirstName],

[Extent1].[BirthDay] AS [BirthDay],

[Extent1].[AddressLine] AS [AddressLine],

[Extent1].[City] AS [City],

[Extent1].[PostalCode] AS [PostalCode],

[Extent1].[Region] AS [Region],

[Extent1].[Country] AS [Country],

(SELECT

COUNT(1) AS [A1]

FROM [dbo].[Orders] AS [Extent2]

WHERE [Extent1].[CustomerId] = [Extent2].[CustomerId]) AS [C1]

FROM [dbo].[Customers] AS [Extent1]

) AS [Project1]

) AS [Project2]

) AS [Project3]

Quant-au plan d’exécution, il pique les yeux :

image_thumb[16]

Ok j’arrête de jouer avec votre patience. Quelle est la meilleure requête ?

Je pense que c’est la suivante :

from c in context.Customers
from o in
    (from o2 in context.Orders
     where o2.CustomerId == c.CustomerId
     group o2 by o2.CustomerId into g
     select new { OrdersCount = g.Count(), LastOrderDate = (DateTime?)g.Max(o => o.OrderDate) }).DefaultIfEmpty()
select new { Customer = c, OrdersCount = (int?)o.OrdersCount ?? 0, o.LastOrderDate };

Avec celle-ci la requête SQL et le plan d’exécution reste simple et efficace :

SELECT

[Extent1].[CustomerId] AS [CustomerId],

[Extent1].[LastName] AS [LastName],

[Extent1].[FirstName] AS [FirstName],

[Extent1].[BirthDay] AS [BirthDay],

[Extent1].[AddressLine] AS [AddressLine],

[Extent1].[City] AS [City],

[Extent1].[PostalCode] AS [PostalCode],

[Extent1].[Region] AS [Region],

[Extent1].[Country] AS [Country],

CASE WHEN ([GroupBy1].[A1] IS NULL) THEN 0 ELSE [GroupBy1].[A1] END AS [C1],

CASE WHEN ([GroupBy1].[K1] IS NULL) THEN CAST(NULL AS datetime2) ELSE CAST( [GroupBy1].[A2] AS datetime2) END AS [C2]

FROM [dbo].[Customers] AS [Extent1]

OUTER APPLY (SELECT

[Extent2].[CustomerId] AS [K1],

COUNT(1) AS [A1],

MAX([Extent2].[OrderDate]) AS [A2]

FROM [dbo].[Orders] AS [Extent2]

WHERE [Extent2].[CustomerId] = [Extent1].[CustomerId]

GROUP BY [Extent2].[CustomerId] ) AS [GroupBy1]

image_thumb[18]

Notez le coût de la requête 11%, comme la première requête qui ne remontait pas les clients sans commande ! Ca rocks !

 

Pourquoi ais-je écris ce post ? Pourquoi ais-je montré le SQL généré ou les plans d’exécution liés ?

Je pense que c’est important. Contrairement au discours marketing, je ne pense pas qu’EF soit magique et facile à utiliser. Bien sûr, pour une base de démo avec 3 rows dans 3 tables, vous pouvez utiliser la requête que vous voulez mais pour une application de la vraie vie avec de vraies problématique de performance, ce n’est pas si simple… Attention, je continue de trouver EF fantastique et je continue d’adorer ça. Cependant, je pense qu’il est important de comprendre les impactes des requêtes L2E sur le SQL et ce n’est pas simple…

Avec ce post, je voulais montrer à quel point les plans d’exécution du SQL peuvent être différent pour un même résultat en fonction de la manière d’écrire la requête L2E.

Je réalise souvent des audits sur Entity Framework, particulièrement pour résoudre des problèmes de performance et je n’ai, à ce jour, jamais vu de vrais problèmes liés à EF. Les problèmes de performance avec EF que j’ai pu observer étaient tous liés à une méconnaissance d’EF et / ou une mauvaise conception de la base.

Posté le mercredi 15 juin 2011 00:48 par Matthieu MEZIL | 3 commentaire(s)

Comment effectuer plusieurs fonctions de grouping en une seule requête LINQ ?

Derrière la magie d’Entity Framework se cache la réalité du SQL. Je persiste et je signe, si vous ne connaissez pas le SQL, il sera très compliqué d’écrire des requêtes L2E optimales.

J’ai trouvé une bonne illustrations avec les fonctions de grouping.

Comment récupérer la dernière date de commande et le nombre de commandes ?

En SQL, on peut écrire la requête suivante :

SELECT COUNT(1), MAX(OrderDate)

FROM Orders

Mais comment faire la même chose en LINQ ?

Bien sûr, on pourrait utiliser deux requêtes mais il y a un point très important à prendre en compte avant de faire ça : la performance. En effet, le coût pour récupérer le count ou le max ou le count et le max est le même :

image_thumb[1]

Cela signifie que, même en ignorant le coût des deux connections à la base, avoir deux requêtes pour récupérer le nombre de commande et la date de la dernière commande est deux fois plus important que de récupérer ces informations en une seule requête !

Si l’on pense SQL, la fonction COUNT et MAX sont des fonctions de grouping.

On obtient le même plan d’exécution en utilisant un group by sur une constante :

SELECT COUNT(1), MAX(OrderDate)

FROM

(

SELECT OrderDate, 1 AS G

FROM Orders

) O

GROUP BY O.G

image_thumb[7]

Et ça on peut l’écrire en LINQ :

from o in context.Orders
group o by 1 into g
select new { Count = g.Count(), MaxOrderDate = g.Max(o => o.OrderDate) };

Posté le mardi 14 juin 2011 21:53 par Matthieu MEZIL | 0 commentaire(s)

Après-midi du développement sur la Task Parallel Library

J’aurai le plaisir de co-animer l’après-midi du développement sur la TPL jeudi prochain accompagné de David, Eric et Bruno.

Pour ma part, je me concentrerai sur des exemples de la vraie vie utilisant EF4.1 Code First et T4.

Posté le mercredi 8 juin 2011 21:07 par Matthieu MEZIL | 2 commentaire(s)

Classé sous : ,

Comment récupérer par code les WorkItems d’un ChangeSet dans un contexte de branche ?

Imaginons que l’on ait 3 branches : dev / integration / prod.

On va faire plusieurs check-ins dans la branche de dev qu’on va relier avec des WorkItems.

Ensuite, on va appliquer un certains nombre de ces changesets sur l’integration, ce qui va générer un nouveau changeset (sur la branche d’intégration).

On peut répéter plusieurs fois cette opération puis, on va appliquer tout ou partie de ces changesets de l’integration sur la branche de prod (ce qui va générer un nouveau changeset sur la branche de prod).

Maintenant, pour un changesetid donné, je veux récupérer tous les workitems qui lui sont liés directement ou indirectement (ie : liés aux changesets de la branche précédente qu’on a intégré).

Comment faire ceci ?

Après avoir un peu galéré à trouver la bonne méthode à utiliser dans les API TFS, j’ai écrit le code suivant :

public static IEnumerable<WorkItem> GetWorkItems(string serverURL, string collectionName, NetworkCredential networkCredential, int changeSetId, params string[] branches)
{
    TfsConfigurationServer tfsConfigurationServer = new TfsConfigurationServer(new Uri(serverURL), networkCredential);
    tfsConfigurationServer.Authenticate();
    return GetWorkItems(tfsConfigurationServer.GetTeamProjectCollection(new Guid(tfsConfigurationServer.CatalogNode.QueryChildren(new[] { CatalogResourceTypes.ProjectCollection },
false, CatalogQueryOptions.None).FirstOrDefault(cn => cn.Resource.DisplayName == collectionName).Resource.Properties["InstanceId"])).GetService<VersionControlServer>(),
changeSetId, branches).Distinct(); } private static IEnumerable<WorkItem> GetWorkItems(VersionControlServer versionControlServer, int changeSetId, IEnumerable<string> branches) {
    foreach (WorkItem wi in versionControlServer.GetChangeset(changeSetId).WorkItems)
        yield return wi;
    string[] subBranches = branches.Take(2).ToArray();
    if (subBranches.Length == 2)
        foreach (WorkItem wi in versionControlServer.QueryMerges(subBranches[1], VersionSpec.Latest, subBranches[0], VersionSpec.Latest, new ChangesetVersionSpec(changeSetId),
new ChangesetVersionSpec(changeSetId), RecursionType.Full).SelectMany(cm => GetWorkItems(versionControlServer, cm.SourceVersion, branches.Skip(1))))
            yield return wi; }

Enjoy Sourire

Posté le vendredi 27 mai 2011 12:12 par Matthieu MEZIL | 0 commentaire(s)

Classé sous : ,

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 :



Les 10 derniers blogs postés

- SharePoint : Bug sur la gestion des permissions et la synchronisation Office par Blog Technique de Romelard Fabrice le 07-10-2014, 11:35

- SharePoint 2007 : La gestion des permissions pour les Workflows par Blog Technique de Romelard Fabrice le 07-08-2014, 11:27

- TypeMock: mock everything! par Fathi Bellahcene le 07-07-2014, 17:06

- Coding is like Read par Aurélien GALTIER le 07-01-2014, 15:30

- Mes vidéos autour des nouveautés VS 2013 par Fathi Bellahcene le 06-30-2014, 20:52

- Recherche un passionné .NET par Tkfé le 06-16-2014, 12:22

- [CodePlex] Projet KISS Workflow Foundation lancé par Blog de Jérémy Jeanson le 06-08-2014, 22:25

- Etes-vous yOS compatible ? (3/3) : la feuille de route par Le blog de Patrick [MVP SharePoint] le 06-06-2014, 00:30

- [MSDN] Utiliser l'approche Contract First avec Workflow Foundation 4.5 par Blog de Jérémy Jeanson le 06-05-2014, 21:19

- [ #ESPC14 ] TH10 Moving mountains with SharePoint ! par Le blog de Patrick [MVP SharePoint] le 06-01-2014, 11:30