1 //---------------------------------------------------------------------
2 // <copyright file="OneToOneMappingSerializer.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
7 // @backupOwner Microsoft
8 //---------------------------------------------------------------------
9 using System
.Collections
.Generic
;
10 using System
.Data
.Common
;
11 using System
.Data
.Common
.Utils
;
12 using System
.Data
.Mapping
;
13 using System
.Data
.Metadata
.Edm
;
14 using System
.Diagnostics
;
18 namespace System
.Data
.Entity
.Design
.Common
20 internal class OneToOneMappingSerializer
22 internal class MappingLookups
24 internal Dictionary
<EntityType
, EntityType
> StoreEntityTypeToModelEntityType
= new Dictionary
<EntityType
, EntityType
>();
25 internal Dictionary
<EdmProperty
, EdmProperty
> StoreEdmPropertyToModelEdmProperty
= new Dictionary
<EdmProperty
, EdmProperty
>();
26 internal Dictionary
<EntitySet
, EntitySet
> StoreEntitySetToModelEntitySet
= new Dictionary
<EntitySet
, EntitySet
>();
28 internal Dictionary
<AssociationType
, AssociationType
> StoreAssociationTypeToModelAssociationType
= new Dictionary
<AssociationType
, AssociationType
>();
29 internal Dictionary
<AssociationEndMember
, AssociationEndMember
> StoreAssociationEndMemberToModelAssociationEndMember
= new Dictionary
<AssociationEndMember
, AssociationEndMember
>();
30 internal Dictionary
<AssociationSet
, AssociationSet
> StoreAssociationSetToModelAssociationSet
= new Dictionary
<AssociationSet
, AssociationSet
>();
31 internal Dictionary
<AssociationSetEnd
, AssociationSetEnd
> StoreAssociationSetEndToModelAssociationSetEnd
= new Dictionary
<AssociationSetEnd
, AssociationSetEnd
>();
33 internal List
<CollapsedEntityAssociationSet
> CollapsedEntityAssociationSets
= new List
<CollapsedEntityAssociationSet
>();
35 internal List
<Tuple
<EdmFunction
, EdmFunction
>> StoreFunctionToFunctionImport
= new List
<Tuple
<EdmFunction
, EdmFunction
>>();
38 // this class represents a construct found in the ssdl where a link table
39 // contained no data (all its properties were part of its keys)
40 // it has exactly two associations
41 // the entity type is the TO side of both associations
42 // all the colums are used as TO columns in the constraint
43 internal class CollapsedEntityAssociationSet
45 private EntitySet _storeEntitySet
;
46 private List
<AssociationSet
> _storeAssociationSets
= new List
<AssociationSet
>(2);
47 private AssociationSet _modelAssociationSet
;
49 public AssociationSet ModelAssociationSet
51 get { return _modelAssociationSet; }
54 Debug
.Assert(_modelAssociationSet
== null, "why is this getting set multiple times, it should only be set after the new set is created");
55 _modelAssociationSet
= value;
59 public CollapsedEntityAssociationSet(EntitySet entitySet
)
61 Debug
.Assert(entitySet
!= null, "entitySet parameter is null");
62 _storeEntitySet
= entitySet
;
65 public EntitySet EntitySet
67 get { return _storeEntitySet; }
70 public List
<AssociationSet
> AssociationSets
72 get { return _storeAssociationSets; }
75 public void GetStoreAssociationSetEnd(int index
, out AssociationSetEnd storeAssociationSetEnd
, out RelationshipMultiplicity multiplicity
, out OperationAction deleteBehavior
)
77 Debug
.Assert(index
>= 0 && index
< AssociationSets
.Count
, "out of bounds dude!!");
78 Debug
.Assert(AssociationSets
.Count
== 2, "This code depends on only having exactly two AssociationSets");
79 GetFromAssociationSetEnd(AssociationSets
[index
], AssociationSets
[(index
+1)%2], out storeAssociationSetEnd
, out multiplicity
, out deleteBehavior
);
82 private void GetFromAssociationSetEnd(AssociationSet definingSet
, AssociationSet multiplicitySet
, out AssociationSetEnd associationSetEnd
, out RelationshipMultiplicity multiplicity
, out OperationAction deleteBehavior
)
84 // for a situation like this (CD is CascadeDelete)
86 // -------- CD -------- CD --------
87 // | A |1 <- 1| AtoB |* <- 1| B |
88 // | |-------| |-------| |
90 // -------- -------- --------
93 // -------- CD --------
99 // Notice that the of the new "link table association" muliplicities are opposite of what comming into the original link table
100 // this seems counter intuitive at first, but makes sense when you think all the way through it
102 // CascadeDelete Behavior (we can assume the runtime will always delete cascade
103 // to the link table from the outside tables (it actually doesn't, but that is a bug))
105 // A -> AToB <- B None
108 // A <- AToB -> B None
114 // Other CascadeDelete rules
115 // 1. Can't have a delete from a Many multiplicity end
116 // 2. Can't have a delete on both ends
119 associationSetEnd
= GetAssociationSetEnd(definingSet
, true);
120 AssociationSetEnd multiplicityAssociationSetEnd
= GetAssociationSetEnd(multiplicitySet
, false);
121 multiplicity
= multiplicityAssociationSetEnd
.CorrespondingAssociationEndMember
.RelationshipMultiplicity
;
122 deleteBehavior
= OperationAction
.None
;
123 if (multiplicity
!= RelationshipMultiplicity
.Many
)
125 OperationAction otherEndBehavior
= GetAssociationSetEnd(definingSet
, false).CorrespondingAssociationEndMember
.DeleteBehavior
;
126 if(otherEndBehavior
== OperationAction
.None
)
128 // Since the other end does not have an operation
129 // that means that only one end could possibly have an operation, that is good
130 // so set it the operation
131 deleteBehavior
= multiplicityAssociationSetEnd
.CorrespondingAssociationEndMember
.DeleteBehavior
;
136 private static AssociationSetEnd
GetAssociationSetEnd(AssociationSet
set, bool fromEnd
)
138 Debug
.Assert(set.ElementType
.ReferentialConstraints
.Count
== 1, "no referenctial constraint for association[0]");
139 ReferentialConstraint constraint
= set.ElementType
.ReferentialConstraints
[0];
141 Debug
.Assert(set.AssociationSetEnds
.Count
== 2, "Associations are assumed to have two ends");
142 int toEndIndex
, fromEndIndex
;
143 if (set.AssociationSetEnds
[0].CorrespondingAssociationEndMember
== constraint
.FromRole
)
158 return set.AssociationSetEnds
[fromEndIndex
];
162 return set.AssociationSetEnds
[toEndIndex
];
166 public bool MeetsRequirementsForCollapsableAssociation
170 if (_storeAssociationSets
.Count
!= 2)
173 ReferentialConstraint constraint0
;
174 ReferentialConstraint constraint1
;
175 GetConstraints(out constraint0
, out constraint1
);
176 if (!IsEntityDependentSideOfBothAssociations(constraint0
, constraint1
))
179 if (!IsAtLeastOneColumnOfBothDependentRelationshipColumnSetsNonNullable(constraint0
, constraint1
))
182 if (!AreAllEntityColumnsMappedAsToColumns(constraint0
, constraint1
))
185 if (IsAtLeastOneColumnFKInBothAssociations(constraint0
, constraint1
))
192 private bool IsAtLeastOneColumnFKInBothAssociations(ReferentialConstraint constraint0
, ReferentialConstraint constraint1
)
194 return constraint1
.ToProperties
.Any(c
=> constraint0
.ToProperties
.Contains(c
));
197 private bool IsAtLeastOneColumnOfBothDependentRelationshipColumnSetsNonNullable(ReferentialConstraint constraint0
, ReferentialConstraint constraint1
)
199 return ToPropertyHasNonNullableColumn(constraint0
) && ToPropertyHasNonNullableColumn(constraint1
);
202 private static bool ToPropertyHasNonNullableColumn(ReferentialConstraint constraint
)
204 foreach (EdmProperty property
in constraint
.ToProperties
)
206 if (!property
.Nullable
)
214 private bool AreAllEntityColumnsMappedAsToColumns(ReferentialConstraint constraint0
, ReferentialConstraint constraint1
)
216 Set
<string> names
= new Set
<string>();
217 AddToPropertyNames(constraint0
, names
);
218 AddToPropertyNames(constraint1
, names
);
219 return names
.Count
== _storeEntitySet
.ElementType
.Properties
.Count
;
222 private static void AddToPropertyNames(ReferentialConstraint constraint
, Set
<string> names
)
224 foreach (EdmProperty property
in constraint
.ToProperties
)
226 names
.Add(property
.Name
);
230 private bool IsEntityDependentSideOfBothAssociations(ReferentialConstraint constraint0
, ReferentialConstraint constraint1
)
232 return ((RefType
)constraint0
.ToRole
.TypeUsage
.EdmType
).ElementType
== _storeEntitySet
.ElementType
&& ((RefType
)constraint1
.ToRole
.TypeUsage
.EdmType
).ElementType
== _storeEntitySet
.ElementType
;
235 private void GetConstraints(out ReferentialConstraint constraint0
, out ReferentialConstraint constraint1
)
237 Debug
.Assert(_storeAssociationSets
.Count
== 2, "don't call this method if you don't have two associations");
238 Debug
.Assert(_storeAssociationSets
[0].ElementType
.ReferentialConstraints
.Count
== 1, "no referenctial constraint for association[0]");
239 Debug
.Assert(_storeAssociationSets
[1].ElementType
.ReferentialConstraints
.Count
== 1, "no referenctial constraint for association[1]");
240 constraint0
= _storeAssociationSets
[0].ElementType
.ReferentialConstraints
[0];
241 constraint1
= _storeAssociationSets
[1].ElementType
.ReferentialConstraints
[0];
245 private MappingLookups _lookups
;
246 private EntityContainer _storeContainer
;
247 private EntityContainer _modelContainer
;
248 private string _xmlNamespace
;
250 internal OneToOneMappingSerializer(MappingLookups lookups
,
251 EntityContainer storeContainer
,
252 EntityContainer modelContainer
,
253 Version schemaVersion
)
255 EDesignUtil
.CheckArgumentNull(lookups
, "lookups");
256 EDesignUtil
.CheckArgumentNull(storeContainer
, "storeContainer");
257 EDesignUtil
.CheckArgumentNull(modelContainer
, "modelContainer");
259 _storeContainer
= storeContainer
;
260 _modelContainer
= modelContainer
;
261 _xmlNamespace
= EntityFrameworkVersions
.GetSchemaNamespace(schemaVersion
, DataSpace
.CSSpace
);
264 public void WriteXml(XmlWriter writer
)
266 EDesignUtil
.CheckArgumentNull(writer
, "writer");
268 WriteMappingStartElement(writer
);
269 WriteEntityContainerMappingElement(writer
);
270 writer
.WriteEndElement();
273 private void WriteEntityContainerMappingElement(XmlWriter writer
)
275 writer
.WriteStartElement(StorageMslConstructs
.EntityContainerMappingElement
, _xmlNamespace
);
276 writer
.WriteAttributeString(StorageMslConstructs
.StorageEntityContainerAttribute
, _storeContainer
.Name
);
277 writer
.WriteAttributeString(StorageMslConstructs
.CdmEntityContainerAttribute
, _modelContainer
.Name
);
279 foreach (EntitySet
set in _lookups
.StoreEntitySetToModelEntitySet
.Keys
)
281 EntitySet modelEntitySet
= _lookups
.StoreEntitySetToModelEntitySet
[set];
282 WriteEntitySetMappingElement(writer
, set, modelEntitySet
);
285 foreach(AssociationSet
set in _lookups
.StoreAssociationSetToModelAssociationSet
.Keys
)
287 AssociationSet modelAssociationSet
= _lookups
.StoreAssociationSetToModelAssociationSet
[set];
288 WriteAssociationSetMappingElement(writer
, set, modelAssociationSet
);
291 foreach (CollapsedEntityAssociationSet
set in _lookups
.CollapsedEntityAssociationSets
)
293 WriteAssociationSetMappingElement(writer
, set);
296 foreach (var functionMapping
in _lookups
.StoreFunctionToFunctionImport
)
298 var storeFunction
= functionMapping
.Item1
;
299 var functionImport
= functionMapping
.Item2
;
300 WriteFunctionImportMappingElement(writer
, storeFunction
, functionImport
);
303 writer
.WriteEndElement();
306 private void WriteFunctionImportMappingElement(XmlWriter writer
, EdmFunction storeFunction
, EdmFunction functionImport
)
308 Debug
.Assert(storeFunction
.IsComposableAttribute
, "storeFunction.IsComposableAttribute");
309 Debug
.Assert(storeFunction
.ReturnParameters
.Count
== 1, "storeFunction.ReturnParameters.Count == 1");
310 Debug
.Assert(functionImport
.IsComposableAttribute
, "functionImport.IsComposableAttribute");
311 Debug
.Assert(functionImport
.ReturnParameters
.Count
== 1, "functionImport.ReturnParameters.Count == 1");
313 writer
.WriteStartElement(StorageMslConstructs
.FunctionImportMappingElement
, _xmlNamespace
);
314 writer
.WriteAttributeString(StorageMslConstructs
.FunctionImportMappingFunctionNameAttribute
, storeFunction
.FullName
);
315 writer
.WriteAttributeString(StorageMslConstructs
.FunctionImportMappingFunctionImportNameAttribute
, functionImport
.Name
);
317 RowType tvfReturnType
= TypeHelpers
.GetTvfReturnType(storeFunction
);
318 if (tvfReturnType
!= null)
320 // Table-valued function
321 Debug
.Assert(functionImport
.ReturnParameter
.TypeUsage
.EdmType
.BuiltInTypeKind
== BuiltInTypeKind
.CollectionType
, "functionImport is expected to return Collection(ComplexType)");
322 var modelCollectionType
= (CollectionType
)functionImport
.ReturnParameter
.TypeUsage
.EdmType
;
323 Debug
.Assert(modelCollectionType
.TypeUsage
.EdmType
.BuiltInTypeKind
== BuiltInTypeKind
.ComplexType
, "functionImport is expected to return Collection(ComplexType)");
324 var modelComplexType
= (ComplexType
)modelCollectionType
.TypeUsage
.EdmType
;
326 // Write ResultMapping/ComplexTypeMapping
327 writer
.WriteStartElement(StorageMslConstructs
.FunctionImportMappingResultMapping
, _xmlNamespace
);
328 writer
.WriteStartElement(StorageMslConstructs
.ComplexTypeMappingElement
, _xmlNamespace
);
329 writer
.WriteAttributeString(StorageMslConstructs
.ComplexTypeMappingTypeNameAttribute
, modelComplexType
.FullName
);
330 foreach (EdmProperty storeProperty
in tvfReturnType
.Properties
)
332 EdmProperty modelProperty
= _lookups
.StoreEdmPropertyToModelEdmProperty
[storeProperty
];
333 WriteScalarPropertyElement(writer
, storeProperty
, modelProperty
);
335 writer
.WriteEndElement();
336 writer
.WriteEndElement();
340 Debug
.Fail("Only TVF store functions are supported.");
343 writer
.WriteEndElement();
346 private void WriteAssociationSetMappingElement(XmlWriter writer
, CollapsedEntityAssociationSet collapsedAssociationSet
)
348 if (!collapsedAssociationSet
.ModelAssociationSet
.ElementType
.IsForeignKey
)
350 writer
.WriteStartElement(StorageMslConstructs
.AssociationSetMappingElement
, _xmlNamespace
);
351 writer
.WriteAttributeString(StorageMslConstructs
.AssociationSetMappingNameAttribute
, collapsedAssociationSet
.ModelAssociationSet
.Name
);
352 writer
.WriteAttributeString(StorageMslConstructs
.AssociationSetMappingTypeNameAttribute
, collapsedAssociationSet
.ModelAssociationSet
.ElementType
.FullName
);
353 writer
.WriteAttributeString(StorageMslConstructs
.AssociationSetMappingStoreEntitySetAttribute
, collapsedAssociationSet
.EntitySet
.Name
);
356 for (int i
= 0; i
< collapsedAssociationSet
.AssociationSets
.Count
; i
++)
358 AssociationSetEnd storeEnd
;
359 RelationshipMultiplicity multiplicity
;
360 OperationAction deleteBehavior
;
361 collapsedAssociationSet
.GetStoreAssociationSetEnd(i
, out storeEnd
, out multiplicity
, out deleteBehavior
);
362 AssociationSetEnd modelEnd
= _lookups
.StoreAssociationSetEndToModelAssociationSetEnd
[storeEnd
];
363 WriteEndPropertyElement(writer
, storeEnd
, modelEnd
);
366 // don't need condition element
368 writer
.WriteEndElement();
372 private void WriteAssociationSetMappingElement(XmlWriter writer
, AssociationSet store
, AssociationSet model
)
374 if (!model
.ElementType
.IsForeignKey
)
376 writer
.WriteStartElement(StorageMslConstructs
.AssociationSetMappingElement
, _xmlNamespace
);
377 writer
.WriteAttributeString(StorageMslConstructs
.AssociationSetMappingNameAttribute
, model
.Name
);
378 writer
.WriteAttributeString(StorageMslConstructs
.AssociationSetMappingTypeNameAttribute
, model
.ElementType
.FullName
);
380 // all column names must be the primary key of the
381 // end, but as columns in the Fk table.
382 AssociationSetEnd foreignKeyTableEnd
= GetAssociationSetEndForForeignKeyTable(store
);
383 writer
.WriteAttributeString(StorageMslConstructs
.AssociationSetMappingStoreEntitySetAttribute
, foreignKeyTableEnd
.EntitySet
.Name
);
385 foreach (AssociationSetEnd storeEnd
in store
.AssociationSetEnds
)
387 AssociationSetEnd modelEnd
= _lookups
.StoreAssociationSetEndToModelAssociationSetEnd
[storeEnd
];
388 WriteEndPropertyElement(writer
, storeEnd
, modelEnd
);
391 ReferentialConstraint constraint
= GetReferentialConstraint(store
);
392 foreach (EdmProperty fkColumn
in constraint
.ToProperties
)
394 if (fkColumn
.Nullable
)
396 WriteConditionElement(writer
, fkColumn
);
400 writer
.WriteEndElement();
404 private void WriteConditionElement(XmlWriter writer
, EdmProperty fkColumn
)
406 writer
.WriteStartElement(StorageMslConstructs
.ConditionElement
, _xmlNamespace
);
407 writer
.WriteAttributeString(StorageMslConstructs
.ConditionColumnNameAttribute
, fkColumn
.Name
);
408 writer
.WriteAttributeString(StorageMslConstructs
.ConditionIsNullAttribute
, "false");
409 writer
.WriteEndElement();
412 private static AssociationSetEnd
GetAssociationSetEndForForeignKeyTable(AssociationSet store
)
414 ReferentialConstraint constraint
= GetReferentialConstraint(store
);
415 return store
.AssociationSetEnds
.GetValue(constraint
.ToRole
.Name
, false);
418 internal static ReferentialConstraint
GetReferentialConstraint(AssociationSet
set)
420 // this seeems like a hack, but it is what we have right now.
421 ReferentialConstraint constraint
= null;
422 foreach (ReferentialConstraint rc
in set.ElementType
.ReferentialConstraints
)
424 Debug
.Assert(constraint
== null, "we should only get one");
427 Debug
.Assert(constraint
!= null, "we should get at least one constraint");
431 private void WriteEndPropertyElement(XmlWriter writer
, AssociationSetEnd store
, AssociationSetEnd model
)
433 writer
.WriteStartElement(StorageMslConstructs
.EndPropertyMappingElement
, _xmlNamespace
);
434 writer
.WriteAttributeString(StorageMslConstructs
.EndPropertyMappingNameAttribute
, model
.Name
);
435 foreach (EdmProperty storeKeyMember
in store
.EntitySet
.ElementType
.KeyMembers
)
437 EdmProperty modelKeyMember
= _lookups
.StoreEdmPropertyToModelEdmProperty
[storeKeyMember
];
438 EdmProperty storeFkTableMember
= GetAssociatedFkColumn(store
, storeKeyMember
);
439 WriteScalarPropertyElement(writer
, storeFkTableMember
, modelKeyMember
);
441 writer
.WriteEndElement();
444 private static EdmProperty
GetAssociatedFkColumn(AssociationSetEnd store
, EdmProperty storeKeyProperty
)
446 ReferentialConstraint constraint
= GetReferentialConstraint(store
.ParentAssociationSet
);
447 if (store
.Name
== constraint
.FromRole
.Name
)
449 for (int i
= 0; i
< constraint
.FromProperties
.Count
; i
++)
451 if (constraint
.FromProperties
[i
] == storeKeyProperty
)
453 // return the matching Fk column
454 return constraint
.ToProperties
[i
];
459 return storeKeyProperty
;
462 private void WriteEntitySetMappingElement(XmlWriter writer
, EntitySet store
, EntitySet model
)
464 writer
.WriteStartElement(StorageMslConstructs
.EntitySetMappingElement
, _xmlNamespace
);
465 writer
.WriteAttributeString(StorageMslConstructs
.EntitySetMappingNameAttribute
, model
.Name
);
466 WriteEntityTypeMappingElement(writer
, store
, model
);
467 writer
.WriteEndElement();
470 private void WriteEntityTypeMappingElement(XmlWriter writer
, EntitySet store
, EntitySet model
)
472 writer
.WriteStartElement(StorageMslConstructs
.EntityTypeMappingElement
, _xmlNamespace
);
473 writer
.WriteAttributeString(StorageMslConstructs
.EntityTypeMappingTypeNameAttribute
, model
.ElementType
.FullName
);
474 WriteMappingFragmentElement(writer
, store
, model
);
475 writer
.WriteEndElement();
478 private void WriteMappingFragmentElement(XmlWriter writer
, EntitySet store
, EntitySet model
)
480 writer
.WriteStartElement(StorageMslConstructs
.MappingFragmentElement
, _xmlNamespace
);
481 writer
.WriteAttributeString(StorageMslConstructs
.EntityTypeMappingStoreEntitySetAttribute
, store
.Name
);
482 foreach (EdmProperty storeProperty
in store
.ElementType
.Properties
)
484 // we don't add the fk properties to c-space, so some are missing,
485 // check to see if we have a map for this one
486 if (_lookups
.StoreEdmPropertyToModelEdmProperty
.ContainsKey(storeProperty
))
488 EdmProperty modelProperty
= _lookups
.StoreEdmPropertyToModelEdmProperty
[storeProperty
];
489 WriteScalarPropertyElement(writer
, storeProperty
, modelProperty
);
492 writer
.WriteEndElement();
495 private void WriteScalarPropertyElement(XmlWriter writer
, EdmProperty store
, EdmProperty model
)
497 Debug
.Assert(store
.TypeUsage
.EdmType
.BuiltInTypeKind
== BuiltInTypeKind
.PrimitiveType
, "only expect scalar type properties");
498 Debug
.Assert(model
.TypeUsage
.EdmType
.BuiltInTypeKind
== BuiltInTypeKind
.PrimitiveType
, "only expect scalar type properties");
500 writer
.WriteStartElement(StorageMslConstructs
.ScalarPropertyElement
, _xmlNamespace
);
501 writer
.WriteAttributeString(StorageMslConstructs
.ScalarPropertyNameAttribute
, model
.Name
);
502 writer
.WriteAttributeString(StorageMslConstructs
.ScalarPropertyColumnNameAttribute
, store
.Name
);
503 writer
.WriteEndElement();
506 private void WriteMappingStartElement(XmlWriter writer
)
508 writer
.WriteStartElement(StorageMslConstructs
.MappingElement
, _xmlNamespace
);
509 writer
.WriteAttributeString(StorageMslConstructs
.MappingSpaceAttribute
, "C-S");