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 = dependents
;
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.AssociationEndMembers
.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 (array1
!= array2
)
{
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);
}
}
}
#>
Suite aux remarques de Thomas et John, ce post a été mis à jour.
Certains d’entre vous le savent, je suis en train de redévelopper mon EDM Designer.
Tout d’abord, la première version était mon premier projet WPF et si je devais le faire aujourd’hui, il est évident que je ne procèderais pas de la même façon (à l’époque, je n’avais pas utilisé MVVM par ex).
De plus, ma première version n’était pas autonome. Il fallait dans un premier temps récupérer l’edmx généré à partir d’une base de données avec VS et ensuite seulement on pouvait travailler avec mon designer
A cela, il faut rajouter que ma version se basait sur EF v1 (EF4 n’existant pas encore à l’époque).
J’ai donc décidé de repartir d’une feuille blanche et de proposer de nombreuses nouvelles fonctionnalités qui, je suis sûr, ne manqueront pas de vous ravir mais dont je garde le secret pour l’instant.
// A ce propos, n’hésitez pas à me soumettre vos idées.
Parce que je trouve certains points de mon dev intéressants mais également parce que je suis friand de critiques constructives, j’ai décidé de ne pas développer seul dans mon coin mais de faire une série de post afin de vous faire partager les choix que j’ai pu faire et recueillir vos avis sur ceux-ci.
L’idée que je vais développer dans ce post est de rendre mon EDM Designer le plus modulaire possible afin de pouvoir
- développer un module par version d’Entity Framework sans tout redévelopper à chaque nouvelle version
- permettre de choisir dans l’application la version d’Entity Framework que l’on souhaite utiliser
- donner la possibilité d’intégrer d’autre base de données que SQL Server
- permettre d’externaliser la gestion du pluralize / singularize
- etc.
Pour l’aspect modulaire, j’ai utilisé PRISM couplé avec Unity.
Plusieurs personnes m’ont dit que PRISM et Unity c’était des trucs pour la “branlette intellectuelle” d’architecte. Je vais donc tenter à travers cet article de leur prouver le contraire.
Unity est particulièrement utile pour mocker nos classes. Cependant, dans mon cas, je ne vais pas l’utiliser pour cela.
Avant de regarder plus en détail la solution que je propose, voici la vision du solution explorer :
Dans mon cas je vais utiliser PRISM pour définir les versions d’Entity Framework disponible dans mon application : en l’occurrence, EF v1 et EF 4.
Pour cela, je vais définir les modules que je veux utiliser dans mon App.config :
<
configuration
>
<
configSections
>
<
section
name
=
"modules"
type="Microsoft.Practices.Composite.Modularity.ModulesConfigurationSection, Microsoft.Practices.Composite"
/>
</
configSections
>
<
modules
>
<
module
assemblyFile
=
"EDM.v1.dll"
moduleType="EDM.v1.Module, EDM.v1"
moduleName="EDM.v1"
/>
<
module
assemblyFile
=
"EDM.v4.dll"
moduleType="EDM.v4.Module, EDM.v4"
moduleName="EDM.v4"
/>
</
modules
>
</
configuration
>
Dans le constructeur de la classe App, je vais faire appel à mon Bootstrapper :
public
App()
{
new Bootstrapper().Run();
}
Dans mon Bootstrapper, j’utilise le UnityBootstrapper fournit par PRISM afin d’instancier chacun des différent modules puis de créer mon Shell et l’afficher :
public
class Bootstrapper : UnityBootstrapper
{
protected override DependencyObject CreateShell()
{
ConfigurationModuleCatalog moduleCatalog = new ConfigurationModuleCatalog();
Container.RegisterInstance<IModuleCatalog>(moduleCatalog);
Container.RegisterType<IModuleManager, ModuleManager>();
IModuleManager moduleManager = Container.Resolve<IModuleManager>();
moduleManager.Run();
Container.RegisterType<IShellViewModel, ShellViewModel>();
Shell shell = Container.Resolve<Shell>();
shell.Show();
return shell;
}
}
J’utilise l’approche View First (ie : c’est la vue qui instancie le ViewModel). On pourrait croire que je fais du ViewModel first en voyant le code mais en réalité ce n’est pas le cas parce que j’utilise Unity.
public
partial class Shell : Window
{
public Shell(IShellViewModel shellViewModel)
{
InitializeComponent();
DataContext = shellViewModel;
}
}
Dans mon cas, IShellViewModel étant associé avec ShellViewModel dans mon UnityContainer, lors de l’instanciation de mon Shell, unity va m’instancier un ShellViewModel pour le passer en paramètre.
Mon ShellViewModel va me retourner la liste des modules disponibles afin que je puisse les afficher dans une ListBox.
<
ListBox
x
:
Name
=
"modulesLB"
ItemsSource="{Binding Modules}"
DisplayMemberPath="Name"
/>
public
interface IShellViewModel
{
IEnumerable<EDMModuleBase> Modules { get; }
}
Comment récupérer la liste des modules ?
Le code que j’utilise est le suivant :
public
class ShellViewModel : IShellViewModel
{
public ShellViewModel(Func<IEnumerable<IEDMModule>> modules)
{
Modules = modules();
}
public IEnumerable<IEDMModule> Modules { get; private set; }
}
Pour pouvoir l’utiliser il faut donc au préalable enregistrer les différents modules dans le container unity.
Je vais effectuer cette action dans le constructeur de mon module :
public
class Module : EDMModuleBase
{
public Module(IUnityContainer unityContainer)
: base(unityContainer)
{
}
public override string Name
{
get { return "EDM v1"; }
}
}
public
abstract class EDMModuleBase : IEDMModule
{
public EDMModuleBase(IUnityContainer unityContainer)
{
unityContainer.RegisterInstance<EDMModuleBase>(Name, this);
}
public abstract string Name { get; }
void IModule.Initialize()
{
}
}
public
interface IEDMModule : IModule
{
string Name { get; }
}
Ainsi mes deux modules sont bien ajoutés dans le UnityContainer lors de leur instanciation dans le Bootstrapper ce qui permet à mon ViewModel de les retrouver.
Maintenant, allons un peu plus loin.
La structure d’un EDM (avec une partie SSDL, une partie CSDL et partie MSL) est la même pour les versions 1 et 4 d’Entity Framework.
Aussi, je n’ai pas envie de redéfinir la classe EDM pour EF4. En revanche, le CSDL lui s’est enrichi avec EF4 (ajout des CSDL Functions). Du coup, je veux que ma classe EDM définie dans EDM.Base m’instancie un EDM.v4.CSDL quand j’utilise le Module v4 et un EDM.Base.CSDL quand j’utilise la v1.
Comment faire cela ?
Unity va bien nous aider.
En partant du UnityContainer principal, je vais définir un ChildContainer dans chacun des modules. Dans le ChildContainer du Module v4, je vais associer le type EDM.Base.CSDL avec le type EDM.v4.CSDL puis je vais rajouter une propriété EDM dans mon module qui sera créé par mon ChildContainer et la classe EDM va utiliser ce UnityContainer pour instancier le CSDL :
public
interface IEDMModule : IModule
{
string Name { get; }
EDM.Base.EDM EDM { get; }
}
public
abstract class EDMModuleBase : IModule
{
public EDMModuleBase(IUnityContainer unityContainer)
{
unityContainer.RegisterInstance<EDMModuleBase>(Name, this);
IUnityContainer childContainer = unityContainer.CreateChildContainer();
Init(childContainer);
EDM = childContainer.Resolve<EDM.Base.EDM>();
}
protected virtual void Init(IUnityContainer unityContainer)
{
}
public abstract string Name { get; }
public EDM.Base.EDM EDM { get; private set; }
void IModule.Initialize()
{
}
}
namespace
EDM.v4
{
public class Module : EDMModuleBase
{
public Module(IUnityContainer unityContainer)
: base(unityContainer)
{
}
protected override void Init(IUnityContainer unityContainer)
{
base.Init(unityContainer);
unityContainer.RegisterType<Base.CSDL, CSDL>();
}
public override string Name
{
get { return "EDM v4"; }
}
}
}
public
class EDM
{
public EDM(SSDL ssdl, CSDL csdl, MSL msl)
{
SSDL = ssdl;
CSDL = csdl;
MSL = msl;
}
public SSDL SSDL { get; private set; }
public CSDL CSDL { get; private set; }
public MSL MSL { get; private set; }
public virtual IEnumerable<IEDMElement> EDMElements
{
get
{
yield return SSDL;
yield return CSDL;
yield return MSL;
}
}
}
Vérifions avec un petit exemple :
<
Grid
>
<
Grid.ColumnDefinitions
>
<
ColumnDefinition
/>
<
ColumnDefinition
/>
</
Grid.ColumnDefinitions
>
<
ListBox
x
:
Name
=
"modulesLB"
ItemsSource="{Binding Modules}"
DisplayMemberPath="Name" />
<Grid Grid.Column="1">
<ListBox ItemsSource="{Binding SelectedItem.EDM.EDMElements, ElementName=modulesLB}" />
</Grid
>
</
Grid
>