1
//---------------------------------------------------------------------
2 // <copyright file="FunctionImportMapping.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
8 //---------------------------------------------------------------------
10 namespace System
.Data
.Mapping
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
;
22 using System
.Xml
.XPath
;
23 using OM
= System
.Collections
.ObjectModel
;
26 /// Represents a mapping from a model function import to a store composable or non-composable function.
28 internal abstract class FunctionImportMapping
30 internal FunctionImportMapping(EdmFunction functionImport
, EdmFunction targetFunction
)
32 this.FunctionImport
= EntityUtil
.CheckArgumentNull(functionImport
, "functionImport");
33 this.TargetFunction
= EntityUtil
.CheckArgumentNull(targetFunction
, "targetFunction");
37 /// Gets model function (or source of the mapping)
39 internal readonly EdmFunction FunctionImport
;
42 /// Gets store function (or target of the mapping)
44 internal readonly EdmFunction TargetFunction
;
47 internal sealed class FunctionImportStructuralTypeMappingKB
49 internal FunctionImportStructuralTypeMappingKB(
50 IEnumerable
<FunctionImportStructuralTypeMapping
> structuralTypeMappings
,
51 ItemCollection itemCollection
)
53 EntityUtil
.CheckArgumentNull(structuralTypeMappings
, "structuralTypeMappings");
54 m_itemCollection
= EntityUtil
.CheckArgumentNull(itemCollection
, "itemCollection");
56 // If no specific type mapping.
57 if (structuralTypeMappings
.Count() == 0)
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
>());
67 IEnumerable
<FunctionImportEntityTypeMapping
> entityTypeMappings
= structuralTypeMappings
.OfType
<FunctionImportEntityTypeMapping
>();
69 // FunctionImportEntityTypeMapping
70 if (null != entityTypeMappings
&& null != entityTypeMappings
.FirstOrDefault
<FunctionImportEntityTypeMapping
>())
72 var isOfTypeEntityTypeColumnsRenameMapping
= new Dictionary
<EntityType
, OM
.Collection
<FunctionImportReturnTypePropertyMapping
>>();
73 var entityTypeColumnsRenameMapping
= new Dictionary
<EntityType
, OM
.Collection
<FunctionImportReturnTypePropertyMapping
>>();
74 var normalizedEntityTypeMappings
= new List
<FunctionImportNormalizedEntityTypeMapping
>();
76 // Collect all mapped entity types.
77 this.MappedEntityTypes
= entityTypeMappings
78 .SelectMany(mapping
=> mapping
.GetMappedEntityTypes(m_itemCollection
))
83 // Collect all discriminator columns.
84 this.DiscriminatorColumns
= entityTypeMappings
85 .SelectMany(mapping
=> mapping
.GetDiscriminatorColumns())
90 m_entityTypeLineInfos
= new KeyToListMap
<EntityType
, LineInfo
>(EqualityComparer
<EntityType
>.Default
);
91 m_isTypeOfLineInfos
= new KeyToListMap
<EntityType
, LineInfo
>(EqualityComparer
<EntityType
>.Default
);
93 foreach (var entityTypeMapping
in entityTypeMappings
)
95 // Remember LineInfos for error reporting.
96 foreach (var entityType
in entityTypeMapping
.EntityTypes
)
98 m_entityTypeLineInfos
.Add(entityType
, entityTypeMapping
.LineInfo
);
100 foreach (var isTypeOf
in entityTypeMapping
.IsOfTypeEntityTypes
)
102 m_isTypeOfLineInfos
.Add(isTypeOf
, entityTypeMapping
.LineInfo
);
105 // Create map from column name to condition.
106 var columnMap
= entityTypeMapping
.Conditions
.ToDictionary(
107 condition
=> condition
.ColumnName
,
108 condition
=> condition
);
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
++)
114 string discriminatorColumn
= this.DiscriminatorColumns
[i
];
115 FunctionImportEntityTypeMappingCondition mappingCondition
;
116 if (columnMap
.TryGetValue(discriminatorColumn
, out mappingCondition
))
118 columnMappings
.Add(mappingCondition
);
122 // Null indicates the value for this discriminator doesn't matter.
123 columnMappings
.Add(null);
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
++)
132 impliedEntityTypesBitMap
[i
] = impliedEntityTypesSet
.Contains(this.MappedEntityTypes
[i
]);
135 // Construct normalized mapping.
136 normalizedEntityTypeMappings
.Add(new FunctionImportNormalizedEntityTypeMapping(this, columnMappings
, new BitArray(impliedEntityTypesBitMap
)));
138 // Construct the rename mappings by adding isTypeOf types and specific entity types to the corresponding lists.
139 foreach (var isOfType
in entityTypeMapping
.IsOfTypeEntityTypes
)
141 if (!isOfTypeEntityTypeColumnsRenameMapping
.Keys
.Contains(isOfType
))
143 isOfTypeEntityTypeColumnsRenameMapping
.Add(isOfType
, new OM
.Collection
<FunctionImportReturnTypePropertyMapping
>());
145 foreach (var rename
in entityTypeMapping
.ColumnsRenameList
)
147 isOfTypeEntityTypeColumnsRenameMapping
[isOfType
].Add(rename
);
150 foreach (var entityType
in entityTypeMapping
.EntityTypes
)
152 if (!entityTypeColumnsRenameMapping
.Keys
.Contains(entityType
))
154 entityTypeColumnsRenameMapping
.Add(entityType
, new OM
.Collection
<FunctionImportReturnTypePropertyMapping
>());
156 foreach (var rename
in entityTypeMapping
.ColumnsRenameList
)
158 entityTypeColumnsRenameMapping
[entityType
].Add(rename
);
163 this.ReturnTypeColumnsRenameMapping
= new FunctionImportReturnTypeEntityTypeColumnsRenameBuilder(isOfTypeEntityTypeColumnsRenameMapping
,
164 entityTypeColumnsRenameMapping
)
165 .ColumnRenameMapping
;
167 this.NormalizedEntityTypeMappings
= new OM
.ReadOnlyCollection
<FunctionImportNormalizedEntityTypeMapping
>(
168 normalizedEntityTypeMappings
);
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
>();
176 Debug
.Assert(complexTypeMappings
.Count() == 1, "how come there are more than 1, complex type cannot derive from other complex type");
178 this.ReturnTypeColumnsRenameMapping
= new Dictionary
<string, FunctionImportReturnTypeStructuralTypeColumnRenameMapping
>();
179 foreach (var rename
in complexTypeMappings
.First().ColumnsRenameList
)
181 FunctionImportReturnTypeStructuralTypeColumnRenameMapping columnRenameMapping
= new FunctionImportReturnTypeStructuralTypeColumnRenameMapping(rename
.CMember
);
182 columnRenameMapping
.AddRename(new FunctionImportReturnTypeStructuralTypeColumn(
184 complexTypeMappings
.First().ReturnType
,
187 this.ReturnTypeColumnsRenameMapping
.Add(rename
.CMember
, columnRenameMapping
);
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
>() { }
);
197 private readonly ItemCollection m_itemCollection
;
198 private readonly KeyToListMap
<EntityType
, LineInfo
> m_entityTypeLineInfos
;
199 private readonly KeyToListMap
<EntityType
, LineInfo
> m_isTypeOfLineInfos
;
202 /// Gets all types in scope for this mapping.
204 internal readonly OM
.ReadOnlyCollection
<EntityType
> MappedEntityTypes
;
207 /// Gets a list of all discriminator columns used in this mapping.
209 internal readonly OM
.ReadOnlyCollection
<string> DiscriminatorColumns
;
212 /// Gets normalized representation of all EntityTypeMapping fragments for this
213 /// function import mapping.
215 internal readonly OM
.ReadOnlyCollection
<FunctionImportNormalizedEntityTypeMapping
> NormalizedEntityTypeMappings
;
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.
221 internal readonly Dictionary
<string, FunctionImportReturnTypeStructuralTypeColumnRenameMapping
> ReturnTypeColumnsRenameMapping
;
223 internal bool ValidateTypeConditions(bool validateAmbiguity
, IList
<EdmSchemaError
> errors
, string sourceLocation
)
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
);
231 foreach (var unreachableEntityType
in unreachableEntityTypes
.KeyValuePairs
)
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
,
241 lineInfo
.LinePosition
);
245 foreach (var unreachableIsTypeOf
in unreachableIsTypeOfs
.KeyValuePairs
)
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
,
256 lineInfo
.LinePosition
);
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.
270 /// - Converting type mapping conditions into vertices
271 /// - Checking that some assignment satisfies
273 private void GetUnreachableTypes(
274 bool validateAmbiguity
,
275 out KeyToListMap
<EntityType
, LineInfo
> unreachableEntityTypes
,
276 out KeyToListMap
<EntityType
, LineInfo
> unreachableIsTypeOfs
)
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();
282 // Convert type mapping conditions to decision diagram vertices.
283 var converter
= new DomainConstraintConversionContext
<string, ValueCondition
>();
284 Vertex
[] mappingConditions
= ConvertMappingConditionsToVertices(converter
, variables
);
286 // Find reachable types.
287 Set
<EntityType
> reachableTypes
= validateAmbiguity
?
288 FindUnambiguouslyReachableTypes(converter
, mappingConditions
) :
289 FindReachableTypes(converter
, mappingConditions
);
291 CollectUnreachableTypes(reachableTypes
, out unreachableEntityTypes
, out unreachableIsTypeOfs
);
294 private DomainVariable
<string, ValueCondition
>[] ConstructDomainVariables()
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
++)
300 discriminatorDomains
[i
] = new Set
<ValueCondition
>();
301 discriminatorDomains
[i
].Add(ValueCondition
.IsOther
);
302 discriminatorDomains
[i
].Add(ValueCondition
.IsNull
);
305 // Collect all domain values.
306 foreach (var typeMapping
in this.NormalizedEntityTypeMappings
)
308 for (int i
= 0; i
< this.DiscriminatorColumns
.Count
; i
++)
310 var discriminatorValue
= typeMapping
.ColumnConditions
[i
];
311 if (null != discriminatorValue
&&
312 !discriminatorValue
.ConditionValue
.IsNotNullCondition
) // NotNull is a special range (everything but IsNull)
314 discriminatorDomains
[i
].Add(discriminatorValue
.ConditionValue
);
319 var discriminatorVariables
= new DomainVariable
<string, ValueCondition
>[discriminatorDomains
.Length
];
320 for (int i
= 0; i
< discriminatorVariables
.Length
; i
++)
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());
327 return discriminatorVariables
;
330 private Vertex
[] ConvertMappingConditionsToVertices(
331 ConversionContext
<DomainConstraint
<string, ValueCondition
>> converter
,
332 DomainVariable
<string, ValueCondition
>[] variables
)
334 Vertex
[] conditions
= new Vertex
[this.NormalizedEntityTypeMappings
.Count
];
335 for (int i
= 0; i
< conditions
.Length
; i
++)
337 var typeMapping
= this.NormalizedEntityTypeMappings
[i
];
339 // create conjunction representing the condition
340 Vertex condition
= Vertex
.One
;
341 for (int j
= 0; j
< this.DiscriminatorColumns
.Count
; j
++)
343 var columnCondition
= typeMapping
.ColumnConditions
[j
];
344 if (null != columnCondition
)
346 var conditionValue
= columnCondition
.ConditionValue
;
347 if (conditionValue
.IsNotNullCondition
)
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
));
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
));
364 conditions
[i
] = condition
;
370 /// Determines which types are produced by this mapping.
372 private Set
<EntityType
> FindReachableTypes(DomainConstraintConversionContext
<string, ValueCondition
> converter
, Vertex
[] mappingConditions
)
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
++)
380 // Seed the candidate function conjunction with 'true'.
381 Vertex candidateFunction
= Vertex
.One
;
382 for (int j
= 0; j
< this.NormalizedEntityTypeMappings
.Count
; j
++)
384 var entityTypeMapping
= this.NormalizedEntityTypeMappings
[j
];
386 // Determine if this mapping is a positive or negative case for the current type.
387 if (entityTypeMapping
.ImpliedEntityTypes
[i
])
389 candidateFunction
= converter
.Solver
.And(candidateFunction
, mappingConditions
[j
]);
393 candidateFunction
= converter
.Solver
.And(candidateFunction
, converter
.Solver
.Not(mappingConditions
[j
]));
396 candidateFunctions
[i
] = candidateFunction
;
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
++)
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
?
408 converter
.Solver
.Not(typeCondition
)));
410 // If the above conjunction is satisfiable, it means some row configuration exists producing the type.
411 if (!isExactlyThisTypeCondition
.IsZero())
413 reachableTypes
.Add(this.MappedEntityTypes
[i
]);
417 return reachableTypes
;
421 /// Determines which types are produced by this mapping.
423 private Set
<EntityType
> FindUnambiguouslyReachableTypes(DomainConstraintConversionContext
<string, ValueCondition
> converter
, Vertex
[] mappingConditions
)
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
++)
430 // Seed the candidate function conjunction with 'true'.
431 Vertex candidateFunction
= Vertex
.One
;
432 for (int j
= 0; j
< this.NormalizedEntityTypeMappings
.Count
; j
++)
434 var entityTypeMapping
= this.NormalizedEntityTypeMappings
[j
];
436 // Determine if this mapping is a positive or negative case for the current type.
437 if (entityTypeMapping
.ImpliedEntityTypes
[i
])
439 candidateFunction
= converter
.Solver
.And(candidateFunction
, mappingConditions
[j
]);
442 candidateFunctions
[i
] = candidateFunction
;
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
)
449 if (candidateFunctions
[i
].IsZero())
451 // The i-th type is unreachable regardless of other types.
452 unambigouslyReachableMap
[i
] = false;
456 for (int j
= i
+ 1; j
< candidateFunctions
.Length
; ++j
)
458 if (!converter
.Solver
.And(candidateFunctions
[i
], candidateFunctions
[j
]).IsZero())
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;
467 var reachableTypes
= new Set
<EntityType
>();
468 for (int i
= 0; i
< candidateFunctions
.Length
; ++i
)
470 if (unambigouslyReachableMap
[i
])
472 reachableTypes
.Add(this.MappedEntityTypes
[i
]);
476 return reachableTypes
;
479 private void CollectUnreachableTypes(Set
<EntityType
> reachableTypes
, out KeyToListMap
<EntityType
, LineInfo
> entityTypes
, out KeyToListMap
<EntityType
, LineInfo
> isTypeOfEntityTypes
)
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
);
485 if (reachableTypes
.Count
== this.MappedEntityTypes
.Count
)
487 // All types are reachable; nothing to check
491 // Find IsTypeOf mappings where no type in hierarchy can generate a row
492 foreach (var isTypeOf
in m_isTypeOfLineInfos
.Keys
)
494 if (!MetadataHelper
.GetTypeAndSubtypesOf(isTypeOf
, m_itemCollection
, false)
496 .Intersect(reachableTypes
)
499 // no type in the hierarchy is reachable...
500 isTypeOfEntityTypes
.AddRange(isTypeOf
, m_isTypeOfLineInfos
.EnumerateValues(isTypeOf
));
504 // Find explicit types not generating a value
505 foreach (var entityType
in m_entityTypeLineInfos
.Keys
)
507 if (!reachableTypes
.Contains(entityType
))
509 entityTypes
.AddRange(entityType
, m_entityTypeLineInfos
.EnumerateValues(entityType
));
515 internal sealed class FunctionImportNormalizedEntityTypeMapping
517 internal FunctionImportNormalizedEntityTypeMapping(FunctionImportStructuralTypeMappingKB parent
,
518 List
<FunctionImportEntityTypeMappingCondition
> columnConditions
, BitArray impliedEntityTypes
)
520 // validate arguments
521 EntityUtil
.CheckArgumentNull(parent
, "parent");
522 EntityUtil
.CheckArgumentNull(columnConditions
, "discriminatorValues");
523 EntityUtil
.CheckArgumentNull(impliedEntityTypes
, "impliedEntityTypes");
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");
530 this.ColumnConditions
= new OM
.ReadOnlyCollection
<FunctionImportEntityTypeMappingCondition
>(columnConditions
.ToList());
531 this.ImpliedEntityTypes
= impliedEntityTypes
;
532 this.ComplementImpliedEntityTypes
= (new BitArray(this.ImpliedEntityTypes
)).Not();
536 /// Gets discriminator values aligned with DiscriminatorColumns of the parent FunctionImportMapping.
537 /// A null ValueCondition indicates 'anything goes'.
539 internal readonly OM
.ReadOnlyCollection
<FunctionImportEntityTypeMappingCondition
> ColumnConditions
;
542 /// Gets bit array with 'true' indicating the corresponding MappedEntityType of the parent
543 /// FunctionImportMapping is implied by this fragment.
545 internal readonly BitArray ImpliedEntityTypes
;
548 /// Gets the complement of the ImpliedEntityTypes BitArray.
550 internal readonly BitArray ComplementImpliedEntityTypes
;
552 public override string ToString()
554 return String
.Format(CultureInfo
.InvariantCulture
, "Values={0}, Types={1}",
555 StringUtil
.ToCommaSeparatedString(this.ColumnConditions
), StringUtil
.ToCommaSeparatedString(this.ImpliedEntityTypes
));
559 internal abstract class FunctionImportEntityTypeMappingCondition
561 protected FunctionImportEntityTypeMappingCondition(string columnName
, LineInfo lineInfo
)
563 this.ColumnName
= EntityUtil
.CheckArgumentNull(columnName
, "columnName");
564 this.LineInfo
= lineInfo
;
567 internal readonly string ColumnName
;
568 internal readonly LineInfo LineInfo
;
570 internal abstract ValueCondition ConditionValue { get; }
572 internal abstract bool ColumnValueMatchesCondition(object columnValue
);
574 public override string ToString()
576 return this.ConditionValue
.ToString();
580 internal sealed class FunctionImportEntityTypeMappingConditionValue
: FunctionImportEntityTypeMappingCondition
582 internal FunctionImportEntityTypeMappingConditionValue(string columnName
, XPathNavigator columnValue
, LineInfo lineInfo
)
583 : base(columnName
, lineInfo
)
585 this._xPathValue
= EntityUtil
.CheckArgumentNull(columnValue
, "columnValue");
586 this._convertedValues
= new Memoizer
<Type
, object>(this.GetConditionValue
, null);
589 private readonly XPathNavigator _xPathValue
;
590 private readonly Memoizer
<Type
, object> _convertedValues
;
592 internal override ValueCondition ConditionValue
594 get { return new ValueCondition(_xPathValue.Value); }
597 internal override bool ColumnValueMatchesCondition(object columnValue
)
599 if (null == columnValue
|| Convert
.IsDBNull(columnValue
))
601 // only FunctionImportEntityTypeMappingConditionIsNull can match a null
606 Type columnValueType
= columnValue
.GetType();
608 // check if we've interpreted this column type yet
609 object conditionValue
= _convertedValues
.Evaluate(columnValueType
);
610 return ByValueEqualityComparer
.Default
.Equals(columnValue
, conditionValue
);
613 private object GetConditionValue(Type columnValueType
)
615 return GetConditionValue(
617 handleTypeNotComparable: () =>
619 throw EntityUtil
.CommandExecution(Strings
.Mapping_FunctionImport_UnsupportedType(this.ColumnName
, columnValueType
.FullName
));
621 handleInvalidConditionValue: () =>
623 throw EntityUtil
.CommandExecution(Strings
.Mapping_FunctionImport_ConditionValueTypeMismatch(StorageMslConstructs
.FunctionImportMappingElement
, this.ColumnName
, columnValueType
.FullName
));
627 internal object GetConditionValue(Type columnValueType
, Action handleTypeNotComparable
, Action handleInvalidConditionValue
)
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
))
634 handleTypeNotComparable();
640 return _xPathValue
.ValueAs(columnValueType
);
642 catch (FormatException
)
644 handleInvalidConditionValue();
650 internal sealed class FunctionImportEntityTypeMappingConditionIsNull
: FunctionImportEntityTypeMappingCondition
652 internal FunctionImportEntityTypeMappingConditionIsNull(string columnName
, bool isNull
, LineInfo lineInfo
)
653 : base(columnName
, lineInfo
)
655 this.IsNull
= isNull
;
658 internal readonly bool IsNull
;
660 internal override ValueCondition ConditionValue
662 get { return IsNull ? ValueCondition.IsNull : ValueCondition.IsNotNull; }
665 internal override bool ColumnValueMatchesCondition(object columnValue
)
667 bool valueIsNull
= null == columnValue
|| Convert
.IsDBNull(columnValue
);
668 return valueIsNull
== this.IsNull
;
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.
677 internal class ValueCondition
: IEquatable
<ValueCondition
>
679 internal readonly string Description
;
680 internal readonly bool IsSentinel
;
682 internal const string IsNullDescription
= "NULL";
683 internal const string IsNotNullDescription
= "NOT NULL";
684 internal const string IsOtherDescription
= "OTHER";
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);
690 private ValueCondition(string description
, bool isSentinel
)
692 Description
= description
;
693 IsSentinel
= isSentinel
;
696 internal ValueCondition(string description
)
697 : this(description
, false)
701 internal bool IsNotNullCondition { get { return object.ReferenceEquals(this, IsNotNull); }
}
703 public bool Equals(ValueCondition other
)
705 return other
.IsSentinel
== this.IsSentinel
&&
706 other
.Description
== this.Description
;
709 public override int GetHashCode()
711 return Description
.GetHashCode();
714 public override string ToString()
716 return this.Description
;
720 internal sealed class LineInfo
: IXmlLineInfo
722 private readonly bool m_hasLineInfo
;
723 private readonly int m_lineNumber
;
724 private readonly int m_linePosition
;
726 internal LineInfo(XPathNavigator nav
)
727 : this((IXmlLineInfo
)nav
)
730 internal LineInfo(IXmlLineInfo lineInfo
)
732 m_hasLineInfo
= lineInfo
.HasLineInfo();
733 m_lineNumber
= lineInfo
.LineNumber
;
734 m_linePosition
= lineInfo
.LinePosition
;
737 internal static readonly LineInfo Empty
= new LineInfo();
740 m_hasLineInfo
= false;
741 m_lineNumber
= default(int);
742 m_linePosition
= default(int);
745 public int LineNumber
746 { get { return m_lineNumber; }
}
748 public int LinePosition
749 { get { return m_linePosition; }
}
751 public bool HasLineInfo()
753 return m_hasLineInfo
;