1 //--------------------------------------------------------------------- 2 // <copyright file="FunctionImportMapping.cs" company="Microsoft"> 3 // Copyright (c) Microsoft Corporation. All rights reserved. 4 // </copyright> 5 // 6 // @owner Microsoft 7 // @backupOwner willa 8 //--------------------------------------------------------------------- 9 10 namespace System.Data.Mapping 11 { 12 using System.Collections; 13 using System.Collections.Generic; 14 using System.Data.Common.Utils; 15 using System.Data.Common.Utils.Boolean; 16 using System.Data.Entity; 17 using System.Data.Metadata.Edm; 18 using System.Diagnostics; 19 using System.Globalization; 20 using System.Linq; 21 using System.Xml; 22 using System.Xml.XPath; 23 using OM = System.Collections.ObjectModel; 24 25 /// <summary> 26 /// Represents a mapping from a model function import to a store composable or non-composable function. 27 /// </summary> 28 internal abstract class FunctionImportMapping 29 { FunctionImportMapping(EdmFunction functionImport, EdmFunction targetFunction)30 internal FunctionImportMapping(EdmFunction functionImport, EdmFunction targetFunction) 31 { 32 this.FunctionImport = EntityUtil.CheckArgumentNull(functionImport, "functionImport"); 33 this.TargetFunction = EntityUtil.CheckArgumentNull(targetFunction, "targetFunction"); 34 } 35 36 /// <summary> 37 /// Gets model function (or source of the mapping) 38 /// </summary> 39 internal readonly EdmFunction FunctionImport; 40 41 /// <summary> 42 /// Gets store function (or target of the mapping) 43 /// </summary> 44 internal readonly EdmFunction TargetFunction; 45 } 46 47 internal sealed class FunctionImportStructuralTypeMappingKB 48 { FunctionImportStructuralTypeMappingKB( IEnumerable<FunctionImportStructuralTypeMapping> structuralTypeMappings, ItemCollection itemCollection)49 internal FunctionImportStructuralTypeMappingKB( 50 IEnumerable<FunctionImportStructuralTypeMapping> structuralTypeMappings, 51 ItemCollection itemCollection) 52 { 53 EntityUtil.CheckArgumentNull(structuralTypeMappings, "structuralTypeMappings"); 54 m_itemCollection = EntityUtil.CheckArgumentNull(itemCollection, "itemCollection"); 55 56 // If no specific type mapping. 57 if (structuralTypeMappings.Count() == 0) 58 { 59 // Initialize with defaults. 60 this.ReturnTypeColumnsRenameMapping = new Dictionary<string, FunctionImportReturnTypeStructuralTypeColumnRenameMapping>(); 61 this.NormalizedEntityTypeMappings = new OM.ReadOnlyCollection<FunctionImportNormalizedEntityTypeMapping>(new List<FunctionImportNormalizedEntityTypeMapping>()); 62 this.DiscriminatorColumns = new OM.ReadOnlyCollection<string>(new List<string>()); 63 this.MappedEntityTypes = new OM.ReadOnlyCollection<EntityType>(new List<EntityType>()); 64 return; 65 } 66 67 IEnumerable<FunctionImportEntityTypeMapping> entityTypeMappings = structuralTypeMappings.OfType<FunctionImportEntityTypeMapping>(); 68 69 // FunctionImportEntityTypeMapping 70 if (null != entityTypeMappings && null != entityTypeMappings.FirstOrDefault<FunctionImportEntityTypeMapping>()) 71 { 72 var isOfTypeEntityTypeColumnsRenameMapping = new Dictionary<EntityType, OM.Collection<FunctionImportReturnTypePropertyMapping>>(); 73 var entityTypeColumnsRenameMapping = new Dictionary<EntityType, OM.Collection<FunctionImportReturnTypePropertyMapping>>(); 74 var normalizedEntityTypeMappings = new List<FunctionImportNormalizedEntityTypeMapping>(); 75 76 // Collect all mapped entity types. 77 this.MappedEntityTypes = entityTypeMappings 78 .SelectMany(mapping => mapping.GetMappedEntityTypes(m_itemCollection)) 79 .Distinct() 80 .ToList() 81 .AsReadOnly(); 82 83 // Collect all discriminator columns. 84 this.DiscriminatorColumns = entityTypeMappings 85 .SelectMany(mapping => mapping.GetDiscriminatorColumns()) 86 .Distinct() 87 .ToList() 88 .AsReadOnly(); 89 90 m_entityTypeLineInfos = new KeyToListMap<EntityType, LineInfo>(EqualityComparer<EntityType>.Default); 91 m_isTypeOfLineInfos = new KeyToListMap<EntityType, LineInfo>(EqualityComparer<EntityType>.Default); 92 93 foreach (var entityTypeMapping in entityTypeMappings) 94 { 95 // Remember LineInfos for error reporting. 96 foreach (var entityType in entityTypeMapping.EntityTypes) 97 { 98 m_entityTypeLineInfos.Add(entityType, entityTypeMapping.LineInfo); 99 } 100 foreach (var isTypeOf in entityTypeMapping.IsOfTypeEntityTypes) 101 { 102 m_isTypeOfLineInfos.Add(isTypeOf, entityTypeMapping.LineInfo); 103 } 104 105 // Create map from column name to condition. 106 var columnMap = entityTypeMapping.Conditions.ToDictionary( 107 condition => condition.ColumnName, 108 condition => condition); 109 110 // Align conditions with discriminator columns. 111 var columnMappings = new List<FunctionImportEntityTypeMappingCondition>(this.DiscriminatorColumns.Count); 112 for (int i = 0; i < this.DiscriminatorColumns.Count; i++) 113 { 114 string discriminatorColumn = this.DiscriminatorColumns[i]; 115 FunctionImportEntityTypeMappingCondition mappingCondition; 116 if (columnMap.TryGetValue(discriminatorColumn, out mappingCondition)) 117 { 118 columnMappings.Add(mappingCondition); 119 } 120 else 121 { 122 // Null indicates the value for this discriminator doesn't matter. 123 columnMappings.Add(null); 124 } 125 } 126 127 // Create bit map for implied entity types. 128 bool[] impliedEntityTypesBitMap = new bool[this.MappedEntityTypes.Count]; 129 var impliedEntityTypesSet = new Set<EntityType>(entityTypeMapping.GetMappedEntityTypes(m_itemCollection)); 130 for (int i = 0; i < this.MappedEntityTypes.Count; i++) 131 { 132 impliedEntityTypesBitMap[i] = impliedEntityTypesSet.Contains(this.MappedEntityTypes[i]); 133 } 134 135 // Construct normalized mapping. 136 normalizedEntityTypeMappings.Add(new FunctionImportNormalizedEntityTypeMapping(this, columnMappings, new BitArray(impliedEntityTypesBitMap))); 137 138 // Construct the rename mappings by adding isTypeOf types and specific entity types to the corresponding lists. 139 foreach (var isOfType in entityTypeMapping.IsOfTypeEntityTypes) 140 { 141 if (!isOfTypeEntityTypeColumnsRenameMapping.Keys.Contains(isOfType)) 142 { 143 isOfTypeEntityTypeColumnsRenameMapping.Add(isOfType, new OM.Collection<FunctionImportReturnTypePropertyMapping>()); 144 } 145 foreach (var rename in entityTypeMapping.ColumnsRenameList) 146 { 147 isOfTypeEntityTypeColumnsRenameMapping[isOfType].Add(rename); 148 } 149 } 150 foreach (var entityType in entityTypeMapping.EntityTypes) 151 { 152 if (!entityTypeColumnsRenameMapping.Keys.Contains(entityType)) 153 { 154 entityTypeColumnsRenameMapping.Add(entityType, new OM.Collection<FunctionImportReturnTypePropertyMapping>()); 155 } 156 foreach (var rename in entityTypeMapping.ColumnsRenameList) 157 { 158 entityTypeColumnsRenameMapping[entityType].Add(rename); 159 } 160 } 161 } 162 163 this.ReturnTypeColumnsRenameMapping = new FunctionImportReturnTypeEntityTypeColumnsRenameBuilder(isOfTypeEntityTypeColumnsRenameMapping, 164 entityTypeColumnsRenameMapping) 165 .ColumnRenameMapping; 166 167 this.NormalizedEntityTypeMappings = new OM.ReadOnlyCollection<FunctionImportNormalizedEntityTypeMapping>( 168 normalizedEntityTypeMappings); 169 } 170 else 171 { 172 // FunctionImportComplexTypeMapping 173 Debug.Assert(structuralTypeMappings.First() is FunctionImportComplexTypeMapping, "only two types can have renames, complexType and entityType"); 174 IEnumerable<FunctionImportComplexTypeMapping> complexTypeMappings = structuralTypeMappings.Cast<FunctionImportComplexTypeMapping>(); 175 176 Debug.Assert(complexTypeMappings.Count() == 1, "how come there are more than 1, complex type cannot derive from other complex type"); 177 178 this.ReturnTypeColumnsRenameMapping = new Dictionary<string, FunctionImportReturnTypeStructuralTypeColumnRenameMapping>(); 179 foreach (var rename in complexTypeMappings.First().ColumnsRenameList) 180 { 181 FunctionImportReturnTypeStructuralTypeColumnRenameMapping columnRenameMapping = new FunctionImportReturnTypeStructuralTypeColumnRenameMapping(rename.CMember); 182 columnRenameMapping.AddRename(new FunctionImportReturnTypeStructuralTypeColumn( 183 rename.SColumn, 184 complexTypeMappings.First().ReturnType, 185 false, 186 rename.LineInfo)); 187 this.ReturnTypeColumnsRenameMapping.Add(rename.CMember, columnRenameMapping); 188 } 189 190 // Initialize the entity mapping data as empty. 191 this.NormalizedEntityTypeMappings = new OM.ReadOnlyCollection<FunctionImportNormalizedEntityTypeMapping>(new List<FunctionImportNormalizedEntityTypeMapping>()); 192 this.DiscriminatorColumns = new OM.ReadOnlyCollection<string>(new List<string>() { }); 193 this.MappedEntityTypes = new OM.ReadOnlyCollection<EntityType>(new List<EntityType>() { }); 194 } 195 } 196 197 private readonly ItemCollection m_itemCollection; 198 private readonly KeyToListMap<EntityType, LineInfo> m_entityTypeLineInfos; 199 private readonly KeyToListMap<EntityType, LineInfo> m_isTypeOfLineInfos; 200 201 /// <summary> 202 /// Gets all types in scope for this mapping. 203 /// </summary> 204 internal readonly OM.ReadOnlyCollection<EntityType> MappedEntityTypes; 205 206 /// <summary> 207 /// Gets a list of all discriminator columns used in this mapping. 208 /// </summary> 209 internal readonly OM.ReadOnlyCollection<string> DiscriminatorColumns; 210 211 /// <summary> 212 /// Gets normalized representation of all EntityTypeMapping fragments for this 213 /// function import mapping. 214 /// </summary> 215 internal readonly OM.ReadOnlyCollection<FunctionImportNormalizedEntityTypeMapping> NormalizedEntityTypeMappings; 216 217 /// <summary> 218 /// Get the columns rename mapping for return type, the first string is the member name 219 /// the second one is column names for different types that mentioned in the mapping. 220 /// </summary> 221 internal readonly Dictionary<string, FunctionImportReturnTypeStructuralTypeColumnRenameMapping> ReturnTypeColumnsRenameMapping; 222 ValidateTypeConditions(bool validateAmbiguity, IList<EdmSchemaError> errors, string sourceLocation)223 internal bool ValidateTypeConditions(bool validateAmbiguity, IList<EdmSchemaError> errors, string sourceLocation) 224 { 225 // Verify that all types can be produced 226 KeyToListMap<EntityType, LineInfo> unreachableEntityTypes; 227 KeyToListMap<EntityType, LineInfo> unreachableIsTypeOfs; 228 GetUnreachableTypes(validateAmbiguity, out unreachableEntityTypes, out unreachableIsTypeOfs); 229 230 bool valid = true; 231 foreach (var unreachableEntityType in unreachableEntityTypes.KeyValuePairs) 232 { 233 var lineInfo = unreachableEntityType.Value.First(); 234 string lines = StringUtil.ToCommaSeparatedString(unreachableEntityType.Value.Select(li => li.LineNumber)); 235 EdmSchemaError error = new EdmSchemaError( 236 Strings.Mapping_FunctionImport_UnreachableType(unreachableEntityType.Key.FullName, lines), 237 (int)StorageMappingErrorCode.MappingFunctionImportAmbiguousTypeConditions, 238 EdmSchemaErrorSeverity.Error, 239 sourceLocation, 240 lineInfo.LineNumber, 241 lineInfo.LinePosition); 242 errors.Add(error); 243 valid = false; 244 } 245 foreach (var unreachableIsTypeOf in unreachableIsTypeOfs.KeyValuePairs) 246 { 247 var lineInfo = unreachableIsTypeOf.Value.First(); 248 string lines = StringUtil.ToCommaSeparatedString(unreachableIsTypeOf.Value.Select(li => li.LineNumber)); 249 string isTypeOfDescription = StorageMslConstructs.IsTypeOf + unreachableIsTypeOf.Key.FullName + StorageMslConstructs.IsTypeOfTerminal; 250 EdmSchemaError error = new EdmSchemaError( 251 Strings.Mapping_FunctionImport_UnreachableIsTypeOf(isTypeOfDescription, lines), 252 (int)StorageMappingErrorCode.MappingFunctionImportAmbiguousTypeConditions, 253 EdmSchemaErrorSeverity.Error, 254 sourceLocation, 255 lineInfo.LineNumber, 256 lineInfo.LinePosition); 257 errors.Add(error); 258 valid = false; 259 } 260 261 return valid; 262 } 263 264 /// <summary> 265 /// Determines which explicitly mapped types in the function import mapping cannot be generated. 266 /// For IsTypeOf declarations, reports if no type in hierarchy can be produced. 267 /// 268 /// Works by: 269 /// 270 /// - Converting type mapping conditions into vertices 271 /// - Checking that some assignment satisfies 272 /// </summary> GetUnreachableTypes( bool validateAmbiguity, out KeyToListMap<EntityType, LineInfo> unreachableEntityTypes, out KeyToListMap<EntityType, LineInfo> unreachableIsTypeOfs)273 private void GetUnreachableTypes( 274 bool validateAmbiguity, 275 out KeyToListMap<EntityType, LineInfo> unreachableEntityTypes, 276 out KeyToListMap<EntityType, LineInfo> unreachableIsTypeOfs) 277 { 278 // Contains, for each DiscriminatorColumn, a domain variable where the domain values are 279 // integers representing the ordinal within discriminatorDomains. 280 DomainVariable<string, ValueCondition>[] variables = ConstructDomainVariables(); 281 282 // Convert type mapping conditions to decision diagram vertices. 283 var converter = new DomainConstraintConversionContext<string, ValueCondition>(); 284 Vertex[] mappingConditions = ConvertMappingConditionsToVertices(converter, variables); 285 286 // Find reachable types. 287 Set<EntityType> reachableTypes = validateAmbiguity ? 288 FindUnambiguouslyReachableTypes(converter, mappingConditions) : 289 FindReachableTypes(converter, mappingConditions); 290 291 CollectUnreachableTypes(reachableTypes, out unreachableEntityTypes, out unreachableIsTypeOfs); 292 } 293 ConstructDomainVariables()294 private DomainVariable<string, ValueCondition>[] ConstructDomainVariables() 295 { 296 // Determine domain for each discriminator column, including "other" and "null" placeholders. 297 var discriminatorDomains = new Set<ValueCondition>[this.DiscriminatorColumns.Count]; 298 for (int i = 0; i < discriminatorDomains.Length; i++) 299 { 300 discriminatorDomains[i] = new Set<ValueCondition>(); 301 discriminatorDomains[i].Add(ValueCondition.IsOther); 302 discriminatorDomains[i].Add(ValueCondition.IsNull); 303 } 304 305 // Collect all domain values. 306 foreach (var typeMapping in this.NormalizedEntityTypeMappings) 307 { 308 for (int i = 0; i < this.DiscriminatorColumns.Count; i++) 309 { 310 var discriminatorValue = typeMapping.ColumnConditions[i]; 311 if (null != discriminatorValue && 312 !discriminatorValue.ConditionValue.IsNotNullCondition) // NotNull is a special range (everything but IsNull) 313 { 314 discriminatorDomains[i].Add(discriminatorValue.ConditionValue); 315 } 316 } 317 } 318 319 var discriminatorVariables = new DomainVariable<string, ValueCondition>[discriminatorDomains.Length]; 320 for (int i = 0; i < discriminatorVariables.Length; i++) 321 { 322 // domain variable is identified by the column name and takes all collected domain values 323 discriminatorVariables[i] = new DomainVariable<string, ValueCondition>( 324 this.DiscriminatorColumns[i], discriminatorDomains[i].MakeReadOnly()); 325 } 326 327 return discriminatorVariables; 328 } 329 ConvertMappingConditionsToVertices( ConversionContext<DomainConstraint<string, ValueCondition>> converter, DomainVariable<string, ValueCondition>[] variables)330 private Vertex[] ConvertMappingConditionsToVertices( 331 ConversionContext<DomainConstraint<string, ValueCondition>> converter, 332 DomainVariable<string, ValueCondition>[] variables) 333 { 334 Vertex[] conditions = new Vertex[this.NormalizedEntityTypeMappings.Count]; 335 for (int i = 0; i < conditions.Length; i++) 336 { 337 var typeMapping = this.NormalizedEntityTypeMappings[i]; 338 339 // create conjunction representing the condition 340 Vertex condition = Vertex.One; 341 for (int j = 0; j < this.DiscriminatorColumns.Count; j++) 342 { 343 var columnCondition = typeMapping.ColumnConditions[j]; 344 if (null != columnCondition) 345 { 346 var conditionValue = columnCondition.ConditionValue; 347 if (conditionValue.IsNotNullCondition) 348 { 349 // the 'not null' condition is not actually part of the domain (since it 350 // covers other elements), so create a Not(value in {null}) condition 351 var isNull = new TermExpr<DomainConstraint<string, ValueCondition>>( 352 new DomainConstraint<string, ValueCondition>(variables[j], ValueCondition.IsNull)); 353 Vertex isNullVertex = converter.TranslateTermToVertex(isNull); 354 condition = converter.Solver.And(condition, converter.Solver.Not(isNullVertex)); 355 } 356 else 357 { 358 var hasValue = new TermExpr<DomainConstraint<string, ValueCondition>>( 359 new DomainConstraint<string, ValueCondition>(variables[j], conditionValue)); 360 condition = converter.Solver.And(condition, converter.TranslateTermToVertex(hasValue)); 361 } 362 } 363 } 364 conditions[i] = condition; 365 } 366 return conditions; 367 } 368 369 /// <summary> 370 /// Determines which types are produced by this mapping. 371 /// </summary> FindReachableTypes(DomainConstraintConversionContext<string, ValueCondition> converter, Vertex[] mappingConditions)372 private Set<EntityType> FindReachableTypes(DomainConstraintConversionContext<string, ValueCondition> converter, Vertex[] mappingConditions) 373 { 374 // For each entity type, create a candidate function that evaluates to true given 375 // discriminator assignments iff. all of that type's conditions evaluate to true 376 // and its negative conditions evaluate to false. 377 Vertex[] candidateFunctions = new Vertex[this.MappedEntityTypes.Count]; 378 for (int i = 0; i < candidateFunctions.Length; i++) 379 { 380 // Seed the candidate function conjunction with 'true'. 381 Vertex candidateFunction = Vertex.One; 382 for (int j = 0; j < this.NormalizedEntityTypeMappings.Count; j++) 383 { 384 var entityTypeMapping = this.NormalizedEntityTypeMappings[j]; 385 386 // Determine if this mapping is a positive or negative case for the current type. 387 if (entityTypeMapping.ImpliedEntityTypes[i]) 388 { 389 candidateFunction = converter.Solver.And(candidateFunction, mappingConditions[j]); 390 } 391 else 392 { 393 candidateFunction = converter.Solver.And(candidateFunction, converter.Solver.Not(mappingConditions[j])); 394 } 395 } 396 candidateFunctions[i] = candidateFunction; 397 } 398 399 // Make sure that for each type there is an assignment that resolves to only that type. 400 var reachableTypes = new Set<EntityType>(); 401 for (int i = 0; i < candidateFunctions.Length; i++) 402 { 403 // Create a function that evaluates to true iff. the current candidate function is true 404 // and every other candidate function is false. 405 Vertex isExactlyThisTypeCondition = converter.Solver.And( 406 candidateFunctions.Select((typeCondition, ordinal) => ordinal == i ? 407 typeCondition : 408 converter.Solver.Not(typeCondition))); 409 410 // If the above conjunction is satisfiable, it means some row configuration exists producing the type. 411 if (!isExactlyThisTypeCondition.IsZero()) 412 { 413 reachableTypes.Add(this.MappedEntityTypes[i]); 414 } 415 } 416 417 return reachableTypes; 418 } 419 420 /// <summary> 421 /// Determines which types are produced by this mapping. 422 /// </summary> FindUnambiguouslyReachableTypes(DomainConstraintConversionContext<string, ValueCondition> converter, Vertex[] mappingConditions)423 private Set<EntityType> FindUnambiguouslyReachableTypes(DomainConstraintConversionContext<string, ValueCondition> converter, Vertex[] mappingConditions) 424 { 425 // For each entity type, create a candidate function that evaluates to true given 426 // discriminator assignments iff. all of that type's conditions evaluate to true. 427 Vertex[] candidateFunctions = new Vertex[this.MappedEntityTypes.Count]; 428 for (int i = 0; i < candidateFunctions.Length; i++) 429 { 430 // Seed the candidate function conjunction with 'true'. 431 Vertex candidateFunction = Vertex.One; 432 for (int j = 0; j < this.NormalizedEntityTypeMappings.Count; j++) 433 { 434 var entityTypeMapping = this.NormalizedEntityTypeMappings[j]; 435 436 // Determine if this mapping is a positive or negative case for the current type. 437 if (entityTypeMapping.ImpliedEntityTypes[i]) 438 { 439 candidateFunction = converter.Solver.And(candidateFunction, mappingConditions[j]); 440 } 441 } 442 candidateFunctions[i] = candidateFunction; 443 } 444 445 // Make sure that for each type with satisfiable candidateFunction all assignments for the type resolve to only that type. 446 var unambigouslyReachableMap = new BitArray(candidateFunctions.Length, true); 447 for (int i = 0; i < candidateFunctions.Length; ++i) 448 { 449 if (candidateFunctions[i].IsZero()) 450 { 451 // The i-th type is unreachable regardless of other types. 452 unambigouslyReachableMap[i] = false; 453 } 454 else 455 { 456 for (int j = i + 1; j < candidateFunctions.Length; ++j) 457 { 458 if (!converter.Solver.And(candidateFunctions[i], candidateFunctions[j]).IsZero()) 459 { 460 // The i-th and j-th types have common assignments, hence they aren't unambiguously reachable. 461 unambigouslyReachableMap[i] = false; 462 unambigouslyReachableMap[j] = false; 463 } 464 } 465 } 466 } 467 var reachableTypes = new Set<EntityType>(); 468 for (int i = 0; i < candidateFunctions.Length; ++i) 469 { 470 if (unambigouslyReachableMap[i]) 471 { 472 reachableTypes.Add(this.MappedEntityTypes[i]); 473 } 474 } 475 476 return reachableTypes; 477 } 478 CollectUnreachableTypes(Set<EntityType> reachableTypes, out KeyToListMap<EntityType, LineInfo> entityTypes, out KeyToListMap<EntityType, LineInfo> isTypeOfEntityTypes)479 private void CollectUnreachableTypes(Set<EntityType> reachableTypes, out KeyToListMap<EntityType, LineInfo> entityTypes, out KeyToListMap<EntityType, LineInfo> isTypeOfEntityTypes) 480 { 481 // Collect line infos for types in violation 482 entityTypes = new KeyToListMap<EntityType, LineInfo>(EqualityComparer<EntityType>.Default); 483 isTypeOfEntityTypes = new KeyToListMap<EntityType, LineInfo>(EqualityComparer<EntityType>.Default); 484 485 if (reachableTypes.Count == this.MappedEntityTypes.Count) 486 { 487 // All types are reachable; nothing to check 488 return; 489 } 490 491 // Find IsTypeOf mappings where no type in hierarchy can generate a row 492 foreach (var isTypeOf in m_isTypeOfLineInfos.Keys) 493 { 494 if (!MetadataHelper.GetTypeAndSubtypesOf(isTypeOf, m_itemCollection, false) 495 .Cast<EntityType>() 496 .Intersect(reachableTypes) 497 .Any()) 498 { 499 // no type in the hierarchy is reachable... 500 isTypeOfEntityTypes.AddRange(isTypeOf, m_isTypeOfLineInfos.EnumerateValues(isTypeOf)); 501 } 502 } 503 504 // Find explicit types not generating a value 505 foreach (var entityType in m_entityTypeLineInfos.Keys) 506 { 507 if (!reachableTypes.Contains(entityType)) 508 { 509 entityTypes.AddRange(entityType, m_entityTypeLineInfos.EnumerateValues(entityType)); 510 } 511 } 512 } 513 } 514 515 internal sealed class FunctionImportNormalizedEntityTypeMapping 516 { FunctionImportNormalizedEntityTypeMapping(FunctionImportStructuralTypeMappingKB parent, List<FunctionImportEntityTypeMappingCondition> columnConditions, BitArray impliedEntityTypes)517 internal FunctionImportNormalizedEntityTypeMapping(FunctionImportStructuralTypeMappingKB parent, 518 List<FunctionImportEntityTypeMappingCondition> columnConditions, BitArray impliedEntityTypes) 519 { 520 // validate arguments 521 EntityUtil.CheckArgumentNull(parent, "parent"); 522 EntityUtil.CheckArgumentNull(columnConditions, "discriminatorValues"); 523 EntityUtil.CheckArgumentNull(impliedEntityTypes, "impliedEntityTypes"); 524 525 Debug.Assert(columnConditions.Count == parent.DiscriminatorColumns.Count, 526 "discriminator values must be ordinally aligned with discriminator columns"); 527 Debug.Assert(impliedEntityTypes.Count == parent.MappedEntityTypes.Count, 528 "implied entity types must be ordinally aligned with mapped entity types"); 529 530 this.ColumnConditions = new OM.ReadOnlyCollection<FunctionImportEntityTypeMappingCondition>(columnConditions.ToList()); 531 this.ImpliedEntityTypes = impliedEntityTypes; 532 this.ComplementImpliedEntityTypes = (new BitArray(this.ImpliedEntityTypes)).Not(); 533 } 534 535 /// <summary> 536 /// Gets discriminator values aligned with DiscriminatorColumns of the parent FunctionImportMapping. 537 /// A null ValueCondition indicates 'anything goes'. 538 /// </summary> 539 internal readonly OM.ReadOnlyCollection<FunctionImportEntityTypeMappingCondition> ColumnConditions; 540 541 /// <summary> 542 /// Gets bit array with 'true' indicating the corresponding MappedEntityType of the parent 543 /// FunctionImportMapping is implied by this fragment. 544 /// </summary> 545 internal readonly BitArray ImpliedEntityTypes; 546 547 /// <summary> 548 /// Gets the complement of the ImpliedEntityTypes BitArray. 549 /// </summary> 550 internal readonly BitArray ComplementImpliedEntityTypes; 551 ToString()552 public override string ToString() 553 { 554 return String.Format(CultureInfo.InvariantCulture, "Values={0}, Types={1}", 555 StringUtil.ToCommaSeparatedString(this.ColumnConditions), StringUtil.ToCommaSeparatedString(this.ImpliedEntityTypes)); 556 } 557 } 558 559 internal abstract class FunctionImportEntityTypeMappingCondition 560 { FunctionImportEntityTypeMappingCondition(string columnName, LineInfo lineInfo)561 protected FunctionImportEntityTypeMappingCondition(string columnName, LineInfo lineInfo) 562 { 563 this.ColumnName = EntityUtil.CheckArgumentNull(columnName, "columnName"); 564 this.LineInfo = lineInfo; 565 } 566 567 internal readonly string ColumnName; 568 internal readonly LineInfo LineInfo; 569 570 internal abstract ValueCondition ConditionValue { get; } 571 ColumnValueMatchesCondition(object columnValue)572 internal abstract bool ColumnValueMatchesCondition(object columnValue); 573 ToString()574 public override string ToString() 575 { 576 return this.ConditionValue.ToString(); 577 } 578 } 579 580 internal sealed class FunctionImportEntityTypeMappingConditionValue : FunctionImportEntityTypeMappingCondition 581 { FunctionImportEntityTypeMappingConditionValue(string columnName, XPathNavigator columnValue, LineInfo lineInfo)582 internal FunctionImportEntityTypeMappingConditionValue(string columnName, XPathNavigator columnValue, LineInfo lineInfo) 583 : base(columnName, lineInfo) 584 { 585 this._xPathValue = EntityUtil.CheckArgumentNull(columnValue, "columnValue"); 586 this._convertedValues = new Memoizer<Type, object>(this.GetConditionValue, null); 587 } 588 589 private readonly XPathNavigator _xPathValue; 590 private readonly Memoizer<Type, object> _convertedValues; 591 592 internal override ValueCondition ConditionValue 593 { 594 get { return new ValueCondition(_xPathValue.Value); } 595 } 596 ColumnValueMatchesCondition(object columnValue)597 internal override bool ColumnValueMatchesCondition(object columnValue) 598 { 599 if (null == columnValue || Convert.IsDBNull(columnValue)) 600 { 601 // only FunctionImportEntityTypeMappingConditionIsNull can match a null 602 // column value 603 return false; 604 } 605 606 Type columnValueType = columnValue.GetType(); 607 608 // check if we've interpreted this column type yet 609 object conditionValue = _convertedValues.Evaluate(columnValueType); 610 return ByValueEqualityComparer.Default.Equals(columnValue, conditionValue); 611 } 612 GetConditionValue(Type columnValueType)613 private object GetConditionValue(Type columnValueType) 614 { 615 return GetConditionValue( 616 columnValueType, 617 handleTypeNotComparable: () => 618 { 619 throw EntityUtil.CommandExecution(Strings.Mapping_FunctionImport_UnsupportedType(this.ColumnName, columnValueType.FullName)); 620 }, 621 handleInvalidConditionValue: () => 622 { 623 throw EntityUtil.CommandExecution(Strings.Mapping_FunctionImport_ConditionValueTypeMismatch(StorageMslConstructs.FunctionImportMappingElement, this.ColumnName, columnValueType.FullName)); 624 }); 625 } 626 GetConditionValue(Type columnValueType, Action handleTypeNotComparable, Action handleInvalidConditionValue)627 internal object GetConditionValue(Type columnValueType, Action handleTypeNotComparable, Action handleInvalidConditionValue) 628 { 629 // Check that the type is supported and comparable. 630 PrimitiveType primitiveType; 631 if (!ClrProviderManifest.Instance.TryGetPrimitiveType(columnValueType, out primitiveType) || 632 !StorageMappingItemLoader.IsTypeSupportedForCondition(primitiveType.PrimitiveTypeKind)) 633 { 634 handleTypeNotComparable(); 635 return null; 636 } 637 638 try 639 { 640 return _xPathValue.ValueAs(columnValueType); 641 } 642 catch (FormatException) 643 { 644 handleInvalidConditionValue(); 645 return null; 646 } 647 } 648 } 649 650 internal sealed class FunctionImportEntityTypeMappingConditionIsNull : FunctionImportEntityTypeMappingCondition 651 { FunctionImportEntityTypeMappingConditionIsNull(string columnName, bool isNull, LineInfo lineInfo)652 internal FunctionImportEntityTypeMappingConditionIsNull(string columnName, bool isNull, LineInfo lineInfo) 653 : base(columnName, lineInfo) 654 { 655 this.IsNull = isNull; 656 } 657 658 internal readonly bool IsNull; 659 660 internal override ValueCondition ConditionValue 661 { 662 get { return IsNull ? ValueCondition.IsNull : ValueCondition.IsNotNull; } 663 } 664 ColumnValueMatchesCondition(object columnValue)665 internal override bool ColumnValueMatchesCondition(object columnValue) 666 { 667 bool valueIsNull = null == columnValue || Convert.IsDBNull(columnValue); 668 return valueIsNull == this.IsNull; 669 } 670 } 671 672 /// <summary> 673 /// Represents a simple value condition of the form (value IS NULL), (value IS NOT NULL) 674 /// or (value EQ X). Supports IEquatable(Of ValueCondition) so that equivalent conditions 675 /// can be identified. 676 /// </summary> 677 internal class ValueCondition : IEquatable<ValueCondition> 678 { 679 internal readonly string Description; 680 internal readonly bool IsSentinel; 681 682 internal const string IsNullDescription = "NULL"; 683 internal const string IsNotNullDescription = "NOT NULL"; 684 internal const string IsOtherDescription = "OTHER"; 685 686 internal readonly static ValueCondition IsNull = new ValueCondition(IsNullDescription, true); 687 internal readonly static ValueCondition IsNotNull = new ValueCondition(IsNotNullDescription, true); 688 internal readonly static ValueCondition IsOther = new ValueCondition(IsOtherDescription, true); 689 ValueCondition(string description, bool isSentinel)690 private ValueCondition(string description, bool isSentinel) 691 { 692 Description = description; 693 IsSentinel = isSentinel; 694 } 695 ValueCondition(string description)696 internal ValueCondition(string description) 697 : this(description, false) 698 { 699 } 700 701 internal bool IsNotNullCondition { get { return object.ReferenceEquals(this, IsNotNull); } } 702 Equals(ValueCondition other)703 public bool Equals(ValueCondition other) 704 { 705 return other.IsSentinel == this.IsSentinel && 706 other.Description == this.Description; 707 } 708 GetHashCode()709 public override int GetHashCode() 710 { 711 return Description.GetHashCode(); 712 } 713 ToString()714 public override string ToString() 715 { 716 return this.Description; 717 } 718 } 719 720 internal sealed class LineInfo: IXmlLineInfo 721 { 722 private readonly bool m_hasLineInfo; 723 private readonly int m_lineNumber; 724 private readonly int m_linePosition; 725 LineInfo(XPathNavigator nav)726 internal LineInfo(XPathNavigator nav) 727 : this((IXmlLineInfo)nav) 728 { } 729 LineInfo(IXmlLineInfo lineInfo)730 internal LineInfo(IXmlLineInfo lineInfo) 731 { 732 m_hasLineInfo = lineInfo.HasLineInfo(); 733 m_lineNumber = lineInfo.LineNumber; 734 m_linePosition = lineInfo.LinePosition; 735 } 736 737 internal static readonly LineInfo Empty = new LineInfo(); LineInfo()738 private LineInfo() 739 { 740 m_hasLineInfo = false; 741 m_lineNumber = default(int); 742 m_linePosition = default(int); 743 } 744 745 public int LineNumber 746 { get { return m_lineNumber; } } 747 748 public int LinePosition 749 { get { return m_linePosition; } } 750 HasLineInfo()751 public bool HasLineInfo() 752 { 753 return m_hasLineInfo; 754 } 755 } 756 } 757