Updates referencesource to .NET 4.7
[mono.git] / mcs / class / referencesource / System.Data.Entity / System / Data / EntityModel / SchemaObjectModel / ReferentialConstraint.cs
1 //---------------------------------------------------------------------
2 // <copyright file="ReferentialConstraint.cs" company="Microsoft">
3 //      Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //
6 // @owner       Microsoft
7 // @backupOwner Microsoft
8 //---------------------------------------------------------------------
9
10 namespace System.Data.EntityModel.SchemaObjectModel
11 {
12     using System.Data.Metadata.Edm;
13     using System.Diagnostics;
14     using System.Xml;
15
16     /// <summary>
17     /// Represents an referential constraint on a relationship
18     /// </summary>
19     internal sealed class ReferentialConstraint : SchemaElement
20     {
21         private const char KEY_DELIMITER = ' ';
22         private ReferentialConstraintRoleElement _principalRole;
23         private ReferentialConstraintRoleElement _dependentRole;
24
25         /// <summary>
26         /// construct a Referential constraint
27         /// </summary>
28         /// <param name="relationship"></param>
29         public ReferentialConstraint(Relationship relationship)
30             : base(relationship)
31         {
32         }
33
34         /// <summary>
35         /// Validate this referential constraint
36         /// </summary>
37         internal override void Validate()
38         {
39             base.Validate();
40             _principalRole.Validate();
41             _dependentRole.Validate();
42
43             if (ReadyForFurtherValidation(_principalRole) && ReadyForFurtherValidation(_dependentRole))
44             {
45                 // Validate the to end and from end of the referential constraint
46                 IRelationshipEnd principalRoleEnd = _principalRole.End;
47                 IRelationshipEnd dependentRoleEnd = _dependentRole.End;
48
49                 bool isPrinicipalRoleKeyProperty, isDependentRoleKeyProperty;
50                 bool areAllPrinicipalRolePropertiesNullable, areAllDependentRolePropertiesNullable;
51                 bool isDependentRolePropertiesSubsetofKeyProperties, isPrinicipalRolePropertiesSubsetofKeyProperties;
52                 bool isAnyPrinicipalRolePropertyNullable, isAnyDependentRolePropertyNullable;
53
54                 // Validate the role name to be different
55                 if (_principalRole.Name == _dependentRole.Name)
56                 {
57                     AddError(ErrorCode.SameRoleReferredInReferentialConstraint,
58                              EdmSchemaErrorSeverity.Error,
59                              System.Data.Entity.Strings.SameRoleReferredInReferentialConstraint(this.ParentElement.Name));
60                 }
61
62                 // Resolve all the property in the ToProperty attribute. Also checks whether this is nullable or not and 
63                 // whether the properties are the keys for the type in the ToRole
64                 IsKeyProperty(_dependentRole, dependentRoleEnd.Type, 
65                     out isPrinicipalRoleKeyProperty, 
66                     out areAllDependentRolePropertiesNullable, 
67                     out isAnyDependentRolePropertyNullable,
68                     out isDependentRolePropertiesSubsetofKeyProperties);
69
70                 // Resolve all the property in the ToProperty attribute. Also checks whether this is nullable or not and 
71                 // whether the properties are the keys for the type in the ToRole
72                 IsKeyProperty(_principalRole, principalRoleEnd.Type, 
73                     out isDependentRoleKeyProperty, 
74                     out areAllPrinicipalRolePropertiesNullable,
75                     out isAnyPrinicipalRolePropertyNullable,
76                     out isPrinicipalRolePropertiesSubsetofKeyProperties);
77
78                 Debug.Assert(_principalRole.RoleProperties.Count != 0, "There should be some ref properties in Principal Role");
79                 Debug.Assert(_dependentRole.RoleProperties.Count != 0, "There should be some ref properties in Dependent Role");
80
81                 // The properties in the PrincipalRole must be the key of the Entity type referred to by the principal role
82                 if (!isDependentRoleKeyProperty)
83                 {
84                     AddError(ErrorCode.InvalidPropertyInRelationshipConstraint,
85                              EdmSchemaErrorSeverity.Error,
86                              System.Data.Entity.Strings.InvalidFromPropertyInRelationshipConstraint(
87                              PrincipalRole.Name, principalRoleEnd.Type.FQName, this.ParentElement.FQName));
88                 }
89                 else
90                 {
91                     bool v1Behavior = Schema.SchemaVersion <= XmlConstants.EdmVersionForV1_1;
92
93                     // Determine expected multiplicities
94                     RelationshipMultiplicity expectedPrincipalMultiplicity = (v1Behavior 
95                         ? areAllPrinicipalRolePropertiesNullable
96                         : isAnyPrinicipalRolePropertyNullable)
97                         ? RelationshipMultiplicity.ZeroOrOne
98                         : RelationshipMultiplicity.One;
99                     RelationshipMultiplicity expectedDependentMultiplicity = (v1Behavior
100                         ? areAllDependentRolePropertiesNullable
101                         : isAnyDependentRolePropertyNullable)
102                         ? RelationshipMultiplicity.ZeroOrOne
103                         : RelationshipMultiplicity.Many;
104                     principalRoleEnd.Multiplicity = principalRoleEnd.Multiplicity ?? expectedPrincipalMultiplicity;
105                     dependentRoleEnd.Multiplicity = dependentRoleEnd.Multiplicity ?? expectedDependentMultiplicity;
106
107                     // Since the FromProperty must be the key of the FromRole, the FromRole cannot be '*' as multiplicity
108                     // Also the lower bound of multiplicity of FromRole can be zero if and only if all the properties in 
109                     // ToProperties are nullable
110                     // for v2+
111                     if (principalRoleEnd.Multiplicity == RelationshipMultiplicity.Many)
112                     {
113                         AddError(ErrorCode.InvalidMultiplicityInRoleInRelationshipConstraint,
114                                  EdmSchemaErrorSeverity.Error,
115                                  System.Data.Entity.Strings.InvalidMultiplicityFromRoleUpperBoundMustBeOne(_principalRole.Name, this.ParentElement.Name));
116                     }
117                     else if (areAllDependentRolePropertiesNullable
118                             && principalRoleEnd.Multiplicity == RelationshipMultiplicity.One)
119                     {
120                         string message = System.Data.Entity.Strings.InvalidMultiplicityFromRoleToPropertyNullableV1(_principalRole.Name, this.ParentElement.Name);
121                         AddError(ErrorCode.InvalidMultiplicityInRoleInRelationshipConstraint,
122                                  EdmSchemaErrorSeverity.Error,
123                                  message);
124                     }
125                     else if ((
126                                 (v1Behavior && !areAllDependentRolePropertiesNullable) ||
127                                 (!v1Behavior && !isAnyDependentRolePropertyNullable)
128                              )
129                             && principalRoleEnd.Multiplicity != RelationshipMultiplicity.One)
130                     {
131                         string message;
132                         if (v1Behavior)
133                         {
134                             message = System.Data.Entity.Strings.InvalidMultiplicityFromRoleToPropertyNonNullableV1(_principalRole.Name, this.ParentElement.Name);
135                         }
136                         else
137                         {
138                             message = System.Data.Entity.Strings.InvalidMultiplicityFromRoleToPropertyNonNullableV2(_principalRole.Name, this.ParentElement.Name);
139                         }
140                         AddError(ErrorCode.InvalidMultiplicityInRoleInRelationshipConstraint,
141                                  EdmSchemaErrorSeverity.Error,
142                                  message);
143                     }
144
145                     // If the ToProperties form the key of the type in ToRole, then the upper bound of the multiplicity 
146                     // of the ToRole must be '1'. The lower bound must always be zero since there can be entries in the from
147                     // column which are not related to child columns.
148                     if (dependentRoleEnd.Multiplicity == RelationshipMultiplicity.One && Schema.DataModel == SchemaDataModelOption.ProviderDataModel)
149                     {
150                         AddError(ErrorCode.InvalidMultiplicityInRoleInRelationshipConstraint,
151                                  EdmSchemaErrorSeverity.Error,
152                                  System.Data.Entity.Strings.InvalidMultiplicityToRoleLowerBoundMustBeZero(_dependentRole.Name, this.ParentElement.Name));
153                     }
154
155                     // Need to constrain the dependent role in CSDL to Key properties if this is not a IsForeignKey
156                     // relationship.
157                     if ((!isDependentRolePropertiesSubsetofKeyProperties) &&
158                         (!this.ParentElement.IsForeignKey) &&
159                         (Schema.DataModel == SchemaDataModelOption.EntityDataModel))
160                     {
161                         AddError(ErrorCode.InvalidPropertyInRelationshipConstraint,
162                                  EdmSchemaErrorSeverity.Error,
163                                  System.Data.Entity.Strings.InvalidToPropertyInRelationshipConstraint(
164                                  DependentRole.Name, dependentRoleEnd.Type.FQName, this.ParentElement.FQName));
165
166                     }
167
168                     // If the ToProperty is a key property, then the upper bound must be 1 i.e. every parent (from property) can 
169                     // have exactly one child
170                     if (isPrinicipalRoleKeyProperty)
171                     {
172                         if (dependentRoleEnd.Multiplicity == RelationshipMultiplicity.Many)
173                         {
174                             AddError(ErrorCode.InvalidMultiplicityInRoleInRelationshipConstraint,
175                                      EdmSchemaErrorSeverity.Error,
176                                      System.Data.Entity.Strings.InvalidMultiplicityToRoleUpperBoundMustBeOne(dependentRoleEnd.Name, this.ParentElement.Name));
177                         }
178                     }
179                     // if the ToProperty is not the key, then the upper bound must be many i.e every parent (from property) can
180                     // be related to many childs
181                     else if (dependentRoleEnd.Multiplicity != RelationshipMultiplicity.Many)
182                     {
183                         AddError(ErrorCode.InvalidMultiplicityInRoleInRelationshipConstraint,
184                                      EdmSchemaErrorSeverity.Error,
185                                      System.Data.Entity.Strings.InvalidMultiplicityToRoleUpperBoundMustBeMany(dependentRoleEnd.Name, this.ParentElement.Name));
186                     }
187
188                     if (_dependentRole.RoleProperties.Count != _principalRole.RoleProperties.Count)
189                     {
190                         AddError(ErrorCode.MismatchNumberOfPropertiesInRelationshipConstraint,
191                                  EdmSchemaErrorSeverity.Error,
192                                  System.Data.Entity.Strings.MismatchNumberOfPropertiesinRelationshipConstraint);
193                     }
194                     else
195                     {
196                         for (int i = 0; i < _dependentRole.RoleProperties.Count; i++)
197                         {
198                             if (_dependentRole.RoleProperties[i].Property.Type != _principalRole.RoleProperties[i].Property.Type)
199                             {
200                                 AddError(ErrorCode.TypeMismatchRelationshipConstaint,
201                                          EdmSchemaErrorSeverity.Error,
202                                          System.Data.Entity.Strings.TypeMismatchRelationshipConstaint(
203                                                        _dependentRole.RoleProperties[i].Name,
204                                                        _dependentRole.End.Type.Identity,
205                                                        _principalRole.RoleProperties[i].Name,
206                                                        _principalRole.End.Type.Identity,
207                                                        this.ParentElement.Name
208                                                        ));
209                             }
210                         }
211                     }
212                 }
213             }
214         }
215
216         private static bool ReadyForFurtherValidation(ReferentialConstraintRoleElement role)
217         {
218             if (role == null)
219                 return false;
220
221             if(role.End == null)
222                 return false;
223
224             if(role.RoleProperties.Count == 0)
225                 return false;
226
227             foreach(PropertyRefElement propRef in role.RoleProperties)
228             {
229                 if(propRef.Property == null)
230                     return false;
231             }
232
233             return true;
234         }
235
236         /// <summary>
237         /// Resolves the given property names to the property in the item
238         /// Also checks whether the properties form the key for the given type and whether all the properties are nullable or not
239         /// </summary>
240         /// <param name="roleElement"></param>
241         /// <param name="itemType"></param>
242         /// <param name="isKeyProperty"></param>
243         /// <param name="areAllPropertiesNullable"></param>
244         /// <param name="isSubsetOfKeyProperties"></param>
245         private static void IsKeyProperty(ReferentialConstraintRoleElement roleElement, SchemaEntityType itemType,
246             out bool isKeyProperty,
247             out bool areAllPropertiesNullable,
248             out bool isAnyPropertyNullable,
249             out bool isSubsetOfKeyProperties)
250         {
251             isKeyProperty = true;
252             areAllPropertiesNullable = true;
253             isAnyPropertyNullable = false;
254             isSubsetOfKeyProperties = true;
255
256             if (itemType.KeyProperties.Count != roleElement.RoleProperties.Count)
257             {
258                 isKeyProperty = false;
259             }
260
261             // Checking that ToProperties must be the key properties in the entity type referred by the ToRole
262             for (int i = 0; i < roleElement.RoleProperties.Count; i++)
263             {
264                 // Once we find that the properties in the constraint are not a subset of the
265                 // Key, one need not search for it every time
266                 if (isSubsetOfKeyProperties)
267                 {
268
269                     bool foundKeyProperty = false;
270
271                     // All properties that are defined in ToProperties must be the key property on the entity type
272                     for (int j = 0; j < itemType.KeyProperties.Count; j++)
273                     {
274                         if (itemType.KeyProperties[j].Property == roleElement.RoleProperties[i].Property)
275                         {
276                             foundKeyProperty = true;
277                             break;
278                         }
279                     }
280
281                     if (!foundKeyProperty)
282                     {
283                         isKeyProperty = false;
284                         isSubsetOfKeyProperties = false;
285                     }
286                 }
287
288                 areAllPropertiesNullable &= roleElement.RoleProperties[i].Property.Nullable;
289                 isAnyPropertyNullable |= roleElement.RoleProperties[i].Property.Nullable;
290             }
291         }
292
293         protected override bool HandleAttribute(XmlReader reader)
294         {
295             return false;
296         }
297
298         protected override bool HandleElement(XmlReader reader)
299         {
300             if (base.HandleElement(reader))
301             {
302                 return true;
303             }
304             else if (CanHandleElement(reader, XmlConstants.PrincipalRole))
305             {
306                 HandleReferentialConstraintPrincipalRoleElement(reader);
307                 return true;
308             }
309             else if (CanHandleElement(reader, XmlConstants.DependentRole))
310             {
311                 HandleReferentialConstraintDependentRoleElement(reader);
312                 return true;
313             }
314
315             return false;
316         }
317
318         internal void HandleReferentialConstraintPrincipalRoleElement(XmlReader reader)
319         {
320             _principalRole = new ReferentialConstraintRoleElement(this);
321             _principalRole.Parse(reader);
322         }
323
324         internal void HandleReferentialConstraintDependentRoleElement(XmlReader reader)
325         {
326             _dependentRole = new ReferentialConstraintRoleElement(this);
327             _dependentRole.Parse(reader);
328         }
329
330         internal override void ResolveTopLevelNames()
331         {
332             _dependentRole.ResolveTopLevelNames();
333
334             _principalRole.ResolveTopLevelNames();
335         }
336
337         /// <summary>
338         /// The parent element as an IRelationship
339         /// </summary>
340         internal new IRelationship ParentElement
341         {
342             get
343             {
344                 return (IRelationship)(base.ParentElement);
345             }
346         }
347
348         internal ReferentialConstraintRoleElement PrincipalRole
349         {
350             get
351             {
352                 return _principalRole;
353             }
354         }
355
356         internal ReferentialConstraintRoleElement DependentRole
357         {
358             get
359             {
360                 return _dependentRole;
361             }
362         }
363     }
364 }