1 //---------------------------------------------------------------------
2 // <copyright file="ExtractorMetadata.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.Data.Common;
13     using System.Data.Common.Utils;
14     using System.Data.Entity;
15     using System.Data.Metadata.Edm;
16     using System.Data.Objects;
17     using System.Diagnostics;
18     using System.Linq;
19 
20     internal enum ModifiedPropertiesBehavior
21     {
22         /// <summary>
23         /// Indicates that all properties are modified. Used for added and deleted entities and for
24         /// modified complex type sub-records.
25         /// </summary>
26         AllModified,
27         /// <summary>
28         /// Indicates that no properties are modified. Used for unmodified complex type sub-records.
29         /// </summary>
30         NoneModified,
31         /// <summary>
32         /// Indicates that some properties are modified. Used for modified entities.
33         /// </summary>
34         SomeModified,
35     }
36 
37     /// <summary>
38     /// Encapsulates metadata information relevant to update for records extracted from
39     /// the entity state manager, such as concurrency flags and key information.
40     /// </summary>
41     internal class ExtractorMetadata
42     {
ExtractorMetadata(EntitySetBase entitySetBase, StructuralType type, UpdateTranslator translator)43         internal ExtractorMetadata(EntitySetBase entitySetBase, StructuralType type, UpdateTranslator translator)
44         {
45             EntityUtil.CheckArgumentNull(entitySetBase, "entitySetBase");
46             m_type = EntityUtil.CheckArgumentNull(type, "type");
47             m_translator = EntityUtil.CheckArgumentNull(translator, "translator");
48 
49             EntityType entityType = null;
50             Set<EdmMember> keyMembers;
51             Set<EdmMember> foreignKeyMembers;
52 
53             switch (type.BuiltInTypeKind)
54             {
55                 case BuiltInTypeKind.RowType:
56                     // for row types (which are actually association end key records in disguise), all members
57                     // are keys
58                     keyMembers = new Set<EdmMember>(((RowType)type).Properties).MakeReadOnly();
59                     foreignKeyMembers = Set<EdmMember>.Empty;
60                     break;
61                 case BuiltInTypeKind.EntityType:
62                     entityType = (EntityType)type;
63                     keyMembers = new Set<EdmMember>(entityType.KeyMembers).MakeReadOnly();
64                     foreignKeyMembers = new Set<EdmMember>(((EntitySet)entitySetBase).ForeignKeyDependents
65                         .SelectMany(fk => fk.Item2.ToProperties)).MakeReadOnly();
66                     break;
67                 default:
68                     keyMembers = Set<EdmMember>.Empty;
69                     foreignKeyMembers = Set<EdmMember>.Empty;
70                     break;
71             }
72 
73             IBaseList<EdmMember> members = TypeHelpers.GetAllStructuralMembers(type);
74             m_memberMap = new MemberInformation[members.Count];
75             // for each member, cache expensive to compute metadata information
76             for (int ordinal = 0; ordinal < members.Count; ordinal++)
77             {
78                 EdmMember member = members[ordinal];
79                 // figure out flags for this member
80                 PropagatorFlags flags = PropagatorFlags.NoFlags;
81                 int? entityKeyOrdinal = default(int?);
82 
83                 if (keyMembers.Contains(member))
84                 {
85                     flags |= PropagatorFlags.Key;
86                     if (null != entityType)
87                     {
88                         entityKeyOrdinal = entityType.KeyMembers.IndexOf(member);
89                     }
90                 }
91                 if (foreignKeyMembers.Contains(member))
92                 {
93                     flags |= PropagatorFlags.ForeignKey;
94                 }
95 
96 
97                 if (MetadataHelper.GetConcurrencyMode(member) == ConcurrencyMode.Fixed)
98                 {
99                     flags |= PropagatorFlags.ConcurrencyValue;
100                 }
101 
102                 // figure out whether this member is mapped to any server generated
103                 // columns in the store
104                 bool isServerGenerated = m_translator.ViewLoader.IsServerGen(entitySetBase, m_translator.MetadataWorkspace, member);
105 
106                 // figure out whether member nullability is used as a condition in mapping
107                 bool isNullConditionMember = m_translator.ViewLoader.IsNullConditionMember(entitySetBase, m_translator.MetadataWorkspace, member);
108 
109                 // add information about this member
110                 m_memberMap[ordinal] = new MemberInformation(ordinal, entityKeyOrdinal, flags, member, isServerGenerated, isNullConditionMember);
111             }
112         }
113 
114         private readonly MemberInformation[] m_memberMap;
115         private readonly StructuralType m_type;
116         private readonly UpdateTranslator m_translator;
117 
118         /// <summary>
119         /// Requires: record must have correct type for this metadata instance.
120         /// Populates a new <see cref="PropagatorResult"/> object representing a member of a record matching the
121         /// type of this extractor. Given a record and a member, this method wraps the value of the member
122         /// in a PropagatorResult. This operation can be performed efficiently by this class, which knows
123         /// important stuff about the type being extracted.
124         /// </summary>
125         /// <param name="stateEntry">state manager entry containing value (used for error reporting)</param>
126         /// <param name="record">Record containing value (used to find the actual value)</param>
127         /// <param name="currentValues">Indicates whether we are reading current or original values.</param>
128         /// <param name="key">Entity key for the state entry. Must be set for entity records.</param>
129         /// <param name="ordinal">Ordinal of Member for which to retrieve a value.</param>
130         /// modified (must be ordinally aligned with the type). Null indicates all members are modified.</param>
131         /// <param name="modifiedPropertiesBehavior">Indicates how to determine whether a property is modified.</param>
132         /// <returns>Propagator result describing this member value.</returns>
RetrieveMember(IEntityStateEntry stateEntry, IExtendedDataRecord record, bool useCurrentValues, EntityKey key, int ordinal, ModifiedPropertiesBehavior modifiedPropertiesBehavior)133         internal PropagatorResult RetrieveMember(IEntityStateEntry stateEntry, IExtendedDataRecord record, bool useCurrentValues,
134             EntityKey key, int ordinal, ModifiedPropertiesBehavior modifiedPropertiesBehavior)
135         {
136             MemberInformation memberInformation = m_memberMap[ordinal];
137 
138             // get identifier value
139             int identifier;
140             if (memberInformation.IsKeyMember)
141             {
142                 // retrieve identifier for this key member
143                 Debug.Assert(null != (object)key, "entities must have keys, and only entity members are marked IsKeyMember by " +
144                     "the metadata wrapper");
145                 int keyOrdinal = memberInformation.EntityKeyOrdinal.Value;
146                 identifier = m_translator.KeyManager.GetKeyIdentifierForMemberOffset(key, keyOrdinal, ((EntityType)m_type).KeyMembers.Count);
147             }
148             else if (memberInformation.IsForeignKeyMember)
149             {
150                 identifier = m_translator.KeyManager.GetKeyIdentifierForMember(key, record.GetName(ordinal), useCurrentValues);
151             }
152             else
153             {
154                 identifier = PropagatorResult.NullIdentifier;
155             }
156 
157             // determine if the member is modified
158             bool isModified = modifiedPropertiesBehavior == ModifiedPropertiesBehavior.AllModified ||
159                 (modifiedPropertiesBehavior == ModifiedPropertiesBehavior.SomeModified &&
160                  stateEntry.ModifiedProperties != null &&
161                  stateEntry.ModifiedProperties[memberInformation.Ordinal]);
162 
163             // determine member value
164             Debug.Assert(record.GetName(ordinal) == memberInformation.Member.Name, "expect record to present properties in metadata order");
165             if (memberInformation.CheckIsNotNull && record.IsDBNull(ordinal))
166             {
167                 throw EntityUtil.Update(Strings.Update_NullValue(record.GetName(ordinal)), null, stateEntry);
168             }
169             object value = record.GetValue(ordinal);
170 
171             // determine what kind of member this is
172 
173             // entityKey (association end)
174             EntityKey entityKey = value as EntityKey;
175             if (null != (object)entityKey)
176             {
177                 return CreateEntityKeyResult(stateEntry, entityKey);
178             }
179 
180             // record (nested complex type)
181             IExtendedDataRecord nestedRecord = value as IExtendedDataRecord;
182             if (null != nestedRecord)
183             {
184                 // for structural types, we track whether the entire complex type value is modified or not
185                 var nestedModifiedPropertiesBehavior = isModified
186                     ? ModifiedPropertiesBehavior.AllModified
187                     : ModifiedPropertiesBehavior.NoneModified;
188                 UpdateTranslator translator = m_translator;
189 
190                 return ExtractResultFromRecord(stateEntry, isModified, nestedRecord, useCurrentValues, translator, nestedModifiedPropertiesBehavior);
191             }
192 
193             // simple value (column/property value)
194             return CreateSimpleResult(stateEntry, record, memberInformation, identifier, isModified, ordinal, value);
195         }
196 
197         // Note that this is called only for association ends. Entities have key values inline.
CreateEntityKeyResult(IEntityStateEntry stateEntry, EntityKey entityKey)198         private PropagatorResult CreateEntityKeyResult(IEntityStateEntry stateEntry, EntityKey entityKey)
199         {
200             // get metadata for key
201             EntityType entityType = entityKey.GetEntitySet(m_translator.MetadataWorkspace).ElementType;
202             RowType keyRowType = entityType.GetKeyRowType(m_translator.MetadataWorkspace);
203 
204             ExtractorMetadata keyMetadata = m_translator.GetExtractorMetadata(stateEntry.EntitySet, keyRowType);
205             int keyMemberCount = keyRowType.Properties.Count;
206             PropagatorResult[] keyValues = new PropagatorResult[keyMemberCount];
207 
208             for (int ordinal = 0; ordinal < keyRowType.Properties.Count; ordinal++)
209             {
210                 EdmMember keyMember = keyRowType.Properties[ordinal];
211                 // retrieve information about this key value
212                 MemberInformation keyMemberInformation = keyMetadata.m_memberMap[ordinal];
213 
214                 int keyIdentifier = m_translator.KeyManager.GetKeyIdentifierForMemberOffset(entityKey, ordinal, keyRowType.Properties.Count);
215 
216                 object keyValue = null;
217                 if (entityKey.IsTemporary)
218                 {
219                     // If the EntityKey is temporary, we need to retrieve the appropriate
220                     // key value from the entity itself (or in this case, the IEntityStateEntry).
221                     IEntityStateEntry entityEntry = stateEntry.StateManager.GetEntityStateEntry(entityKey);
222                     Debug.Assert(entityEntry.State == EntityState.Added,
223                         "The corresponding entry for a temp EntityKey should be in the Added State.");
224                     keyValue = entityEntry.CurrentValues[keyMember.Name];
225                 }
226                 else
227                 {
228                     // Otherwise, we extract the value from within the EntityKey.
229                     keyValue = entityKey.FindValueByName(keyMember.Name);
230                 }
231                 Debug.Assert(keyValue != null, "keyValue should've been retrieved.");
232 
233                 // construct propagator result
234                 keyValues[ordinal] = PropagatorResult.CreateKeyValue(
235                     keyMemberInformation.Flags,
236                     keyValue,
237                     stateEntry,
238                     keyIdentifier);
239 
240                 // see UpdateTranslator.Identifiers for information on key identifiers and ordinals
241             }
242 
243             return PropagatorResult.CreateStructuralValue(keyValues, keyMetadata.m_type, false);
244         }
245 
CreateSimpleResult(IEntityStateEntry stateEntry, IExtendedDataRecord record, MemberInformation memberInformation, int identifier, bool isModified, int recordOrdinal, object value)246         private PropagatorResult CreateSimpleResult(IEntityStateEntry stateEntry, IExtendedDataRecord record, MemberInformation memberInformation,
247             int identifier, bool isModified, int recordOrdinal, object value)
248         {
249             CurrentValueRecord updatableRecord = record as CurrentValueRecord;
250 
251             // construct flags for the value, which is needed for complex type and simple members
252             PropagatorFlags flags = memberInformation.Flags;
253             if (!isModified) { flags |= PropagatorFlags.Preserve; }
254             if (PropagatorResult.NullIdentifier != identifier)
255             {
256                 // construct a key member
257                 PropagatorResult result;
258                 if ((memberInformation.IsServerGenerated || memberInformation.IsForeignKeyMember) && null != updatableRecord)
259                 {
260                     result = PropagatorResult.CreateServerGenKeyValue(flags, value, stateEntry, identifier, recordOrdinal);
261                 }
262                 else
263                 {
264                     result = PropagatorResult.CreateKeyValue(flags, value, stateEntry, identifier);
265                 }
266 
267                 // we register the entity as the "owner" of an identity so that back-propagation can succeed
268                 // (keys can only be back-propagated to entities, not association ends). It also allows us
269                 // to walk to the entity state entry in case of exceptions, since the state entry propagated
270                 // through the stack may be eliminated in a project above a join.
271                 m_translator.KeyManager.RegisterIdentifierOwner(result);
272 
273                 return result;
274             }
275             else
276             {
277                 if ((memberInformation.IsServerGenerated || memberInformation.IsForeignKeyMember) && null != updatableRecord)
278                 {
279                     // note: we only produce a server gen result when
280                     return PropagatorResult.CreateServerGenSimpleValue(flags, value, updatableRecord, recordOrdinal);
281                 }
282                 else
283                 {
284                     return PropagatorResult.CreateSimpleValue(flags, value);
285                 }
286             }
287         }
288 
289         /// <summary>
290         /// Converts a record to a propagator result
291         /// </summary>
292         /// <param name="stateEntry">state manager entry containing the record</param>
293         /// <param name="isModified">Indicates whether the root element is modified (i.e., whether the type has changed)</param>
294         /// <param name="record">Record to convert</param>
295         /// <param name="useCurrentValues">Indicates whether we are retrieving current or original values.</param>
296         /// <param name="translator">Translator for session context; registers new metadata for the record type if none
297         /// exists</param>
298         /// <param name="modifiedPropertiesBehavior">Indicates how to determine whether a property is modified.</param>
299         /// <returns>Result corresponding to the given record</returns>
ExtractResultFromRecord(IEntityStateEntry stateEntry, bool isModified, IExtendedDataRecord record, bool useCurrentValues, UpdateTranslator translator, ModifiedPropertiesBehavior modifiedPropertiesBehavior)300         internal static PropagatorResult ExtractResultFromRecord(IEntityStateEntry stateEntry, bool isModified, IExtendedDataRecord record,
301             bool useCurrentValues, UpdateTranslator translator, ModifiedPropertiesBehavior modifiedPropertiesBehavior)
302         {
303             StructuralType structuralType = (StructuralType)record.DataRecordInfo.RecordType.EdmType;
304             ExtractorMetadata metadata = translator.GetExtractorMetadata(stateEntry.EntitySet, structuralType);
305             EntityKey key = stateEntry.EntityKey;
306 
307             PropagatorResult[] nestedValues = new PropagatorResult[record.FieldCount];
308             for (int ordinal = 0; ordinal < nestedValues.Length; ordinal++)
309             {
310                 nestedValues[ordinal] = metadata.RetrieveMember(stateEntry, record, useCurrentValues, key,
311                     ordinal, modifiedPropertiesBehavior);
312             }
313 
314             return PropagatorResult.CreateStructuralValue(nestedValues, structuralType, isModified);
315         }
316 
317         private class MemberInformation
318         {
319             /// <summary>
320             /// Gets ordinal of the member.
321             /// </summary>
322             internal readonly int Ordinal;
323 
324             /// <summary>
325             /// Gets key ordinal for primary key member (null if not a primary key).
326             /// </summary>
327             internal readonly int? EntityKeyOrdinal;
328 
329             /// <summary>
330             /// Gets propagator flags for the member, excluding the 'Preserve' flag
331             /// which can only be set in context.
332             /// </summary>
333             internal readonly PropagatorFlags Flags;
334 
335             /// <summary>
336             /// Indicates whether this is a key member.
337             /// </summary>
338             internal bool IsKeyMember
339             {
340                 get
341                 {
342                     return PropagatorFlags.Key == (Flags & PropagatorFlags.Key);
343                 }
344             }
345 
346             /// <summary>
347             /// Indicates whether this is a foreign key member.
348             /// </summary>
349             internal bool IsForeignKeyMember
350             {
351                 get
352                 {
353                     return PropagatorFlags.ForeignKey == (Flags & PropagatorFlags.ForeignKey);
354                 }
355             }
356 
357             /// <summary>
358             /// Indicates whether this value is server generated.
359             /// </summary>
360             internal readonly bool IsServerGenerated;
361 
362             /// <summary>
363             /// Indicates whether non-null values are supported for this member.
364             /// </summary>
365             internal readonly bool CheckIsNotNull;
366 
367             /// <summary>
368             /// Gets the member described by this wrapper.
369             /// </summary>
370             internal readonly EdmMember Member;
371 
MemberInformation(int ordinal, int? entityKeyOrdinal, PropagatorFlags flags, EdmMember member, bool isServerGenerated, bool isNullConditionMember)372             internal MemberInformation(int ordinal, int? entityKeyOrdinal, PropagatorFlags flags, EdmMember member, bool isServerGenerated, bool isNullConditionMember)
373             {
374                 Debug.Assert(entityKeyOrdinal.HasValue ==
375                     (member.DeclaringType.BuiltInTypeKind == BuiltInTypeKind.EntityType && (flags & PropagatorFlags.Key) == PropagatorFlags.Key),
376                     "key ordinal should only be provided if this is an entity key property");
377 
378                 this.Ordinal = ordinal;
379                 this.EntityKeyOrdinal = entityKeyOrdinal;
380                 this.Flags = flags;
381                 this.Member = member;
382                 this.IsServerGenerated = isServerGenerated;
383                 // in two cases, we must check that a member value is not null:
384                 // - where the type participates in an isnull condition, nullability constraints must be honored
385                 // - for complex types, mapping relies on nullability constraint
386                 // - in other cases, nullability does not impact round trippability so we don't check
387                 this.CheckIsNotNull = !TypeSemantics.IsNullable(member) &&
388                     (isNullConditionMember || member.TypeUsage.EdmType.BuiltInTypeKind == BuiltInTypeKind.ComplexType);
389             }
390         }
391     }
392 }
393