|
//---------------------------------------------------------------------
// <copyright file="ReferentialConstraint.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner Microsoft
// @backupOwner Microsoft
//---------------------------------------------------------------------
namespace System.Data.EntityModel.SchemaObjectModel
{
using System.Data.Metadata.Edm;
using System.Diagnostics;
using System.Xml;
/// <summary>
/// Represents an referential constraint on a relationship
/// </summary>
internal sealed class ReferentialConstraint : SchemaElement
{
private const char KEY_DELIMITER = ' ';
private ReferentialConstraintRoleElement _principalRole;
private ReferentialConstraintRoleElement _dependentRole;
/// <summary>
/// construct a Referential constraint
/// </summary>
/// <param name="relationship"></param>
public ReferentialConstraint(Relationship relationship)
: base(relationship)
{
}
/// <summary>
/// Validate this referential constraint
/// </summary>
internal override void Validate()
{
base.Validate();
_principalRole.Validate();
_dependentRole.Validate();
if (ReadyForFurtherValidation(_principalRole) && ReadyForFurtherValidation(_dependentRole))
{
// Validate the to end and from end of the referential constraint
IRelationshipEnd principalRoleEnd = _principalRole.End;
IRelationshipEnd dependentRoleEnd = _dependentRole.End;
bool isPrinicipalRoleKeyProperty, isDependentRoleKeyProperty;
bool areAllPrinicipalRolePropertiesNullable, areAllDependentRolePropertiesNullable;
bool isDependentRolePropertiesSubsetofKeyProperties, isPrinicipalRolePropertiesSubsetofKeyProperties;
bool isAnyPrinicipalRolePropertyNullable, isAnyDependentRolePropertyNullable;
// Validate the role name to be different
if (_principalRole.Name == _dependentRole.Name)
{
AddError(ErrorCode.SameRoleReferredInReferentialConstraint,
EdmSchemaErrorSeverity.Error,
System.Data.Entity.Strings.SameRoleReferredInReferentialConstraint(this.ParentElement.Name));
}
// Resolve all the property in the ToProperty attribute. Also checks whether this is nullable or not and
// whether the properties are the keys for the type in the ToRole
IsKeyProperty(_dependentRole, dependentRoleEnd.Type,
out isPrinicipalRoleKeyProperty,
out areAllDependentRolePropertiesNullable,
out isAnyDependentRolePropertyNullable,
out isDependentRolePropertiesSubsetofKeyProperties);
// Resolve all the property in the ToProperty attribute. Also checks whether this is nullable or not and
// whether the properties are the keys for the type in the ToRole
IsKeyProperty(_principalRole, principalRoleEnd.Type,
out isDependentRoleKeyProperty,
out areAllPrinicipalRolePropertiesNullable,
out isAnyPrinicipalRolePropertyNullable,
out isPrinicipalRolePropertiesSubsetofKeyProperties);
Debug.Assert(_principalRole.RoleProperties.Count != 0, "There should be some ref properties in Principal Role");
Debug.Assert(_dependentRole.RoleProperties.Count != 0, "There should be some ref properties in Dependent Role");
// The properties in the PrincipalRole must be the key of the Entity type referred to by the principal role
if (!isDependentRoleKeyProperty)
{
AddError(ErrorCode.InvalidPropertyInRelationshipConstraint,
EdmSchemaErrorSeverity.Error,
System.Data.Entity.Strings.InvalidFromPropertyInRelationshipConstraint(
PrincipalRole.Name, principalRoleEnd.Type.FQName, this.ParentElement.FQName));
}
else
{
bool v1Behavior = Schema.SchemaVersion <= XmlConstants.EdmVersionForV1_1;
// Determine expected multiplicities
RelationshipMultiplicity expectedPrincipalMultiplicity = (v1Behavior
? areAllPrinicipalRolePropertiesNullable
: isAnyPrinicipalRolePropertyNullable)
? RelationshipMultiplicity.ZeroOrOne
: RelationshipMultiplicity.One;
RelationshipMultiplicity expectedDependentMultiplicity = (v1Behavior
? areAllDependentRolePropertiesNullable
: isAnyDependentRolePropertyNullable)
? RelationshipMultiplicity.ZeroOrOne
: RelationshipMultiplicity.Many;
principalRoleEnd.Multiplicity = principalRoleEnd.Multiplicity ?? expectedPrincipalMultiplicity;
dependentRoleEnd.Multiplicity = dependentRoleEnd.Multiplicity ?? expectedDependentMultiplicity;
// Since the FromProperty must be the key of the FromRole, the FromRole cannot be '*' as multiplicity
// Also the lower bound of multiplicity of FromRole can be zero if and only if all the properties in
// ToProperties are nullable
// for v2+
if (principalRoleEnd.Multiplicity == RelationshipMultiplicity.Many)
{
AddError(ErrorCode.InvalidMultiplicityInRoleInRelationshipConstraint,
EdmSchemaErrorSeverity.Error,
System.Data.Entity.Strings.InvalidMultiplicityFromRoleUpperBoundMustBeOne(_principalRole.Name, this.ParentElement.Name));
}
else if (areAllDependentRolePropertiesNullable
&& principalRoleEnd.Multiplicity == RelationshipMultiplicity.One)
{
string message = System.Data.Entity.Strings.InvalidMultiplicityFromRoleToPropertyNullableV1(_principalRole.Name, this.ParentElement.Name);
AddError(ErrorCode.InvalidMultiplicityInRoleInRelationshipConstraint,
EdmSchemaErrorSeverity.Error,
message);
}
else if ((
(v1Behavior && !areAllDependentRolePropertiesNullable) ||
(!v1Behavior && !isAnyDependentRolePropertyNullable)
)
&& principalRoleEnd.Multiplicity != RelationshipMultiplicity.One)
{
string message;
if (v1Behavior)
{
message = System.Data.Entity.Strings.InvalidMultiplicityFromRoleToPropertyNonNullableV1(_principalRole.Name, this.ParentElement.Name);
}
else
{
message = System.Data.Entity.Strings.InvalidMultiplicityFromRoleToPropertyNonNullableV2(_principalRole.Name, this.ParentElement.Name);
}
AddError(ErrorCode.InvalidMultiplicityInRoleInRelationshipConstraint,
EdmSchemaErrorSeverity.Error,
message);
}
// If the ToProperties form the key of the type in ToRole, then the upper bound of the multiplicity
// of the ToRole must be '1'. The lower bound must always be zero since there can be entries in the from
// column which are not related to child columns.
if (dependentRoleEnd.Multiplicity == RelationshipMultiplicity.One && Schema.DataModel == SchemaDataModelOption.ProviderDataModel)
{
AddError(ErrorCode.InvalidMultiplicityInRoleInRelationshipConstraint,
EdmSchemaErrorSeverity.Error,
System.Data.Entity.Strings.InvalidMultiplicityToRoleLowerBoundMustBeZero(_dependentRole.Name, this.ParentElement.Name));
}
// Need to constrain the dependent role in CSDL to Key properties if this is not a IsForeignKey
// relationship.
if ((!isDependentRolePropertiesSubsetofKeyProperties) &&
(!this.ParentElement.IsForeignKey) &&
(Schema.DataModel == SchemaDataModelOption.EntityDataModel))
{
AddError(ErrorCode.InvalidPropertyInRelationshipConstraint,
EdmSchemaErrorSeverity.Error,
System.Data.Entity.Strings.InvalidToPropertyInRelationshipConstraint(
DependentRole.Name, dependentRoleEnd.Type.FQName, this.ParentElement.FQName));
}
// If the ToProperty is a key property, then the upper bound must be 1 i.e. every parent (from property) can
// have exactly one child
if (isPrinicipalRoleKeyProperty)
{
if (dependentRoleEnd.Multiplicity == RelationshipMultiplicity.Many)
{
AddError(ErrorCode.InvalidMultiplicityInRoleInRelationshipConstraint,
EdmSchemaErrorSeverity.Error,
System.Data.Entity.Strings.InvalidMultiplicityToRoleUpperBoundMustBeOne(dependentRoleEnd.Name, this.ParentElement.Name));
}
}
// if the ToProperty is not the key, then the upper bound must be many i.e every parent (from property) can
// be related to many childs
else if (dependentRoleEnd.Multiplicity != RelationshipMultiplicity.Many)
{
AddError(ErrorCode.InvalidMultiplicityInRoleInRelationshipConstraint,
EdmSchemaErrorSeverity.Error,
System.Data.Entity.Strings.InvalidMultiplicityToRoleUpperBoundMustBeMany(dependentRoleEnd.Name, this.ParentElement.Name));
}
if (_dependentRole.RoleProperties.Count != _principalRole.RoleProperties.Count)
{
AddError(ErrorCode.MismatchNumberOfPropertiesInRelationshipConstraint,
EdmSchemaErrorSeverity.Error,
System.Data.Entity.Strings.MismatchNumberOfPropertiesinRelationshipConstraint);
}
else
{
for (int i = 0; i < _dependentRole.RoleProperties.Count; i++)
{
if (_dependentRole.RoleProperties[i].Property.Type != _principalRole.RoleProperties[i].Property.Type)
{
AddError(ErrorCode.TypeMismatchRelationshipConstaint,
EdmSchemaErrorSeverity.Error,
System.Data.Entity.Strings.TypeMismatchRelationshipConstaint(
_dependentRole.RoleProperties[i].Name,
_dependentRole.End.Type.Identity,
_principalRole.RoleProperties[i].Name,
_principalRole.End.Type.Identity,
this.ParentElement.Name
));
}
}
}
}
}
}
private static bool ReadyForFurtherValidation(ReferentialConstraintRoleElement role)
{
if (role == null)
return false;
if(role.End == null)
return false;
if(role.RoleProperties.Count == 0)
return false;
foreach(PropertyRefElement propRef in role.RoleProperties)
{
if(propRef.Property == null)
return false;
}
return true;
}
/// <summary>
/// Resolves the given property names to the property in the item
/// Also checks whether the properties form the key for the given type and whether all the properties are nullable or not
/// </summary>
/// <param name="roleElement"></param>
/// <param name="itemType"></param>
/// <param name="isKeyProperty"></param>
/// <param name="areAllPropertiesNullable"></param>
/// <param name="isSubsetOfKeyProperties"></param>
private static void IsKeyProperty(ReferentialConstraintRoleElement roleElement, SchemaEntityType itemType,
out bool isKeyProperty,
out bool areAllPropertiesNullable,
out bool isAnyPropertyNullable,
out bool isSubsetOfKeyProperties)
{
isKeyProperty = true;
areAllPropertiesNullable = true;
isAnyPropertyNullable = false;
isSubsetOfKeyProperties = true;
if (itemType.KeyProperties.Count != roleElement.RoleProperties.Count)
{
isKeyProperty = false;
}
// Checking that ToProperties must be the key properties in the entity type referred by the ToRole
for (int i = 0; i < roleElement.RoleProperties.Count; i++)
{
// Once we find that the properties in the constraint are not a subset of the
// Key, one need not search for it every time
if (isSubsetOfKeyProperties)
{
bool foundKeyProperty = false;
// All properties that are defined in ToProperties must be the key property on the entity type
for (int j = 0; j < itemType.KeyProperties.Count; j++)
{
if (itemType.KeyProperties[j].Property == roleElement.RoleProperties[i].Property)
{
foundKeyProperty = true;
break;
}
}
if (!foundKeyProperty)
{
isKeyProperty = false;
isSubsetOfKeyProperties = false;
}
}
areAllPropertiesNullable &= roleElement.RoleProperties[i].Property.Nullable;
isAnyPropertyNullable |= roleElement.RoleProperties[i].Property.Nullable;
}
}
protected override bool HandleAttribute(XmlReader reader)
{
return false;
}
protected override bool HandleElement(XmlReader reader)
{
if (base.HandleElement(reader))
{
return true;
}
else if (CanHandleElement(reader, XmlConstants.PrincipalRole))
{
HandleReferentialConstraintPrincipalRoleElement(reader);
return true;
}
else if (CanHandleElement(reader, XmlConstants.DependentRole))
{
HandleReferentialConstraintDependentRoleElement(reader);
return true;
}
return false;
}
internal void HandleReferentialConstraintPrincipalRoleElement(XmlReader reader)
{
_principalRole = new ReferentialConstraintRoleElement(this);
_principalRole.Parse(reader);
}
internal void HandleReferentialConstraintDependentRoleElement(XmlReader reader)
{
_dependentRole = new ReferentialConstraintRoleElement(this);
_dependentRole.Parse(reader);
}
internal override void ResolveTopLevelNames()
{
_dependentRole.ResolveTopLevelNames();
_principalRole.ResolveTopLevelNames();
}
/// <summary>
/// The parent element as an IRelationship
/// </summary>
internal new IRelationship ParentElement
{
get
{
return (IRelationship)(base.ParentElement);
}
}
internal ReferentialConstraintRoleElement PrincipalRole
{
get
{
return _principalRole;
}
}
internal ReferentialConstraintRoleElement DependentRole
{
get
{
return _dependentRole;
}
}
}
}
|