1 //--------------------------------------------------------------------- 2 // <copyright file="FunctionMappingTranslator.cs" company="Microsoft"> 3 // Copyright (c) Microsoft Corporation. All rights reserved. 4 // </copyright> 5 // 6 // @owner Microsoft 7 // @backupOwner Microsoft 8 //--------------------------------------------------------------------- 9 10 namespace System.Data.Mapping.Update.Internal 11 { 12 using System.Collections.Generic; 13 using System.Data.Common; 14 using System.Data.Entity; 15 using System.Data.Metadata.Edm; 16 using System.Diagnostics; 17 using System.Linq; 18 19 /// <summary> 20 /// Modification function mapping translators are defined per extent (entity set 21 /// or association set) and manage the creation of function commands. 22 /// </summary> 23 internal abstract class ModificationFunctionMappingTranslator 24 { 25 /// <summary> 26 /// Requires: this translator must be registered to handle the entity set 27 /// for the given state entry. 28 /// 29 /// Translates the given state entry to a command. 30 /// </summary> 31 /// <param name="translator">Parent update translator (global state for the workload)</param> 32 /// <param name="stateEntry">State entry to translate. Must belong to the 33 /// entity/association set handled by this translator</param> 34 /// <returns>Command corresponding to the given state entry</returns> Translate( UpdateTranslator translator, ExtractedStateEntry stateEntry)35 internal abstract FunctionUpdateCommand Translate( 36 UpdateTranslator translator, 37 ExtractedStateEntry stateEntry); 38 39 /// <summary> 40 /// Initialize a translator for the given entity set mapping. 41 /// </summary> 42 /// <param name="setMapping">Entity set mapping.</param> 43 /// <returns>Translator.</returns> CreateEntitySetTranslator( StorageEntitySetMapping setMapping)44 internal static ModificationFunctionMappingTranslator CreateEntitySetTranslator( 45 StorageEntitySetMapping setMapping) 46 { 47 return new EntitySetTranslator(setMapping); 48 } 49 50 /// <summary> 51 /// Initialize a translator for the given association set mapping. 52 /// </summary> 53 /// <param name="setMapping">Association set mapping.</param> 54 /// <returns>Translator.</returns> CreateAssociationSetTranslator( StorageAssociationSetMapping setMapping)55 internal static ModificationFunctionMappingTranslator CreateAssociationSetTranslator( 56 StorageAssociationSetMapping setMapping) 57 { 58 return new AssociationSetTranslator(setMapping); 59 } 60 61 private sealed class EntitySetTranslator : ModificationFunctionMappingTranslator 62 { 63 private readonly Dictionary<EntityType, StorageEntityTypeModificationFunctionMapping> m_typeMappings; 64 EntitySetTranslator(StorageEntitySetMapping setMapping)65 internal EntitySetTranslator(StorageEntitySetMapping setMapping) 66 { 67 Debug.Assert(null != setMapping && null != setMapping.ModificationFunctionMappings && 68 0 < setMapping.ModificationFunctionMappings.Count, "set mapping must exist and must specify function mappings"); 69 m_typeMappings = new Dictionary<EntityType, StorageEntityTypeModificationFunctionMapping>(); 70 foreach (StorageEntityTypeModificationFunctionMapping typeMapping in setMapping.ModificationFunctionMappings) 71 { 72 m_typeMappings.Add(typeMapping.EntityType, typeMapping); 73 } 74 } 75 Translate( UpdateTranslator translator, ExtractedStateEntry stateEntry)76 internal override FunctionUpdateCommand Translate( 77 UpdateTranslator translator, 78 ExtractedStateEntry stateEntry) 79 { 80 var mapping = GetFunctionMapping(stateEntry); 81 StorageEntityTypeModificationFunctionMapping typeMapping = mapping.Item1; 82 StorageModificationFunctionMapping functionMapping = mapping.Item2; 83 EntityKey entityKey = stateEntry.Source.EntityKey; 84 85 var stateEntries = new HashSet<IEntityStateEntry> { stateEntry.Source }; 86 87 // gather all referenced association ends 88 var collocatedEntries = 89 // find all related entries corresponding to collocated association types 90 from end in functionMapping.CollocatedAssociationSetEnds 91 join candidateEntry in translator.GetRelationships(entityKey) 92 on end.CorrespondingAssociationEndMember.DeclaringType equals candidateEntry.EntitySet.ElementType 93 select Tuple.Create(end.CorrespondingAssociationEndMember, candidateEntry); 94 95 var currentReferenceEnds = new Dictionary<AssociationEndMember, IEntityStateEntry>(); 96 var originalReferenceEnds = new Dictionary<AssociationEndMember, IEntityStateEntry>(); 97 98 foreach (var candidate in collocatedEntries) 99 { 100 ProcessReferenceCandidate(entityKey, stateEntries, currentReferenceEnds, originalReferenceEnds, candidate.Item1, candidate.Item2); 101 } 102 103 // create function object 104 FunctionUpdateCommand command; 105 106 // consider the following scenario, we need to loop through all the state entries that is correlated with entity2 and make sure it is not changed. 107 // entity1 <-- Independent Association <-- entity2 <-- Fk association <-- entity 3 108 // | 109 // entity4 <-- Fk association <-- 110 if (stateEntries.All(e => e.State == EntityState.Unchanged)) 111 { 112 // we shouldn't update the entity if it is unchanged, only update when referenced association is changed. 113 // if not, then this will trigger a fake update for principal end as describe in bug 894569. 114 command = null; 115 } 116 else 117 { 118 command = new FunctionUpdateCommand(functionMapping, translator, stateEntries.ToList().AsReadOnly(), stateEntry); 119 120 // bind all function parameters 121 BindFunctionParameters(translator, stateEntry, functionMapping, command, currentReferenceEnds, originalReferenceEnds); 122 123 // interpret all result bindings 124 if (null != functionMapping.ResultBindings) 125 { 126 foreach (StorageModificationFunctionResultBinding resultBinding in functionMapping.ResultBindings) 127 { 128 PropagatorResult result = stateEntry.Current.GetMemberValue(resultBinding.Property); 129 command.AddResultColumn(translator, resultBinding.ColumnName, result); 130 } 131 } 132 } 133 134 return command; 135 } 136 ProcessReferenceCandidate( EntityKey source, HashSet<IEntityStateEntry> stateEntries, Dictionary<AssociationEndMember, IEntityStateEntry> currentReferenceEnd, Dictionary<AssociationEndMember, IEntityStateEntry> originalReferenceEnd, AssociationEndMember endMember, IEntityStateEntry candidateEntry)137 private static void ProcessReferenceCandidate( 138 EntityKey source, 139 HashSet<IEntityStateEntry> stateEntries, 140 Dictionary<AssociationEndMember, IEntityStateEntry> currentReferenceEnd, 141 Dictionary<AssociationEndMember, IEntityStateEntry> originalReferenceEnd, 142 AssociationEndMember endMember, 143 IEntityStateEntry candidateEntry) 144 { 145 Func<DbDataRecord, int, EntityKey> getEntityKey = (record, ordinal) => (EntityKey)record[ordinal]; 146 Action<DbDataRecord, Action<IEntityStateEntry>> findMatch = (record, registerTarget) => 147 { 148 // find the end corresponding to the 'to' end 149 int toOrdinal = record.GetOrdinal(endMember.Name); 150 Debug.Assert(-1 != toOrdinal, "to end of relationship doesn't exist in record"); 151 152 // the 'from' end must be the other end 153 int fromOrdinal = 0 == toOrdinal ? 1 : 0; 154 155 if (getEntityKey(record, fromOrdinal) == source) 156 { 157 stateEntries.Add(candidateEntry); 158 registerTarget(candidateEntry); 159 } 160 }; 161 162 switch (candidateEntry.State) 163 { 164 case EntityState.Unchanged: 165 findMatch( 166 candidateEntry.CurrentValues, 167 (target) => 168 { 169 currentReferenceEnd.Add(endMember, target); 170 originalReferenceEnd.Add(endMember, target); 171 }); 172 break; 173 case EntityState.Added: 174 findMatch( 175 candidateEntry.CurrentValues, 176 (target) => currentReferenceEnd.Add(endMember, target)); 177 break; 178 case EntityState.Deleted: 179 findMatch( 180 candidateEntry.OriginalValues, 181 (target) => originalReferenceEnd.Add(endMember, target)); 182 break; 183 default: 184 break; 185 } 186 } 187 GetFunctionMapping(ExtractedStateEntry stateEntry)188 private Tuple<StorageEntityTypeModificationFunctionMapping, StorageModificationFunctionMapping> GetFunctionMapping(ExtractedStateEntry stateEntry) 189 { 190 // choose mapping based on type and operation 191 StorageModificationFunctionMapping functionMapping; 192 EntityType entityType; 193 if (null != stateEntry.Current) 194 { 195 entityType = (EntityType)stateEntry.Current.StructuralType; 196 } 197 else 198 { 199 entityType = (EntityType)stateEntry.Original.StructuralType; 200 } 201 StorageEntityTypeModificationFunctionMapping typeMapping = m_typeMappings[entityType]; 202 switch (stateEntry.State) 203 { 204 case EntityState.Added: 205 functionMapping = typeMapping.InsertFunctionMapping; 206 EntityUtil.ValidateNecessaryModificationFunctionMapping(functionMapping, "Insert", stateEntry.Source, "EntityType", entityType.Name); 207 break; 208 case EntityState.Deleted: 209 functionMapping = typeMapping.DeleteFunctionMapping; 210 EntityUtil.ValidateNecessaryModificationFunctionMapping(functionMapping, "Delete", stateEntry.Source, "EntityType", entityType.Name); 211 break; 212 case EntityState.Unchanged: 213 case EntityState.Modified: 214 functionMapping = typeMapping.UpdateFunctionMapping; 215 EntityUtil.ValidateNecessaryModificationFunctionMapping(functionMapping, "Update", stateEntry.Source, "EntityType", entityType.Name); 216 break; 217 default: 218 functionMapping = null; 219 Debug.Fail("unexpected state"); 220 break; 221 } 222 return Tuple.Create(typeMapping, functionMapping); 223 } 224 225 // Walks through all parameter bindings in the function mapping and binds the parameters to the 226 // requested properties of the given state entry. BindFunctionParameters(UpdateTranslator translator, ExtractedStateEntry stateEntry, StorageModificationFunctionMapping functionMapping, FunctionUpdateCommand command, Dictionary<AssociationEndMember, IEntityStateEntry> currentReferenceEnds, Dictionary<AssociationEndMember, IEntityStateEntry> originalReferenceEnds)227 private void BindFunctionParameters(UpdateTranslator translator, ExtractedStateEntry stateEntry, StorageModificationFunctionMapping functionMapping, FunctionUpdateCommand command, Dictionary<AssociationEndMember, IEntityStateEntry> currentReferenceEnds, Dictionary<AssociationEndMember, IEntityStateEntry> originalReferenceEnds) 228 { 229 // bind all parameters 230 foreach (StorageModificationFunctionParameterBinding parameterBinding in functionMapping.ParameterBindings) 231 { 232 PropagatorResult result; 233 234 // extract value 235 if (null != parameterBinding.MemberPath.AssociationSetEnd) 236 { 237 // find the relationship entry corresponding to the navigation 238 AssociationEndMember endMember = parameterBinding.MemberPath.AssociationSetEnd.CorrespondingAssociationEndMember; 239 IEntityStateEntry relationshipEntry; 240 bool hasTarget = parameterBinding.IsCurrent 241 ? currentReferenceEnds.TryGetValue(endMember, out relationshipEntry) 242 : originalReferenceEnds.TryGetValue(endMember, out relationshipEntry); 243 if (!hasTarget) 244 { 245 if (endMember.RelationshipMultiplicity == RelationshipMultiplicity.One) 246 { 247 string entitySetName = stateEntry.Source.EntitySet.Name; 248 string associationSetName = parameterBinding.MemberPath.AssociationSetEnd.ParentAssociationSet.Name; 249 throw EntityUtil.Update(Strings.Update_MissingRequiredRelationshipValue(entitySetName, associationSetName), 250 null, 251 command.GetStateEntries(translator)); 252 } 253 else 254 { 255 result = PropagatorResult.CreateSimpleValue(PropagatorFlags.NoFlags, null); 256 } 257 } 258 else 259 { 260 // get the actual value 261 PropagatorResult relationshipResult = parameterBinding.IsCurrent ? 262 translator.RecordConverter.ConvertCurrentValuesToPropagatorResult(relationshipEntry, ModifiedPropertiesBehavior.AllModified) : 263 translator.RecordConverter.ConvertOriginalValuesToPropagatorResult(relationshipEntry, ModifiedPropertiesBehavior.AllModified); 264 PropagatorResult endResult = relationshipResult.GetMemberValue(endMember); 265 EdmProperty keyProperty = (EdmProperty)parameterBinding.MemberPath.Members[0]; 266 result = endResult.GetMemberValue(keyProperty); 267 } 268 } 269 else 270 { 271 // walk through the member path to find the appropriate propagator results 272 result = parameterBinding.IsCurrent ? stateEntry.Current : stateEntry.Original; 273 for (int i = parameterBinding.MemberPath.Members.Count; i > 0;) 274 { 275 --i; 276 EdmMember member = parameterBinding.MemberPath.Members[i]; 277 result = result.GetMemberValue(member); 278 } 279 } 280 281 // create DbParameter 282 command.SetParameterValue(result, parameterBinding, translator); 283 } 284 // Add rows affected parameter 285 command.RegisterRowsAffectedParameter(functionMapping.RowsAffectedParameter); 286 } 287 } 288 289 private sealed class AssociationSetTranslator : ModificationFunctionMappingTranslator 290 { 291 // If this value is null, it indicates that the association set is 292 // only implicitly mapped as part of an entity set 293 private readonly StorageAssociationSetModificationFunctionMapping m_mapping; 294 AssociationSetTranslator(StorageAssociationSetMapping setMapping)295 internal AssociationSetTranslator(StorageAssociationSetMapping setMapping) 296 { 297 if (null != setMapping) 298 { 299 m_mapping = setMapping.ModificationFunctionMapping; 300 } 301 } 302 Translate( UpdateTranslator translator, ExtractedStateEntry stateEntry)303 internal override FunctionUpdateCommand Translate( 304 UpdateTranslator translator, 305 ExtractedStateEntry stateEntry) 306 { 307 if (null == m_mapping) { return null; } 308 309 bool isInsert = EntityState.Added == stateEntry.State; 310 311 EntityUtil.ValidateNecessaryModificationFunctionMapping( 312 isInsert ? m_mapping.InsertFunctionMapping : m_mapping.DeleteFunctionMapping, 313 isInsert ? "Insert" : "Delete", 314 stateEntry.Source, "AssociationSet", m_mapping.AssociationSet.Name); 315 316 // initialize a new command 317 StorageModificationFunctionMapping functionMapping = isInsert ? m_mapping.InsertFunctionMapping : m_mapping.DeleteFunctionMapping; 318 FunctionUpdateCommand command = new FunctionUpdateCommand(functionMapping, translator, new [] { stateEntry.Source }.ToList().AsReadOnly(), stateEntry); 319 320 // extract the relationship values from the state entry 321 PropagatorResult recordResult; 322 if (isInsert) 323 { 324 recordResult = stateEntry.Current; 325 } 326 else 327 { 328 recordResult = stateEntry.Original; 329 } 330 331 // bind parameters 332 foreach (StorageModificationFunctionParameterBinding parameterBinding in functionMapping.ParameterBindings) 333 { 334 // extract the relationship information 335 Debug.Assert(2 == parameterBinding.MemberPath.Members.Count, "relationship parameter binding member " + 336 "path should include the relationship end and key property only"); 337 338 EdmProperty keyProperty = (EdmProperty)parameterBinding.MemberPath.Members[0]; 339 AssociationEndMember endMember = (AssociationEndMember)parameterBinding.MemberPath.Members[1]; 340 341 // get the end member 342 PropagatorResult endResult = recordResult.GetMemberValue(endMember); 343 PropagatorResult keyResult = endResult.GetMemberValue(keyProperty); 344 345 command.SetParameterValue(keyResult, parameterBinding, translator); 346 } 347 // add rows affected output parameter 348 command.RegisterRowsAffectedParameter(functionMapping.RowsAffectedParameter); 349 350 return command; 351 } 352 } 353 } 354 } 355