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