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