<#@ template language="VB" debug="false" hostspecific="true"#> <#@ include file="EF.Utility.VB.ttinclude"#><#@ output extension=".vb"#><# ' Copyright (c) Microsoft Corporation. All rights reserved. Dim code As New CodeGenerationTools(Me) Dim ef As New MetadataTools(Me) Dim loader As New MetadataLoader(Me) Dim region As New CodeRegion(Me) Dim fileManager As EntityFrameworkTemplateFileManager = EntityFrameworkTemplateFileManager.Create(Me) Dim inputFile As String = "$edmxInputFile$" Dim ItemCollection As EdmItemCollection = loader.CreateEdmItemCollection(inputFile) Dim namespaceName As String = code.VsNamespaceSuggestion() Dim container As EntityContainer = ItemCollection.GetItems(Of EntityContainer)().FirstOrDefault() If container Is Nothing Then Return "' No EntityContainer exists in the model, so no code was generated" End If WriteHeader(fileManager) BeginNamespace(namespaceName, code) #> Partial <#=Accessibility.ForType(container)#> Class <#=code.Escape(container)#> Inherits ObjectContext Public Const SettingsConnectionString As String = "name=<#=container.Name#>" Public Const ContainerName As String = "<#=container.Name#>" <# region.Begin("Constructors") #> Public Sub New() MyBase.New(SettingsConnectionString, ContainerName) Initialize() End Sub Public Sub New(ByVal connectionString As String) MyBase.New(connectionString, ContainerName) Initialize() End Sub Public Sub New(ByVal connection As EntityConnection) MyBase.New(connection, ContainerName) Initialize() End Sub Private Sub Initialize() ' Creating proxies requires the use of the ProxyDataContractResolver and ' may allow lazy loading which can expand the loaded graph during serialization. ContextOptions.ProxyCreationEnabled = False AddHandler ObjectMaterialized, AddressOf HandleObjectMaterialized End Sub Private Sub HandleObjectMaterialized(ByVal sender As Object, ByVal e As ObjectMaterializedEventArgs) Dim entity As IObjectWithChangeTracker = TryCast(e.Entity, IObjectWithChangeTracker) If entity IsNot Nothing Then Dim changeTrackingEnabled As Boolean = entity.ChangeTracker.ChangeTrackingEnabled Try entity.MarkAsUnchanged() Finally entity.ChangeTracker.ChangeTrackingEnabled = changeTrackingEnabled End Try Me.StoreReferenceKeyValues(entity) End If End Sub <# region.End() region.Begin("ObjectSet Properties") For Each entitySet As EntitySet In container.BaseEntitySets.OfType(Of EntitySet)() #> <#=Accessibility.ForReadOnlyProperty(entitySet)#> ReadOnly Property <#=code.Escape(entitySet)#>() As ObjectSet(Of <#=code.Escape(entitySet.ElementType)#>) Get If <#=code.FieldName(entitySet) #> Is Nothing Then <#=code.FieldName(entitySet)#> = CreateObjectSet(Of <#=code.Escape(entitySet.ElementType)#>)("<#=entitySet.Name#>") End If Return <#=code.FieldName(entitySet)#> End Get End Property Private <#=code.FieldName(entitySet)#> As ObjectSet(Of <#=code.Escape(entitySet.ElementType)#>) <# Next region.End() region.Begin("Function Imports") For Each edmFunction As EdmFunction In container.FunctionImports Dim parameters As IEnumerable(Of FunctionImportParameter) = FunctionImportParameter.Create(edmFunction.Parameters, code, ef) Dim paramList As String = String.Join(", ", parameters.Select(Function(p) "ByVal " & p.FunctionParameterName & " As " & p.FunctionParameterType).ToArray()) If edmFunction.ReturnParameter Is Nothing Then Continue For End If Dim returnTypeElement As String = code.Escape(ef.GetElementType(edmFunction.ReturnParameter.TypeUsage)) #> <#=Accessibility.ForMethod(edmFunction)#> Function <#=code.Escape(edmFunction)#>(<#=paramList#>) As ObjectResult(Of <#=returnTypeElement#>) <# For Each parameter As FunctionImportParameter In parameters If Not parameter.NeedsLocalVariable Then Continue For End If #> Dim <#=parameter.LocalVariableName#> As ObjectParameter If <#=If(parameter.IsNullableOfT, parameter.FunctionParameterName & ".HasValue", parameter.FunctionParameterName & " IsNot Nothing")#> Then <#=parameter.LocalVariableName#> = New ObjectParameter("<#=parameter.EsqlParameterName#>", <#=parameter.FunctionParameterName#>) Else <#=parameter.LocalVariableName#> = New ObjectParameter("<#=parameter.EsqlParameterName#>", GetType(<#=parameter.RawClrTypeName#>)) End If <# Next #> Return MyBase.ExecuteFunction(Of <#=returnTypeElement#>)("<#=edmFunction.Name#>"<#=code.StringBefore(", ", String.Join(", ", parameters.Select(Function(p) p.ExecuteParameterName).ToArray()))#>) End Function <# Next region.End() #> End Class <# EndNamespace(namespaceName) fileManager.StartNewFile(Path.GetFileNameWithoutExtension(Host.TemplateFile) & ".Extensions.vb") BeginNamespace(namespaceName, code) WriteApplyChanges(code) EndNamespace(namespaceName) fileManager.Process() #> <#+ Private Sub WriteHeader(ByVal fileManager As EntityFrameworkTemplateFileManager, ByVal ParamArray extraUsings As String()) fileManager.StartHeader() #> '------------------------------------------------------------------------------ ' ' This code was generated from a template. ' ' Changes to this file may cause incorrect behavior and will be lost if ' the code is regenerated. ' '------------------------------------------------------------------------------ Imports System Imports System.Collections.Generic Imports System.ComponentModel Imports System.Data.Common Imports System.Data.EntityClient Imports System.Data.Metadata.Edm Imports System.Data.Objects.DataClasses Imports System.Data.Objects Imports System.Data Imports System.Diagnostics Imports System.Globalization Imports System.Linq Imports System.Runtime.CompilerServices <#=String.Join(String.Empty, extraUsings.Select(Function(u) "Imports " & u & Environment.NewLine).ToArray())#> <#+ fileManager.EndBlock() End Sub Private Sub BeginNamespace(ByVal namespaceName As String, ByVal code As CodeGenerationTools) Dim region As CodeRegion = New CodeRegion(Me) If Not String.IsNullOrEmpty(namespaceName) Then #> Namespace <#=code.EscapeNamespace(namespaceName)#> <#+ PushIndent(CodeRegion.GetIndent(1)) End If End Sub Private Sub EndNamespace(ByVal namespaceName As String) If Not String.IsNullOrEmpty(namespaceName) Then PopIndent() #> End Namespace <#+ End If End Sub Private Sub WriteApplyChanges(ByVal code As CodeGenerationTools) #> Public Module SelfTrackingEntitiesContextExtensions ''' ''' ApplyChanges takes the changes in a connected set of entities and applies them to an ObjectContext. ''' ''' Expected type of the ObjectSet ''' The ObjectSet referencing the ObjectContext to which changes will be applied. ''' The entity serving as the entry point of the object graph that contains changes. Public Sub ApplyChanges(Of TEntity As {Class, IObjectWithChangeTracker})(ByVal objectSet As ObjectSet(Of TEntity), ByVal entity As TEntity) If objectSet Is Nothing Then Throw New ArgumentNullException("objectSet") End If objectSet.Context.ApplyChanges(Of TEntity)(objectSet.EntitySet.EntityContainer.Name & "." & objectSet.EntitySet.Name, entity) End Sub ''' ''' ApplyChanges takes the changes in a connected set of entities and applies them to an ObjectContext. ''' ''' Expected type of the EntitySet ''' The ObjectContext to which changes will be applied. ''' The EntitySet name of the entity. ''' The entity serving as the entry point of the object graph that contains changes. Public Sub ApplyChanges(Of TEntity As IObjectWithChangeTracker)(ByVal context As ObjectContext, ByVal entitySetName As String, ByVal entity As TEntity) If context Is Nothing Then Throw New ArgumentNullException("context") End If If String.IsNullOrEmpty(entitySetName) Then Throw New ArgumentException("String parameter cannot be null or empty.", "entitySetName") End If If entity Is Nothing Then Throw New ArgumentNullException("entity") End If Dim lazyLoadingSetting As Boolean = context.ContextOptions.LazyLoadingEnabled Try context.ContextOptions.LazyLoadingEnabled = False Dim entityIndex As EntityIndex = AddHelper.AddAllEntities(context, entitySetName, entity) Dim allRelationships As New RelationshipSet(context, entityIndex.AllEntities) ' Handle Initial Entity State For Each changedEntity As IObjectWithChangeTracker In entityIndex.AllEntities.Where(Function(x) x.ChangeTracker.State = ObjectState.Deleted) HandleDeletedEntity(context, entityIndex, allRelationships, changedEntity) Next For Each changedEntity As IObjectWithChangeTracker In entityIndex.AllEntities.Where(Function(x) x.ChangeTracker.State <> ObjectState.Deleted) HandleEntity(context, entityIndex, allRelationships, changedEntity) Next 'Loop through each object state entry For Each changedEntity As IObjectWithChangeTracker In entityIndex.AllEntities Dim entry As ObjectStateEntry = context.ObjectStateManager.GetObjectStateEntry(changedEntity) Dim entityType As EntityType = context.MetadataWorkspace.GetCSpaceEntityType(changedEntity.GetType()) For Each navProp As NavigationProperty In entityType.NavigationProperties Dim relatedEnd As RelatedEnd = entry.GetRelatedEnd(navProp.Name) If Not DirectCast(relatedEnd.RelationshipSet.ElementType, AssociationType).IsForeignKey Then ApplyChangesToIndependentAssociation(context, DirectCast(changedEntity, IObjectWithChangeTracker), entry, navProp, relatedEnd, allRelationships) End If Next Next ' Change all the remaining relationships to the appropriate state For Each relationship As RelationshipWrapper In allRelationships context.ObjectStateManager.ChangeRelationshipState(relationship.End0, relationship.End1, relationship.AssociationSet.ElementType.FullName, relationship.AssociationEndMembers(1).Name, relationship.State) Next Finally context.ContextOptions.LazyLoadingEnabled = lazyLoadingSetting End Try End Sub Private Sub ApplyChangesToIndependentAssociation( ByVal context As ObjectContext, ByVal changedEntity As IObjectWithChangeTracker, ByVal entry As ObjectStateEntry, ByVal navProp As NavigationProperty, ByVal relatedEnd As IRelatedEnd, ByVal allRelationships As RelationshipSet) Dim changeTracker As ObjectChangeTracker = changedEntity.ChangeTracker If changeTracker.State = ObjectState.Added Then ' Relationships should remain added so remove them from the list of allRelationships For Each relatedEntity As Object In relatedEnd Dim addedRelationshipEntry As ObjectStateEntry = context.ObjectStateManager.ChangeRelationshipState(changedEntity, relatedEntity, navProp.Name, EntityState.Added) allRelationships.Remove(addedRelationshipEntry) Next Else If navProp.ToEndMember.RelationshipMultiplicity = RelationshipMultiplicity.Many Then 'Handle removal to FixupCollections Dim collectionPropertyChanges As ObjectList = Nothing If changeTracker.ObjectsRemovedFromCollectionProperties.TryGetValue(navProp.Name, collectionPropertyChanges) Then For Each removedEntityFromAssociation As Object In collectionPropertyChanges Dim deletedRelationshipEntry As ObjectStateEntry = context.ObjectStateManager.ChangeRelationshipState(changedEntity, removedEntityFromAssociation, navProp.Name, EntityState.Deleted) allRelationships.Remove(deletedRelationshipEntry) Next End If 'Handle addition to FixupCollection If changeTracker.ObjectsAddedToCollectionProperties.TryGetValue(navProp.Name, collectionPropertyChanges) Then For Each addedEntityFromAssociation As Object In collectionPropertyChanges Dim addedRelationshipEntry As ObjectStateEntry = context.ObjectStateManager.ChangeRelationshipState(changedEntity, addedEntityFromAssociation, navProp.Name, EntityState.Added) allRelationships.Remove(addedRelationshipEntry) Next End If Else ' Handle original relationship values Dim originalReferenceValue As Object = Nothing If changeTracker.OriginalValues.TryGetValue(navProp.Name, originalReferenceValue) Then If originalReferenceValue IsNot Nothing Then 'Capture the deletion of association Dim deletedRelationshipEntry As ObjectStateEntry = context.ObjectStateManager.ChangeRelationshipState(entry.Entity, originalReferenceValue, navProp.Name, EntityState.Deleted) allRelationships.Remove(deletedRelationshipEntry) End If 'Capture the Addition of association Dim currentReferenceValue As Object = Nothing For Each o As Object In relatedEnd currentReferenceValue = o Exit For Next If currentReferenceValue IsNot Nothing Then Dim addedRelationshipEntry As ObjectStateEntry = context.ObjectStateManager.ChangeRelationshipState(changedEntity, currentReferenceValue, navProp.Name, EntityState.Added) allRelationships.Remove(addedRelationshipEntry) ' if the current value of the reference is null, then the user must set the entity reference to null ' which is already being handled by the deletion of the relationship End If End If End If End If End Sub ' Extracts the relationship key information from the ExtendedProperties and OriginalValues records of each ObjectChangeTracker ' This is done by: ' 1. Creating any existing relationship specified in the ExtendedProperties ' 2. Determine if there was a previous relationship, and if there was create a deleted relationship between the entity and the previous entity or key value Private Sub HandleRelationshipKeys(ByVal context As ObjectContext, ByVal entityIndex As EntityIndex, ByVal allRelationships As RelationshipSet, ByVal entity As IObjectWithChangeTracker) Dim changeTracker As ObjectChangeTracker = entity.ChangeTracker If changeTracker.State = ObjectState.Unchanged OrElse changeTracker.State = ObjectState.Modified OrElse changeTracker.State = ObjectState.Deleted Then Dim entry As ObjectStateEntry = context.ObjectStateManager.GetObjectStateEntry(entity) Dim entityType As EntityType = context.MetadataWorkspace.GetCSpaceEntityType(entity.GetType()) Dim relationshipManager As RelationshipManager = context.ObjectStateManager.GetRelationshipManager(entity) For Each entityReference As EntityReference In EnumerateSaveReferences(relationshipManager) Dim associationSet As AssociationSet = DirectCast(entityReference.RelationshipSet, AssociationSet) Dim fromEnd As AssociationEndMember = associationSet.AssociationSetEnds(entityReference.SourceRoleName).CorrespondingAssociationEndMember Dim toEnd As AssociationEndMember = associationSet.AssociationSetEnds(entityReference.TargetRoleName).CorrespondingAssociationEndMember ' Find if there is a NavigationProperty for this candidate Dim navigationProperty As NavigationProperty = entityType.NavigationProperties.SingleOrDefault( _ Function(x) Equals(x.RelationshipType, associationSet.ElementType) AndAlso _ Equals(x.FromEndMember, fromEnd) AndAlso _ Equals(x.ToEndMember, toEnd)) ' Only handle relationship keys in one of these cases ' 1. There is no navigation property ' 2. The navigation property has a null current reference value null there are no removes or adds ' 3. The navigation property has a current reference value, but there is no remove Dim currentKey As EntityKey = GetSavedReferenceKey(entityIndex, entityReference, entity, navigationProperty, changeTracker.ExtendedProperties) ' Get any original value from the change tracking information Dim originalValue As Object = Nothing Dim originalKey As EntityKey = Nothing Dim hasOriginalValue As Boolean = False If changeTracker.OriginalValues IsNot Nothing Then ' Try to get the original value from the NavigationProperty first If navigationProperty IsNot Nothing Then hasOriginalValue = changeTracker.OriginalValues.TryGetValue(navigationProperty.Name, originalValue) End If ' Try to get the original value from the reference key second If Not hasOriginalValue OrElse originalValue Is Nothing Then originalKey = GetSavedReferenceKey(entityIndex, entityReference, entity, navigationProperty, changeTracker.OriginalValues) End If End If ' Create the current relationship If currentKey IsNot Nothing Then ' If the key is for a deleted entity, move that key to an originalValue and fixup the entities key values ' Otherwise create a new relationship Dim currentEntry As ObjectStateEntry = Nothing If context.ObjectStateManager.TryGetObjectStateEntry(currentKey, currentEntry) AndAlso currentEntry.Entity IsNot Nothing AndAlso currentEntry.State = EntityState.Deleted Then entityReference.EntityKey = Nothing MoveSavedReferenceKey(entityReference, entity, navigationProperty, changeTracker.ExtendedProperties, changeTracker.OriginalValues) originalKey = currentKey Else CreateRelationship(context, entityReference, entry.EntityKey, currentKey, If(originalKey Is Nothing, EntityState.Unchanged, EntityState.Added)) End If Else ' Find the current key ' Cannot get the EntityKey directly because this is null when it points to an Added entity currentKey = entityReference.GetCurrentEntityKey(context) End If ' Create the original relationship If originalKey IsNot Nothing Then ' If the key is for a deleted entity, remember to create a deleted relationship, ' otherwise use the entityReference to setup the deleted relationship Dim originalEntry As ObjectStateEntry = Nothing Dim deletedRelationshipEntry As ObjectStateEntry = Nothing If context.ObjectStateManager.TryGetObjectStateEntry(originalKey, originalEntry) AndAlso originalEntry.Entity IsNot Nothing AndAlso originalEntry.State = EntityState.Deleted Then allRelationships.Add(entityReference, entry.Entity, originalEntry.Entity, EntityState.Deleted) Else ' To create a deleted relationship to a key, first detach the existing relationship between entry and currentKey Dim currentRelationshipState As EntityState = DetachRelationship(context, entityReference, entry, currentKey) ' If the relationship is 1 to 0..1, detach the relationship from currentKey to its target (targetKey) Dim targetRelationshipState As EntityState = EntityState.Detached Dim targetReference As EntityReference = Nothing Dim targetKey As EntityKey = Nothing If originalEntry IsNot Nothing AndAlso originalEntry.Entity IsNot Nothing AndAlso originalEntry.RelationshipManager IsNot Nothing AndAlso associationSet.AssociationSetEnds(fromEnd.Name).CorrespondingAssociationEndMember.RelationshipMultiplicity <> RelationshipMultiplicity.Many Then targetReference = TryCast(originalEntry.RelationshipManager.GetRelatedEnd(entityReference.RelationshipName, entityReference.SourceRoleName), EntityReference) targetKey = targetReference.GetCurrentEntityKey(context) If targetKey IsNot Nothing Then targetRelationshipState = DetachRelationship(context, targetReference, originalEntry, targetKey) End If End If ' Create the deleted relationship between entry and originalKey deletedRelationshipEntry = CreateRelationship(context, entityReference, entry.EntityKey, originalKey, EntityState.Deleted) ' Set the previous relationship between entry and currentKey back CreateRelationship(context, entityReference, entry.EntityKey, currentKey, currentRelationshipState) ' Set the previous relationship between originalEntry and targetKey back If targetKey IsNot Nothing Then CreateRelationship(context, targetReference, originalEntry.EntityKey, targetKey, targetRelationshipState) End If End If If deletedRelationshipEntry IsNot Nothing Then ' Remove the deleted relationship from those that need to be processed later in ApplyChanges allRelationships.Remove(deletedRelationshipEntry) End If ElseIf currentKey Is Nothing AndAlso originalValue IsNot Nothing AndAlso entityReference.IsDependentEndOfReferentialConstraint() Then ' the graph won't have this hooked up because there is no current value, but there is an original value, ' so the relationship processing code will want to delete a relationship. ' we can add this one so it has a relationship to change to deleted. context.ObjectStateManager.ChangeRelationshipState(entry.Entity, originalValue, entityReference.RelationshipName, entityReference.TargetRoleName, EntityState.Added) End If Next End If End Sub Private Function CreateRelationship(ByVal context As ObjectContext, ByVal entityReference As EntityReference, ByVal fromKey As EntityKey, ByVal toKey As EntityKey, ByVal state As EntityState) As ObjectStateEntry If state <> EntityState.Detached Then Dim associationSet As AssociationSet = DirectCast(entityReference.RelationshipSet, AssociationSet) Dim fromEnd As AssociationEndMember = associationSet.AssociationSetEnds(entityReference.SourceRoleName).CorrespondingAssociationEndMember Dim toEnd As AssociationEndMember = associationSet.AssociationSetEnds(entityReference.TargetRoleName).CorrespondingAssociationEndMember ' set the relationship to the original relationship in the unchanged state Debug.Assert(toKey IsNot Nothing, "why/how would we do a delete with a null originalKey?") If toKey.IsTemporary Then ' Clear any existing relationship entityReference.EntityKey = Nothing ' If the target entity is Added, use Add on RelatedEnd Dim targetEntry As ObjectStateEntry = Nothing context.ObjectStateManager.TryGetObjectStateEntry(toKey, targetEntry) Debug.Assert(targetEntry IsNot Nothing, "Should have found the state entry") DirectCast(entityReference, IRelatedEnd).Add(targetEntry.Entity) Else entityReference.EntityKey = toKey End If Dim relationshipEntry As ObjectStateEntry = Nothing Dim found As Boolean = context.TryGetObjectStateEntry(fromKey, toKey, associationSet, fromEnd, toEnd, relationshipEntry) Debug.Assert(found, "Did not find the created relationship.") Select Case state Case EntityState.Added Case EntityState.Unchanged relationshipEntry.AcceptChanges() Case EntityState.Deleted relationshipEntry.AcceptChanges() entityReference.EntityKey = Nothing End Select Return relationshipEntry End If Return Nothing End Function Private Function DetachRelationship(ByVal context As ObjectContext, ByVal entityReference As EntityReference, ByVal fromEntry As ObjectStateEntry, ByVal toKey As EntityKey) As EntityState Dim currentRelationshipState As EntityState = EntityState.Detached If toKey IsNot Nothing Then Dim associationSet As AssociationSet = DirectCast(entityReference.RelationshipSet, AssociationSet) Dim fromEnd As AssociationEndMember = associationSet.AssociationSetEnds(entityReference.SourceRoleName).CorrespondingAssociationEndMember Dim toEnd As AssociationEndMember = associationSet.AssociationSetEnds(entityReference.TargetRoleName).CorrespondingAssociationEndMember Dim currentRelationshipEntry As ObjectStateEntry = Nothing If context.TryGetObjectStateEntry(fromEntry.EntityKey, toKey, associationSet, fromEnd, toEnd, currentRelationshipEntry) Then currentRelationshipState = currentRelationshipEntry.State entityReference.EntityKey = Nothing If currentRelationshipEntry.State = EntityState.Deleted Then currentRelationshipEntry.AcceptChanges() End If Debug.Assert(currentRelationshipEntry.State = EntityState.Detached, "relationship was not detached") End If End If Return currentRelationshipState End Function Private Function CreateReferenceKeyLookup(ByVal keyMemberName As String, ByVal reference As EntityReference, ByVal navigationProperty As NavigationProperty) As String ' use the more usable navigation property name to qualify the member ' if available If navigationProperty IsNot Nothing Then Return String.Format(CultureInfo.InvariantCulture, "{0}.{1}", navigationProperty.Name, keyMemberName) Else Return String.Format(CultureInfo.InvariantCulture, "Navigate({0}.{1}).{2}", reference.RelationshipSet.ElementType.FullName, reference.TargetRoleName, keyMemberName) End If End Function ' retrieves the key corresponding to the passed in EntityReference ' these keys can be set during the ObjectMaterialized event or through relationship fixup Private Function GetSavedReferenceKey( ByVal entityIndex As EntityIndex, ByVal reference As EntityReference, ByVal entity As Object, ByVal navigationProperty As NavigationProperty, ByVal values As IDictionary(Of String, Object)) As EntityKey Debug.Assert(navigationProperty Is Nothing OrElse Equals(reference.RelationshipSet.ElementType, navigationProperty.RelationshipType), "the reference and navigationProperty should correspond") Dim entitySet As EntitySet = DirectCast(reference.RelationshipSet, AssociationSet).AssociationSetEnds(reference.TargetRoleName).EntitySet Dim foundKeyMembers As New List(Of EntityKeyMember)(1) Dim foundNone As Boolean = True Dim missingSome As Boolean = False For Each keyMember As EdmMember In entitySet.ElementType.KeyMembers Dim lookupKey As String = CreateReferenceKeyLookup(keyMember.Name, reference, navigationProperty) Dim value As Object = Nothing If values.TryGetValue(lookupKey, value) Then foundKeyMembers.Add(New EntityKeyMember(keyMember.Name, value)) foundNone = False Else missingSome = True End If Next If foundNone Then ' we didn't find a key Return Nothing ElseIf missingSome Then Throw New InvalidOperationException( String.Format( CultureInfo.CurrentCulture, "The OriginalValues or ExtendedProperties collections on the type '{0}' contained only a partial key to satisfy the relationship '{1}' targeting the role '{2}'", entity.GetType().FullName, reference.RelationshipName, reference.TargetRoleName)) End If Return entityIndex.ConvertEntityKey(New EntityKey(reference.GetEntitySetName(), foundKeyMembers)) End Function ' Moves the key corresponding to the passed in EntityReference from a source collection to a target collection Private Sub MoveSavedReferenceKey( ByVal reference As EntityReference, ByVal entity As Object, ByVal navigationProperty As NavigationProperty, ByVal sourceValues As IDictionary(Of String, Object), ByVal targetValues As IDictionary(Of String, Object)) Debug.Assert(navigationProperty Is Nothing OrElse Equals(reference.RelationshipSet.ElementType, navigationProperty.RelationshipType), "the reference and navigationProperty should coorospond") Dim entitySet As EntitySet = DirectCast(reference.RelationshipSet, AssociationSet).AssociationSetEnds(reference.TargetRoleName).EntitySet Dim missingSome As Boolean = False For Each keyMember As EdmMember In entitySet.ElementType.KeyMembers Dim lookupKey As String = CreateReferenceKeyLookup(keyMember.Name, reference, navigationProperty) Dim value As Object = Nothing If sourceValues.TryGetValue(lookupKey, value) Then If targetValues.ContainsKey(lookupKey) Then targetValues(lookupKey) = value Else targetValues.Add(lookupKey, value) End If sourceValues.Remove(lookupKey) Else missingSome = True End If Next If missingSome Then Throw New InvalidOperationException( String.Format( CultureInfo.CurrentCulture, " The OriginalValues or ExtendedProperties collections on the type '{0}' contained only a partial key to satisfy the relationship '{1}' targeting the role '{2}'", entity.GetType().FullName, reference.RelationshipName, reference.TargetRoleName)) End If End Sub Private Function EnumerateSaveReferences(ByVal manager As RelationshipManager) As IEnumerable(Of EntityReference) Return manager.GetAllRelatedEnds().OfType(Of EntityReference)().Where( Function(er) er.RelationshipSet.ElementType.RelationshipEndMembers(er.SourceRoleName).RelationshipMultiplicity <> RelationshipMultiplicity.One AndAlso _ Not DirectCast(er.RelationshipSet, AssociationSet).ElementType.IsForeignKey) End Function Friend Sub StoreReferenceKeyValues(ByVal context As ObjectContext, ByVal entity As IObjectWithChangeTracker) If entity Is Nothing Then Throw New ArgumentNullException("entity") End If Dim entry As ObjectStateEntry = Nothing If Not context.ObjectStateManager.TryGetObjectStateEntry(entity, entry) Then ' must be a no tracking query, the reference key info won't be available Return End If Dim relationshipManager As RelationshipManager = entry.RelationshipManager Dim entityType As EntityType = context.MetadataWorkspace.GetCSpaceEntityType(entity.GetType()) For Each loopEntityReference As EntityReference In EnumerateSaveReferences(relationshipManager) Dim entityReference As EntityReference = loopEntityReference Dim navigationProperty As NavigationProperty = entityType.NavigationProperties.FirstOrDefault( _ Function(n) n.RelationshipType Is entityReference.RelationshipSet.ElementType AndAlso _ n.FromEndMember.Name = entityReference.SourceRoleName AndAlso _ n.ToEndMember.Name = entityReference.TargetRoleName) Dim value As Object = entityReference.GetValue() If (navigationProperty Is Nothing OrElse value Is Nothing) AndAlso entityReference.EntityKey IsNot Nothing Then For Each item As EntityKeyMember In entityReference.EntityKey.EntityKeyValues Dim key As String = CreateReferenceKeyLookup(item.Key, entityReference, navigationProperty) entity.ChangeTracker.ExtendedProperties.Add(key, item.Value) Next End If Next End Sub Private Sub HandleEntity(ByVal context As ObjectContext, ByVal entityIndex As EntityIndex, ByVal allRelationships As RelationshipSet, ByVal entity As IObjectWithChangeTracker) ChangeEntityStateBasedOnObjectState(context, entity) HandleRelationshipKeys(context, entityIndex, allRelationships, entity) UpdateOriginalValues(context, entity) End Sub Private Sub HandleDeletedEntity(ByVal context As ObjectContext, ByVal entityIndex As EntityIndex, ByVal allRelationships As RelationshipSet, ByVal entity As IObjectWithChangeTracker) HandleRelationshipKeys(context, entityIndex, allRelationships, entity) ChangeEntityStateBasedOnObjectState(context, entity) UpdateOriginalValues(context, entity) End Sub Private Sub UpdateOriginalValues(ByVal context As ObjectContext, ByVal entity As IObjectWithChangeTracker) If entity.ChangeTracker.State = ObjectState.Unchanged OrElse entity.ChangeTracker.State = ObjectState.Added OrElse entity.ChangeTracker.OriginalValues Is Nothing Then ' nothing to do here Exit Sub End If ' we only need/want to deal with scalar and complex properties Dim entry As ObjectStateEntry = context.ObjectStateManager.GetObjectStateEntry(entity) Dim originalValueRecord As OriginalValueRecord = entry.GetUpdatableOriginalValues() Dim entityType As EntityType = context.MetadataWorkspace.GetCSpaceEntityType(entity.GetType()) ' walk through each property and see if we have an original value for it ' set it if we do. Walk down through ComplexType properties to set original values ' for each of them also ' ' it is expected that the original values will be sparse because we are trying ' to only capture originals for the ones we are required to have (concurency, sproc, condition, more?) For Each edmProperty As EdmProperty In entityType.Properties Dim value As Object = Nothing If TypeOf edmProperty.TypeUsage.EdmType Is PrimitiveType AndAlso entity.ChangeTracker.OriginalValues.TryGetValue(edmProperty.Name, value) Then originalValueRecord.SetValue(edmProperty, value) ElseIf TypeOf edmProperty.TypeUsage.EdmType Is ComplexType Then Dim complexOriginalValues As OriginalValueRecord = originalValueRecord.GetOriginalValueRecord(edmProperty.Name) UpdateOriginalValues(DirectCast(edmProperty.TypeUsage.EdmType, ComplexType), entity.GetType().FullName, edmProperty.Name, entity.ChangeTracker.OriginalValues, complexOriginalValues) End If Next End Sub Private Sub UpdateOriginalValues( ByVal complexType As ComplexType, ByVal entityTypeName As String, ByVal propertyPathToType As String, ByVal originalValueSource As IDictionary(Of String, Object), ByVal complexOriginalValueRecord As OriginalValueRecord) ' Note that complexOriginalValueRecord may be null ' a null complexOriginalValueRecord will only occur if a null reference is assigned ' to a ComplexType property and then given to ApplyChanges. ' walk through each property and see if we have an original value for it ' set it if we do. Walk down through ComplexType properties to set original values ' for each of them also For Each edmProperty As EdmProperty In complexType.Properties Dim value As Object = Nothing Dim propertyPath As String = String.Format(CultureInfo.InvariantCulture, "{0}.{1}", propertyPathToType, edmProperty.Name) If TypeOf edmProperty.TypeUsage.EdmType Is PrimitiveType AndAlso originalValueSource.TryGetValue(propertyPath, value) Then If complexOriginalValueRecord IsNot Nothing Then complexOriginalValueRecord.SetValue(edmProperty, value) ElseIf value IsNot Nothing Then Debug.Assert(complexOriginalValueRecord Is Nothing, "we only throw when the value is not null and the recored is null") Throw New InvalidOperationException( String.Format( CultureInfo.CurrentCulture, "Can not set the original value on the object stored in the property '{0}' on the type '{1}' because the property is null.", propertyPathToType, entityTypeName)) End If ElseIf TypeOf edmProperty.TypeUsage.EdmType Is ComplexType Then Dim nestedOriginalValueRecord As OriginalValueRecord = Nothing If complexOriginalValueRecord IsNot Nothing Then nestedOriginalValueRecord = complexOriginalValueRecord.GetOriginalValueRecord(edmProperty.Name) End If ' recurse down the chain of complex types... UpdateOriginalValues(DirectCast(edmProperty.TypeUsage.EdmType, ComplexType), entityTypeName, propertyPath, originalValueSource, nestedOriginalValueRecord) End If Next End Sub Private Function GetOriginalValueRecord(ByVal record As OriginalValueRecord, ByVal name As String) As OriginalValueRecord Dim ordinal As Integer = record.GetOrdinal(name) If Not record.IsDBNull(ordinal) Then Return TryCast(record.GetDataRecord(ordinal), OriginalValueRecord) Else Return Nothing End If End Function Private Sub SetValue(ByVal record As OriginalValueRecord, ByVal edmProperty As EdmProperty, ByVal value As Object) If value Is Nothing Then Dim entityClrType As Type = DirectCast(edmProperty.TypeUsage.EdmType, PrimitiveType).ClrEquivalentType If entityClrType.IsValueType AndAlso Not (entityClrType.IsGenericType AndAlso GetType(Nullable(Of )) Is entityClrType.GetGenericTypeDefinition()) Then ' Skip setting null original values on non-nullable CLR types because the ObjectStateEntry won't allow this Return End If End If Dim ordinal As Integer = record.GetOrdinal(edmProperty.Name) record.SetValue(ordinal, value) End Sub Private Sub ChangeEntityStateBasedOnObjectState(ByVal context As ObjectContext, ByVal entity As IObjectWithChangeTracker) Select Case entity.ChangeTracker.State Case (ObjectState.Added) ' No-op: the state entry is already marked as added Debug.Assert(context.ObjectStateManager.GetObjectStateEntry(entity).State = EntityState.Added, "State should have been Added") Case (ObjectState.Unchanged) context.ObjectStateManager.ChangeObjectState(entity, EntityState.Unchanged) Case (ObjectState.Modified) context.ObjectStateManager.ChangeObjectState(entity, EntityState.Modified) Case (ObjectState.Deleted) context.ObjectStateManager.ChangeObjectState(entity, EntityState.Deleted) End Select End Sub Private Function GetCSpaceEntityType(ByVal workspace As MetadataWorkspace, ByVal type As Type) As EntityType Dim ospaceEntityType As EntityType = Nothing Dim cspaceEntityType As StructuralType = Nothing Dim resultEntityType As EntityType = Nothing If workspace.TryGetItem(Of EntityType)(type.FullName, DataSpace.OSpace, ospaceEntityType) Then If workspace.TryGetEdmSpaceType(ospaceEntityType, cspaceEntityType) Then resultEntityType = TryCast(cspaceEntityType, EntityType) End If End If If resultEntityType Is Nothing Then Throw New ArgumentException(String.Format(CultureInfo.CurrentCulture, "Unable to find a CSpace type for type {0}", type.FullName)) End If Return resultEntityType End Function Private Function GetValue(ByVal entityReference As System.Data.Objects.DataClasses.EntityReference) As Object For Each value As Object In entityReference Return value Next Return Nothing End Function Private Function GetCurrentEntityKey(ByVal entityReference As System.Data.Objects.DataClasses.EntityReference, ByVal context As ObjectContext) As EntityKey Dim currentKey As EntityKey = Nothing Dim currentValue As Object = entityReference.GetValue() If currentValue IsNot Nothing Then Dim relatedEntry As ObjectStateEntry = context.ObjectStateManager.GetObjectStateEntry(currentValue) currentKey = relatedEntry.EntityKey Else currentKey = entityReference.EntityKey End If Return currentKey End Function Private Function GetRelatedEnd(ByVal entry As ObjectStateEntry, ByVal navigationPropertyIdentity As String) As RelatedEnd Dim navigationProperty As NavigationProperty = GetNavigationProperty(entry.ObjectStateManager.MetadataWorkspace.GetCSpaceEntityType(entry.Entity.GetType()), navigationPropertyIdentity) Return TryCast(entry.RelationshipManager.GetRelatedEnd(navigationProperty.RelationshipType.FullName, navigationProperty.ToEndMember.Name), RelatedEnd) End Function Private Function GetNavigationProperty(ByVal entityType As EntityType, ByVal navigationPropertyIdentity As String) As NavigationProperty Dim navigationProperty As NavigationProperty = Nothing If Not entityType.NavigationProperties.TryGetValue(navigationPropertyIdentity, False, navigationProperty) Then Throw New InvalidOperationException( String.Format( CultureInfo.CurrentCulture, "Could not find navigation property '{0}' in EntityType '{1}'.", navigationPropertyIdentity, entityType.FullName)) End If Return navigationProperty End Function Private Function GetEntitySetName(ByVal relatedEnd As RelatedEnd) As String Dim entitySet As EntitySet = DirectCast(relatedEnd.RelationshipSet, AssociationSet).AssociationSetEnds(relatedEnd.TargetRoleName).EntitySet Return (entitySet.EntityContainer.Name & ".") & entitySet.Name End Function Private Function IsDependentEndOfReferentialConstraint(ByVal relatedEnd As RelatedEnd) As Boolean If relatedEnd.RelationshipSet IsNot Nothing Then ' NOTE Referential constraints collection will usually contains 0 or 1 element, ' so performance shouldn't be an issue here For Each constraint As ReferentialConstraint In DirectCast(relatedEnd.RelationshipSet.ElementType, AssociationType).ReferentialConstraints If constraint.ToRole.Name = relatedEnd.SourceRoleName Then ' Example: ' Client --- Order ' RI Constraint: Principal/From , Dependent/To ' When current RelatedEnd is a CollectionOrReference in Order's relationships, ' constarint.ToRole == this._fromEndProperty == Order Return True End If Next End If Return False End Function Private Function TryGetObjectStateEntry( ByVal context As ObjectContext, ByVal fromKey As EntityKey, ByVal toKey As EntityKey, ByVal associationSet As AssociationSet, ByVal fromEnd As AssociationEndMember, ByVal toEnd As AssociationEndMember, ByRef entry As ObjectStateEntry) As Boolean entry = Nothing For Each relationshipEntry As ObjectStateEntry In ( From e In context.ObjectStateManager.GetObjectStateEntries(EntityState.Added Or EntityState.Unchanged) Where e.IsRelationship AndAlso Equals(e.EntitySet, associationSet) Select e) Dim currentValues As CurrentValueRecord = relationshipEntry.CurrentValues Dim fromOrdinal As Integer = currentValues.GetOrdinal(fromEnd.Name) Dim toOrdinal As Integer = currentValues.GetOrdinal(toEnd.Name) If DirectCast(currentValues.GetValue(fromOrdinal), EntityKey) = fromKey AndAlso DirectCast(currentValues.GetValue(toOrdinal), EntityKey) = toKey Then entry = relationshipEntry Return True End If Next Return False End Function Private NotInheritable Class AddHelper Private ReadOnly _context As ObjectContext Private ReadOnly _entityIndex As EntityIndex ' Used during add processing Private ReadOnly _entitiesToAdd As Queue(Of Tuple(Of String, IObjectWithChangeTracker)) Private ReadOnly _entitiesDuringAdd As Queue(Of Tuple(Of ObjectStateEntry, String, IEnumerable(Of Object))) Public Shared Function AddAllEntities(ByVal context As ObjectContext, ByVal entitySetName As String, ByVal entity As IObjectWithChangeTracker) As EntityIndex Dim addHelper As New AddHelper(context) Try ' Include the root element to start the Apply addHelper.QueueAdd(entitySetName, entity) ' Add everything While addHelper.HasMore Dim entityInSet As Tuple(Of String, IObjectWithChangeTracker) = addHelper.NextAdd() ' Only add the object if it's not already in the context Dim entry As ObjectStateEntry = Nothing If Not context.ObjectStateManager.TryGetObjectStateEntry(entityInSet.Item2, entry) Then context.AddObject(entityInSet.Item1, entityInSet.Item2) End If End While Finally addHelper.Detach() End Try Return addHelper.EntityIndex End Function Private Sub New(ByVal context As ObjectContext) _context = context AddHandler _context.ObjectStateManager.ObjectStateManagerChanged, AddressOf Me.HandleStateManagerChange _entityIndex = New EntityIndex(context) _entitiesToAdd = New Queue(Of Tuple(Of String, IObjectWithChangeTracker))() _entitiesDuringAdd = New Queue(Of Tuple(Of ObjectStateEntry, String, IEnumerable(Of Object)))() End Sub Private Sub Detach() RemoveHandler _context.ObjectStateManager.ObjectStateManagerChanged, AddressOf Me.HandleStateManagerChange End Sub Private Sub HandleStateManagerChange(ByVal sender As Object, ByVal args As CollectionChangeEventArgs) If args.Action = CollectionChangeAction.Add Then Dim entity As Object = args.Element Dim entry As ObjectStateEntry = _context.ObjectStateManager.GetObjectStateEntry(entity) Dim changeTracker As ObjectChangeTracker = DirectCast(entity, IObjectWithChangeTracker).ChangeTracker changeTracker.ChangeTrackingEnabled = False _entityIndex.Add(entry, changeTracker) ' Queue removed reference values Dim navPropNames As IEnumerable(Of String) = _context.MetadataWorkspace.GetCSpaceEntityType(entity.GetType()).NavigationProperties.Select(Function(n) n.Name) Dim entityRefOriginalValues As IEnumerable(Of KeyValuePair(Of String, Object)) = changeTracker.OriginalValues.Where(Function(kvp) navPropNames.Contains(kvp.Key)) For Each originalValueWithName As KeyValuePair(Of String, Object) In entityRefOriginalValues If originalValueWithName.Value IsNot Nothing Then _entitiesDuringAdd.Enqueue(New Tuple(Of ObjectStateEntry, String, IEnumerable(Of Object))(entry, originalValueWithName.Key, New Object() {originalValueWithName.Value})) End If Next ' Queue removed collection values For Each collectionPropertyChangesWithName As KeyValuePair(Of String, ObjectList) In changeTracker.ObjectsRemovedFromCollectionProperties _entitiesDuringAdd.Enqueue(New Tuple(Of ObjectStateEntry, String, IEnumerable(Of Object))(entry, collectionPropertyChangesWithName.Key, collectionPropertyChangesWithName.Value)) Next End If End Sub Private ReadOnly Property EntityIndex() As EntityIndex Get Return _entityIndex End Get End Property Private ReadOnly Property HasMore() As Boolean Get ProcessNewAdds() Return _entitiesToAdd.Count > 0 End Get End Property Private Sub QueueAdd(ByVal entitySetName As String, ByVal entity As IObjectWithChangeTracker) If Not _entityIndex.Contains(entity) Then ' Queue the entity so that we can add the 'removed collection' items _entitiesToAdd.Enqueue(New Tuple(Of String, IObjectWithChangeTracker)(entitySetName, entity)) End If End Sub Private Function NextAdd() As Tuple(Of String, IObjectWithChangeTracker) ProcessNewAdds() Return _entitiesToAdd.Dequeue() End Function Private Sub ProcessNewAdds() While _entitiesDuringAdd.Count > 0 Dim relatedEntities As Tuple(Of ObjectStateEntry, String, IEnumerable(Of Object)) = _entitiesDuringAdd.Dequeue() Dim relatedEnd As RelatedEnd = relatedEntities.Item1.GetRelatedEnd(relatedEntities.Item2) Dim entitySetName As String = relatedEnd.GetEntitySetName() For Each targetEntity As Object In relatedEntities.Item3 QueueAdd(entitySetName, CType(targetEntity, IObjectWithChangeTracker)) Next End While End Sub End Class Private NotInheritable Class EntityIndex Private ReadOnly _context As ObjectContext ' Set of all entities Private ReadOnly _allEntities As HashSet(Of IObjectWithChangeTracker) ' Index of the final key that will be used in the context (could be real for non-added, could be temporary for added) ' to the initial temporary key Private ReadOnly _temporaryKeyMap As Dictionary(Of EntityKey, EntityKey) Public Sub New(ByVal context As ObjectContext) _context = context _allEntities = New HashSet(Of IObjectWithChangeTracker)() _temporaryKeyMap = New Dictionary(Of EntityKey, EntityKey)() End Sub Public Sub Add(ByVal entry As ObjectStateEntry, ByVal changeTracker As ObjectChangeTracker) Dim temporaryKey As EntityKey = entry.EntityKey Dim finalKey As EntityKey If Not _allEntities.Contains(DirectCast(entry.Entity, IObjectWithChangeTracker)) Then ' Track that this Apply will be handling this entity _allEntities.Add(DirectCast(entry.Entity, IObjectWithChangeTracker)) End If If changeTracker.State = ObjectState.Added Then finalKey = temporaryKey Else finalKey = _context.CreateEntityKey((temporaryKey.EntityContainerName & ".") & temporaryKey.EntitySetName, entry.Entity) End If If Not _temporaryKeyMap.ContainsKey(finalKey) Then _temporaryKeyMap.Add(finalKey, temporaryKey) End If End Sub Public Function Contains(ByVal entity As Object) As Boolean Return _allEntities.Contains(DirectCast(entity, IObjectWithChangeTracker)) End Function Public ReadOnly Property AllEntities() As IEnumerable(Of IObjectWithChangeTracker) Get Return _allEntities End Get End Property ' Converts the passed in EntityKey to the EntityKey that is usable by the current state of ApplyChanges Public Function ConvertEntityKey(ByVal targetKey As EntityKey) As EntityKey Dim targetEntry As ObjectStateEntry = Nothing If Not _context.ObjectStateManager.TryGetObjectStateEntry(targetKey, targetEntry) Then ' If no entry exists, then either: ' 1. This is an EntityKey that is not represented in the set of entities being dealt with during the Apply ' 2. This is an EntityKey that will represent one of the yet-to-be-processed Added entries, so look it up Dim temporaryKey As EntityKey = Nothing If _temporaryKeyMap.TryGetValue(targetKey, temporaryKey) Then targetKey = temporaryKey End If End If Return targetKey End Function End Class ' The RelationshipSet builds a list of all relationships from an ' initial set of entities Private NotInheritable Class RelationshipSet Implements IEnumerable(Of RelationshipWrapper) Private ReadOnly _relationships As HashSet(Of RelationshipWrapper) Private ReadOnly _context As ObjectContext Public Sub New(ByVal context As ObjectContext, ByVal allEntities As IEnumerable(Of Object)) _context = context _relationships = New HashSet(Of RelationshipWrapper)() For Each entity As Object In allEntities Dim entry As ObjectStateEntry = context.ObjectStateManager.GetObjectStateEntry(entity) For Each relatedEnd As IRelatedEnd In entry.RelationshipManager.GetAllRelatedEnds() If Not DirectCast(relatedEnd.RelationshipSet.ElementType, AssociationType).IsForeignKey Then For Each targetEntity As Object In relatedEnd Add(relatedEnd, entity, targetEntity, EntityState.Unchanged) Next End If Next Next End Sub ' Adds an entry to the index based on a IRelatedEnd Public Sub Add(ByVal relatedEnd As IRelatedEnd, ByVal sourceEntity As Object, ByVal targetEntity As Object, ByVal state As EntityState) Dim wrapper As New RelationshipWrapper(DirectCast(relatedEnd.RelationshipSet, AssociationSet), relatedEnd.SourceRoleName, sourceEntity, relatedEnd.TargetRoleName, targetEntity, state) If Not _relationships.Contains(wrapper) Then _relationships.Add(wrapper) End If End Sub ' Removes an entry from the index based on a relationship ObjectStateEntry Public Sub Remove(ByVal relationshipEntry As ObjectStateEntry) Debug.Assert(relationshipEntry.IsRelationship) Dim associationSet As AssociationSet = DirectCast(relationshipEntry.EntitySet, AssociationSet) Dim values As DbDataRecord = If(relationshipEntry.State = EntityState.Deleted, relationshipEntry.OriginalValues, relationshipEntry.CurrentValues) Dim fromOridinal As Integer = values.GetOrdinal(associationSet.ElementType.AssociationEndMembers(0).Name) Dim fromEntity As Object = _context.ObjectStateManager.GetObjectStateEntry(DirectCast(values.GetValue(fromOridinal), EntityKey)).Entity Dim toOridinal As Integer = values.GetOrdinal(associationSet.ElementType.AssociationEndMembers(1).Name) Dim toEntity As Object = _context.ObjectStateManager.GetObjectStateEntry(DirectCast(values.GetValue(toOridinal), EntityKey)).Entity If fromEntity IsNot Nothing AndAlso toEntity IsNot Nothing Then Dim wrapper As New RelationshipWrapper(associationSet, associationSet.ElementType.AssociationEndMembers(0).Name, fromEntity, associationSet.ElementType.AssociationEndMembers(1).Name, toEntity, EntityState.Unchanged) _relationships.Remove(wrapper) End If End Sub #Region "IEnumerable" Public Function GetEnumerator() As IEnumerator(Of RelationshipWrapper) Implements System.Collections.Generic.IEnumerable(Of RelationshipWrapper).GetEnumerator Return _relationships.GetEnumerator() End Function Private Function GetEnumerator1() As System.Collections.IEnumerator Implements System.Collections.IEnumerable.GetEnumerator Return _relationships.GetEnumerator() End Function #End Region End Class ' A RelationshipWrapper is used to identify a relationship between two entities ' The relationship is identified by the AssociationSet, and the order of the entities based ' on the roles they play (via AssociationEndMember) Private NotInheritable Class RelationshipWrapper Implements IEquatable(Of RelationshipWrapper) Friend ReadOnly AssociationSet As AssociationSet Friend ReadOnly End0 As Object Friend ReadOnly End1 As Object Friend ReadOnly State As EntityState Friend Sub New(ByVal extent As AssociationSet, ByVal role0 As String, ByVal associationEndMember0 As Object, ByVal role1 As String, ByVal associationEndMember1 As Object, ByVal state As EntityState) Debug.Assert(extent IsNot Nothing, "null AssociationSet") Debug.Assert(DirectCast(associationEndMember0, Object) IsNot Nothing, "null associationEndMember0") Debug.Assert(DirectCast(associationEndMember1, Object) IsNot Nothing, "null associationEndMember1") AssociationSet = extent Debug.Assert(extent.ElementType.AssociationEndMembers.Count = 2, "only 2 ends are supported") Me.State = state If extent.ElementType.AssociationEndMembers(0).Name = role0 Then Debug.Assert(extent.ElementType.AssociationEndMembers(1).Name = role1, "a)roleAndKey1 Name differs") End0 = associationEndMember0 End1 = associationEndMember1 Else Debug.Assert(extent.ElementType.AssociationEndMembers(0).Name = role1, "b)roleAndKey1 Name differs") Debug.Assert(extent.ElementType.AssociationEndMembers(1).Name = role0, "b)roleAndKey0 Name differs") End0 = associationEndMember1 End1 = associationEndMember0 End If End Sub Friend ReadOnly Property AssociationEndMembers() As ReadOnlyMetadataCollection(Of AssociationEndMember) Get Return Me.AssociationSet.ElementType.AssociationEndMembers End Get End Property Public Overloads Overrides Function GetHashCode() As Integer Return Me.AssociationSet.Name.GetHashCode() Xor (Me.End0.GetHashCode() + Me.End1.GetHashCode()) End Function Public Overloads Overrides Function Equals(ByVal obj As Object) As Boolean Return Equals(TryCast(obj, RelationshipWrapper)) End Function Public Overloads Function Equals(ByVal wrapper As RelationshipWrapper) As Boolean Implements System.IEquatable(Of RelationshipWrapper).Equals Return (Object.ReferenceEquals(Me, wrapper) OrElse ((wrapper IsNot Nothing) AndAlso Object.ReferenceEquals(Me.AssociationSet, wrapper.AssociationSet) AndAlso Object.ReferenceEquals(Me.End0, wrapper.End0) AndAlso Object.ReferenceEquals(Me.End1, wrapper.End1))) End Function End Class End Module <#+ End Sub #>