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