ForeignConstraint.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ Dotnetfx_Vista_SP2 / Dotnetfx_Vista_SP2 / 8.0.50727.4016 / DEVDIV / depot / DevDiv / releases / Orcas / QFE / ndp / fx / src / DataEntity / System / Data / Map / ViewGeneration / Validation / ForeignConstraint.cs / 2 / ForeignConstraint.cs

                            //---------------------------------------------------------------------- 
// 
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// 
// 
// @owner [....]
// @backupOwner [....] 
//--------------------------------------------------------------------- 

using System.Data.Common.Utils; 
using System.Data.Common.Utils.Boolean;
using System.Data.Mapping.ViewGeneration.Structures;
using System.Data.Mapping.ViewGeneration.Utils;
using System.Data.Mapping.ViewGeneration.QueryRewriting; 
using System.Collections.Generic;
using System.Text; 
using System.Diagnostics; 
using System.Data.Metadata.Edm;
 
namespace System.Data.Mapping.ViewGeneration.Validation {

    using WrapperBoolExpr    = BoolExpr;
    using WrapperOrExpr      = OrExpr; 
    using WrapperNotExpr     = NotExpr;
    using WrapperTermExpr    = TermExpr; 
 
    // An abstraction that captures a foreign key constraint:
    //  -->  
    internal class ForeignConstraint : InternalBase {

        #region Constructor
        // effects: Creates a foreign key constraint of the form: 
        //  --> 
        // i_fkeySet is the name of the constraint 
        internal ForeignConstraint(AssociationSet i_fkeySet, EntitySet i_parentTable, EntitySet i_childTable, 
                                   ReadOnlyMetadataCollection i_parentColumns, ReadOnlyMetadataCollection i_childColumns, MetadataWorkspace workspace) {
            m_fKeySet = i_fkeySet; 
            m_parentTable = i_parentTable;
            m_childTable = i_childTable;
            m_childColumns = new List();
            // Create parent and child paths using the table names 
            foreach (EdmProperty property in i_childColumns) {
                MemberPath path = new MemberPath(m_childTable, property, workspace); 
                m_childColumns.Add(path); 
            }
 
            m_parentColumns = new List();
            foreach (EdmProperty property in i_parentColumns) {
                MemberPath path = new MemberPath(m_parentTable, property, workspace);
                m_parentColumns.Add(path); 
            }
        } 
        #endregion 

        #region Fields 
        private AssociationSet m_fKeySet; // Just for debugging
        private EntitySet m_parentTable;
        private EntitySet m_childTable;
        private List m_parentColumns; 
        private List m_childColumns;
        #endregion 
 
        #region Properties
        internal EntitySet ParentTable { 
            get { return m_parentTable;}
        }

        internal EntitySet ChildTable { 
            get { return m_childTable;}
        } 
 
        internal IEnumerable ChildColumns {
            get { return m_childColumns;} 
        }

        internal IEnumerable ParentColumns {
            get { return m_parentColumns;} 
        }
        #endregion 
 
        #region Externally available Methods
        // effects: Given a store-side container, returns all the foreign key 
        // constraints specified for different tables
        internal static List GetForeignConstraints(EntityContainer container, MetadataWorkspace workspace) {
            List foreignKeyConstraints = new List();
 
            // Go through all the extents and get the associations
            foreach (EntitySetBase extent in container.BaseEntitySets) { 
                AssociationSet relationSet = extent as AssociationSet; 

                if (relationSet == null) continue; 
                // Keep track of the end to EntitySet mapping
                Dictionary endToExtents = new Dictionary();

                foreach (AssociationSetEnd  end in relationSet.AssociationSetEnds) { 
                    endToExtents.Add(end.Name, end.EntitySet);
                } 
 
                AssociationType relationType = relationSet.ElementType;
                // Go through each referential constraint, determine the name 
                // of the tables that the constraint refers to and then
                // create the foreign key constraint between the tables
                // Wow! We go to great lengths to make it cumbersome for a
                // programmer to deal with foreign keys 
                foreach (ReferentialConstraint constraint in relationType.ReferentialConstraints) {
                    // Note: We are correlating the constraint's roles with 
                    // the ends above using the role names, i.e., 
                    // FromRole.Name and ToRole.Name here and end.Role above
                    EntitySet parentExtent = endToExtents[constraint.FromRole.Name]; 
                    EntitySet childExtent = endToExtents[constraint.ToRole.Name];
                    ForeignConstraint foreignKeyConstraint = new ForeignConstraint(relationSet, parentExtent, childExtent,
                                                                       constraint.FromProperties, constraint.ToProperties, workspace);
                    foreignKeyConstraints.Add(foreignKeyConstraint); 

                } 
            } 
            return foreignKeyConstraints;
        } 

        // requires: childNormalizer and parentNormalizer correspond to the
        // normalizers for ChildTable and ParentTable respectively (or null
        // if none exist) 
        // effects: Checks that this foreign key constraints for all the
        // tables are being ensured on the C-side as well. If not, adds 
        // errors to the errorLog 
        internal void CheckConstraint(Set cells, QueryRewriter childRewriter, QueryRewriter parentRewriter,
                                      ErrorLog errorLog, ConfigViewGenerator config, MetadataWorkspace workspace) { 
            if (IsConstraintRelevantForCells(cells) == false) {
                // if the constraint does not deal with any cell in this group, ignore it
                return;
            } 

            if (config.IsNormalTracing) { 
                Trace.WriteLine(String.Empty); 
                Trace.WriteLine(String.Empty);
                Trace.Write("Checking: "); 
                Trace.WriteLine(this);
            }

            if (childRewriter == null && parentRewriter == null) { 
                // Neither table is mapped - so we are fine
                return; 
            } 

            // If the child table has not been mapped, we used to say that we 
            // are fine. However, if we have SPerson(pid) and SAddress(aid,
            // pid), where pid is an FK into SPerson, we are in trouble if
            // SAddress is not mapped - SPerson could get deleted. So we
            // check for it as well 
            // if the parent table is not mapped, we also have a problem
 
            if (childRewriter == null) { 
                string message = System.Data.Entity.Strings.ViewGen_Foreign_Key_Missing_Table_Mapping_1(
                                               ToUserString(), ChildTable.Name); 
                // Get the cells from the parent table
                ErrorLog.Record record = new ErrorLog.Record(true, ViewGenErrorCode.ForeignKeyMissingTableMapping, message, parentRewriter.UsedCells, String.Empty);
                errorLog.AddEntry(record);
                return; 
            }
 
            if (parentRewriter == null) { 
                string message = System.Data.Entity.Strings.ViewGen_Foreign_Key_Missing_Table_Mapping_1(
                                               ToUserString(), ParentTable.Name); 
                // Get the cells from the child table
                ErrorLog.Record record = new ErrorLog.Record(true, ViewGenErrorCode.ForeignKeyMissingTableMapping, message, childRewriter.UsedCells, String.Empty);
                errorLog.AddEntry(record);
                return; 
            }
 
            // Note: we do not check if the parent columns correspond to the 
            // table's keys - metadata checks for that
 
            // Check if the foreign key in the child table corresponds to the primary key, i.e., if
            // the foreign key (e.g., pid, pid2) is a superset of the actual key members (e.g., pid), it means
            // that the foreign key is also the primary key for this table -- so we can propagate the queries upto C-Space
            // rather than doing the cell check 

            int initialErrorLogSize = errorLog.Count; 
            if (IsForeignKeySuperSetOfPrimaryKeyInChildTable()) { 
                GuaranteeForeignKeyConstraintInCSpace(childRewriter, parentRewriter, errorLog, config);
            } else { 
                GuaranteeMappedRelationshipForForeignKey(childRewriter, parentRewriter, cells, errorLog, config, workspace);
            }

            if (initialErrorLogSize == errorLog.Count) { 
                // Check if the order of columns in foreign key correponds to the
                // mappings in m_cellGroup, e.g., if  in SAddress is 
                // a foreign key into  of the SPerson table, make 
                // sure that this order is preserved through the mappings in m_cellGroup
                CheckForeignKeyColumnOrder(cells, errorLog); 
            }
        }
        #endregion
 
        #region Methods (mostly) for Query Containment Check via Keys
        // requires: constraint.ChildColumns form a key in 
        // constraint.ChildTable (actually they should subsume the primary key) 
        // The normalizer for both parent and child is present in m_cellNormalizerMap
        // effects: Projects the constraint into C-space and checks if it is 
        // being preserved in C-space -- if not, adds an error to m_errorLog
        private void GuaranteeForeignKeyConstraintInCSpace(QueryRewriter childRewriter, QueryRewriter parentRewriter,
                                                           ErrorLog errorLog, ConfigViewGenerator config) {
            CellNormalizer childNormalizer = childRewriter.Normalizer; 
            CellNormalizer parentNormalizer = parentRewriter.Normalizer;
            CellTreeNode cNode = childRewriter.BasicView; 
            CellTreeNode pNode = parentRewriter.BasicView; 

            FragmentQueryProcessor qp = FragmentQueryProcessor.Merge(childNormalizer.RightFragmentQP, parentNormalizer.RightFragmentQP); 
            bool cImpliesP = qp.IsContainedIn(cNode.RightFragmentQuery, pNode.RightFragmentQuery);

            if (false == cImpliesP)
            { 
                // Foreign key constraint not being ensured in C-space
                string childExtents = LeftCellWrapper.GetExtentListAsUserString(cNode.GetLeaves()); 
                string parentExtents = LeftCellWrapper.GetExtentListAsUserString(pNode.GetLeaves()); 
                string message = System.Data.Entity.Strings.ViewGen_Foreign_Key_Not_Guaranteed_InCSpace_0(
                                               ToUserString()); 
                // Add all wrappers into allWrappers
                Set allWrappers = new Set(pNode.GetLeaves());
                allWrappers.AddRange(cNode.GetLeaves());
                ErrorLog.Record record = new ErrorLog.Record(true, ViewGenErrorCode.ForeignKeyNotGuaranteedInCSpace, message, allWrappers, String.Empty); 
                errorLog.AddEntry(record);
            } 
        } 

        #endregion 

        #region Methods for Foreign Keys mapped to association
        // requires: The normalizer for both parent and child is present in m_cellNormalizerMap
        // effects: Ensures that there is a relationship mapped into the C-space for some cell in m_cellGroup. Else 
        // adds an error to errorLog
        private void GuaranteeMappedRelationshipForForeignKey(QueryRewriter childRewriter, QueryRewriter parentRewriter, 
                                                              IEnumerable cells, 
                                                              ErrorLog errorLog, ConfigViewGenerator config, MetadataWorkspace workspace) {
 
            CellNormalizer childNormalizer = childRewriter.Normalizer;
            CellNormalizer parentNormalizer = parentRewriter.Normalizer;

            // Find a cell where this foreign key is mapped as a relationship 
            MemberPath prefix = new MemberPath(ChildTable, workspace);
            ExtentKey primaryKey = ExtentKey.GetPrimaryKeyForEntityType(prefix, ChildTable.ElementType); 
            IEnumerable primaryKeyFields = primaryKey.KeyFields; 
            bool foundCell = false;
 
            bool foundValidParentColumnsForForeignKey = false; //we need to find only one, dont error on any one check being false
            List errorListForInvalidParentColumnsForForeignKey = null;
            foreach (Cell cell in cells) {
                if (cell.SQuery.Extent.Equals(ChildTable) == false) { 
                    continue;
                } 
 
                // The childtable is mapped to a relationship in the C-space in cell
                // Check that all the columns of the foreign key and the primary key in the child table are mapped to some 
                // property in the C-space

                AssociationEndMember parentEnd = GetRelationEndForColumns(cell, ChildColumns, workspace);
                if (parentEnd != null && CheckParentColumnsForForeignKey(cell, cells, parentEnd, ref errorListForInvalidParentColumnsForForeignKey) == false) 
                {
                    // Not an error unless we find no valid case 
                    continue; 
                }
                else 
                {
                    foundValidParentColumnsForForeignKey = true;
                }
 
                AssociationEndMember childEnd = GetRelationEndForColumns(cell, primaryKeyFields, workspace);
                Debug.Assert(childEnd == null || parentEnd != childEnd, 
                             "Ends are same => PKey and child columns are same - code should gone to other method"); 
                // Note: If both of them are not-null, they are mapped to the
                // same association set -- since we checked that particular cell 

                if (childEnd != null && parentEnd != null &&
                    FindEntitySetForColumnsMappedToEntityKeys(cells, primaryKeyFields) != null) {
                    foundCell = true; 
                    CheckConstraintWhenParentChildMapped(cell, errorLog, parentEnd, config);
                    break; // Done processing for the foreign key - either it was mapped correctly or it was not 
                } else if (parentEnd != null) { 
                    // At this point, we know cell corresponds to an association set
                    AssociationSet assocSet = (AssociationSet) cell.CQuery.Extent; 
                    EntitySet parentSet = MetadataHelper.GetEntitySetAtEnd(assocSet, parentEnd);
                    foundCell = CheckConstraintWhenOnlyParentMapped(cell, parentSet, assocSet, parentEnd, childRewriter, parentRewriter, config, workspace);
                    if (foundCell) {
                        break; 
                    }
                } 
            } 

            //CheckParentColumnsForForeignKey has returned no matches, Error. 
            if (!foundValidParentColumnsForForeignKey)
            {
                Debug.Assert(errorListForInvalidParentColumnsForForeignKey != null && errorListForInvalidParentColumnsForForeignKey.Count > 0);
                foreach(var errorRecord in errorListForInvalidParentColumnsForForeignKey) 
                {
                    errorLog.AddEntry(errorRecord); 
                } 
                return;
            } 

            if (foundCell == false) {
                // No cell found -- Declare error
                string message = System.Data.Entity.Strings.ViewGen_Foreign_Key_Missing_Relationship_Mapping_0(ToUserString()); 

                // m_cellNormalizerMap contains the normalizer for all tables unless they have not been mapped at all 
                IEnumerable parentWrappers = GetWrappersFromNormalizer(parentNormalizer, ParentTable); 
                IEnumerable childWrappers = GetWrappersFromNormalizer(childNormalizer, ChildTable);
                Set bothExtentWrappers = 
                    new Set(parentWrappers);
                bothExtentWrappers.AddRange(childWrappers);
                ErrorLog.Record record = new ErrorLog.Record(true, ViewGenErrorCode.ForeignKeyMissingRelationshipMapping, message, bothExtentWrappers, String.Empty);
                errorLog.AddEntry(record); 
            }
        } 
 
        // requires: IsForeignKeySuperSetOfPrimaryKeyInChildTable() is false
        // and primaryKeys of ChildTable are not mapped in cell. cell 
        // corresponds to an association set. parentSet is the set
        // corresponding to the end that we are looking at
        // effects: Checks if the constraint is correctly maintained in
        // C-space via an association set (being a subset of the 
        // corresponding entitySet)
        private bool CheckConstraintWhenOnlyParentMapped(Cell cell, EntitySet parentSet, AssociationSet assocSet, AssociationEndMember endMember, 
                                                         QueryRewriter childRewriter, QueryRewriter parentRewriter, 
                                                         ConfigViewGenerator config, MetadataWorkspace workspace) {
 
            CellNormalizer childNormalizer = childRewriter.Normalizer;
            CellNormalizer parentNormalizer = parentRewriter.Normalizer;

            CellTreeNode pNode = parentRewriter.BasicView; 
            Debug.Assert(pNode != null);
 
            RoleBoolean endRoleBoolean = new RoleBoolean(assocSet.AssociationSetEnds[endMember.Name]); 
            // use query in pNode as a factory to create a bool expression for the endRoleBoolean
            BoolExpression endCondition = pNode.RightFragmentQuery.Condition.Create(endRoleBoolean); 
            FragmentQuery cNodeQuery = FragmentQuery.Create(pNode.RightFragmentQuery.Attributes, endCondition);

            FragmentQueryProcessor qp = FragmentQueryProcessor.Merge(childNormalizer.RightFragmentQP, parentNormalizer.RightFragmentQP);
            bool cImpliesP = qp.IsContainedIn(cNodeQuery, pNode.RightFragmentQuery); 
            return cImpliesP;
        } 
 
        // requires: IsForeignKeySuperSetOfPrimaryKeyInChildTable() is false
        // effects: Given that both the ChildColumns in this and the 
        // primaryKey of ChildTable are mapped. Return true iff no error occurred
        private bool CheckConstraintWhenParentChildMapped(Cell cell, ErrorLog errorLog,
                                                          AssociationEndMember parentEnd, ConfigViewGenerator config) {
            bool ok = true; 

            // The foreign key constraint has been mapped to a 
            // relationship. Check if the multiplicities are consistent 
            // If all columns in the child table (corresponding to
            // the constraint) are nullable, the parent end can be 
            // 0..1 or 1..1. Else if must be 1..1
            if (parentEnd.RelationshipMultiplicity == RelationshipMultiplicity.Many) {
                // Parent should at most one since we are talking
                // about foreign keys here 
                string message = System.Data.Entity.Strings.ViewGen_Foreign_Key_UpperBound_MustBeOne_2(ToUserString(),
                                               cell.CQuery.Extent.Name, parentEnd.Name); 
                ErrorLog.Record record = new ErrorLog.Record(true, ViewGenErrorCode.ForeignKeyUpperBoundMustBeOne, message, cell, String.Empty); 
                errorLog.AddEntry(record);
                ok = false; 
            }

            if (MemberPath.AreAllMembersNullable(ChildColumns) == false && parentEnd.RelationshipMultiplicity != RelationshipMultiplicity.One) {
                // Some column in the constraint in the child table 
                // is non-nullable and lower bound is not 1
                string message = System.Data.Entity.Strings.ViewGen_Foreign_Key_LowerBound_MustBeOne_2(ToUserString(), 
                                               cell.CQuery.Extent.Name, parentEnd.Name); 
                ErrorLog.Record record = new ErrorLog.Record(true, ViewGenErrorCode.ForeignKeyLowerBoundMustBeOne, message, cell, String.Empty);
                errorLog.AddEntry(record); 
                ok = false;
            }

            if (config.IsNormalTracing && ok) { 
                Trace.WriteLine("Foreign key mapped to relationship " + cell.CQuery.Extent.Name);
            } 
            return ok; 
        }
 
        // effects: Given the foreign key constraint, checks if the
        // constraint.ParentColumns are mapped to the entity set E'e keys in
        // C-space where E corresponds to the entity set corresponding to end
        // Returns true iff such a mapping exists in cell 
        private bool CheckParentColumnsForForeignKey(Cell cell, IEnumerable cells, AssociationEndMember parentEnd, ref List errorList) {
            // The child columns are mapped to some end of cell.CQuery.Extent. ParentColumns 
            // must correspond to the EntitySet for this end 
            AssociationSet relationSet = (AssociationSet)cell.CQuery.Extent;
            EntitySet endSet = MetadataHelper.GetEntitySetAtEnd(relationSet, parentEnd); 

            // Check if the ParentColumns are mapped to endSet's keys

            // Find the entity set that they map to - if any 
            EntitySet entitySet = FindEntitySetForColumnsMappedToEntityKeys(cells, ParentColumns);
            if (entitySet == null || endSet.Equals(entitySet) == false) { 
 
                if (errorList == null) //lazily initialize only if there is an error
                { 
                    errorList = new List();
                }

                // childColumns are mapped to parentEnd but ParentColumns are not mapped to the end 
                // corresponding to the parentEnd -- this is an error
                string message = System.Data.Entity.Strings.ViewGen_Foreign_Key_ParentTable_NotMappedToEnd_5( 
                                               ToUserString(), ChildTable.Name, 
                                               cell.CQuery.Extent.Name, parentEnd.Name, ParentTable.Name, endSet.Name);
                ErrorLog.Record record = new ErrorLog.Record(true, ViewGenErrorCode.ForeignKeyParentTableNotMappedToEnd, message, cell, String.Empty); 
                errorList.Add(record);
                return false;
            }
            return true; 
        }
        #endregion 
 
        #region Static Helper Methods
        // effects: Returns the entity set to which tableFields are mapped 
        // and if the mapped fields correspond precisely to the entity set's
        // keys. Else returns null
        private static EntitySet FindEntitySetForColumnsMappedToEntityKeys(IEnumerable cells, IEnumerable tableColumns) {
            foreach (Cell cell in cells) { 
                CellQuery cQuery = cell.CQuery;
                if (cQuery.Extent is AssociationSet) { 
                    continue; 
                }
 
                Set cSideMembers = cell.GetCSlotsForTableColumns(tableColumns);
                if (cSideMembers == null) {
                    continue;
                } 
                // Now check if these fields correspond to the key fields of
                // the entity set 
 
                EntitySet entitySet = cQuery.Extent as EntitySet;
 
                // Construct a List
                List propertyList = new List();
                foreach (EdmProperty property in entitySet.ElementType.KeyMembers) {
                    propertyList.Add(property); 
                }
 
                Set keyMembers = new Set(propertyList).MakeReadOnly(); 
                if (keyMembers.SetEquals(cSideMembers)) {
                    return entitySet; 
                }
            }
            return null;
        } 

        // effects: Returns the end to which columns are exactly mapped in the 
        // relationship set given by cell.CQuery.Extent -- if this extent is 
        // an entityset returns null. If the columns are not mapped in this cell to an
        // end exactly or columns are not projected in cell, returns null 
        private static AssociationEndMember GetRelationEndForColumns(Cell cell, IEnumerable columns, MetadataWorkspace workspace) {
            if (cell.CQuery.Extent is EntitySet) {
                return null;
            } 

            AssociationSet relationSet = (AssociationSet)cell.CQuery.Extent; 
 
            // Go through all the ends and see if they are mapped in this cell
            foreach (AssociationSetEnd relationEnd in relationSet.AssociationSetEnds) { 

                AssociationEndMember endMember = relationEnd.CorrespondingAssociationEndMember;
                MemberPath prefix = new MemberPath(relationSet, endMember, workspace);
                // Note: primaryKey is the key for the entity set but 
                // prefixed with the relationship's path - we are trying to
                // check if the entity's keys are mapped in this cell as an end 
                ExtentKey primaryKey = ExtentKey.GetPrimaryKeyForEntityType(prefix, relationEnd.EntitySet.ElementType); 

                // Check if this end is mapped in this cell -- we are 
                // checking on the C-side -- we get all the indexes of the
                // end's keyfields
                List endIndexes = cell.CQuery.GetProjectedPositions(primaryKey.KeyFields);
                if (endIndexes != null) 
                {
                    // Get all the slots corresponding to the columns 
                    // But stick to the slots with in these ends since the same column might be 
                    //projected twice in different ends
                    List columnIndexes = cell.SQuery.GetProjectedPositions(columns, endIndexes); 
                    if (columnIndexes == null)
                    {
                        continue; // columns are not projected with in this end
                    } 
                    // Note that the positions need not match exactly - we have a
                    // separate test that will do that for us: CheckForeignKeyColumnOrder 
                    if (Helpers.IsSetEqual(columnIndexes, endIndexes, EqualityComparer.Default)) 
                    {
                        // The columns map exactly to this end -- return it 
                        return endMember;
                    }
                }
            } 
            return null;
        } 
 
        // effects: Returns wrappers for extent if there are some available in the normalizer. Else returns an empty enumeration
        private static List GetWrappersFromNormalizer(CellNormalizer normalizer, EntitySetBase extent) { 
            List wrappers;
            if (normalizer == null) {
                wrappers = new List();
            } else { 
                Debug.Assert(normalizer.Extent.Equals(extent), "Normalizer extent and expected extent different");
                wrappers = normalizer.AllWrappersForExtent; 
            } 
            return wrappers;
        } 
        #endregion

        #region Regular Helper Methods
        // requires: all columns in constraint.ParentColumns and 
        // constraint.ChildColumns must have been mapped in some cell in m_cellGroup
        // effects: Given the foreign key constraint, checks if the 
        // constraint.ChildColumns are mapped to the constraint.ParentColumns 
        // in m_cellGroup in the right oder. If not, adds an error to m_errorLog and returns
        // false. Else returns true 
        private bool CheckForeignKeyColumnOrder(Set cells, ErrorLog errorLog) {
            // Go through every cell and find the cells that are relevant to
            // parent and those that are relevant to child
            // Then for each cell pair (parent, child) make sure that the 
            // projected foreign keys columns in C-space are aligned
 
            List parentCells = new List(); 
            List childCells = new List();
 
            foreach (Cell cell in cells) {
                if (cell.SQuery.Extent.Equals(ChildTable)) {
                    childCells.Add(cell);
                } 

                if (cell.SQuery.Extent.Equals(ParentTable)) { 
                    parentCells.Add(cell); 
                }
            } 

            // Make sure that all child cells and parent cells align on
            // the columns, i.e., for each DISTINCT pair C and P, get the columns
            // on the S-side. Then get the corresponding fields on the 
            // C-side. The fields on the C-side should match
 
            bool foundParentCell = false; 
            bool foundChildCell = false;
 
            foreach (Cell childCell in childCells) {
                List> allChildSlotNums = GetSlotNumsForColumns(childCell, ChildColumns);

                if (allChildSlotNums.Count == 0) { // slots in present in S-side, ignore 
                    continue;
                } 
                bool isError = false; 

                List childPaths = null; 
                List parentPaths = null;
                Cell errorParentCell = null;

                foreach (List childSlotNums in allChildSlotNums) { 
                    foundChildCell = true;
 
                    // Get the fields on the C-side 
                    childPaths = new List(childSlotNums.Count);
                    foreach (int childSlotNum in childSlotNums) { 
                        // Initial slots only have JoinTreeSlots
                        JoinTreeSlot childSlot = (JoinTreeSlot)childCell.CQuery.ProjectedSlotAt(childSlotNum);
                        Debug.Assert(childSlot != null);
                        childPaths.Add(childSlot.MemberPath); 
                    }
 
                    foreach (Cell parentCell in parentCells) { 
                        List> allParentSlotNums = GetSlotNumsForColumns(parentCell, ParentColumns);
                        if (parentCell.Equals(childCell) || allParentSlotNums.Count == 0) { 
                            // * Parent and child cell are the same - we do not
                            // need to check since we want to check the foreign
                            // key constraint mapping across cells
                            // * Some slots not in present in S-side, ignore 
                            continue;
                        } 
                        foreach (List parentSlotNums in allParentSlotNums) { 
                            foundParentCell = true;
 
                            parentPaths = new List(parentSlotNums.Count);
                            foreach (int parentSlotNum in parentSlotNums) {
                                JoinTreeSlot parentSlot = (JoinTreeSlot)parentCell.CQuery.ProjectedSlotAt(parentSlotNum);
                                Debug.Assert(parentSlot != null); 
                                parentPaths.Add(parentSlot.MemberPath);
                            } 
 
                            // Make sure that the last member of each of these is the same
                            // or the paths are essentially equivalent via referential constraints 
                            // We need to check that the last member is essentially the same because it could
                            // be a regular scenario where aid is mapped to PersonAddress and Address - there
                            // is no ref constraint. So when projected into C-Space, we will get Address.aid
                            // and PersonAddress.Address.aid 
                            isError = childPaths.Count != parentPaths.Count;
                            if (isError == false) { 
                                for (int i = 0; i < childPaths.Count; i++) { 
                                    MemberPath parentPath = parentPaths[i];
                                    MemberPath childPath = childPaths[i]; 
                                    if (parentPath.LastMember.Equals(childPath.LastMember) == false) {
                                        if (parentPath.IsEquivalentViaRefConstraint(childPath)) {
                                            //Specifying the referential constraint once in the C space should be enough.
                                            //This is the only way possible today. 
                                            //We might be able to derive more knowledge by using boolean logic
                                            return true; 
                                        } else { 
                                            isError = true;
                                            errorParentCell = parentCell; 
                                        }
                                    }
                                }
                            } 
                        }
                    } 
                } 
                if (isError) {
                    Debug.Assert(childPaths != null, "child paths should be set"); 
                    Debug.Assert(parentPaths != null, "parent paths should be set");
                    Debug.Assert(errorParentCell != null, "errorParentCell should be set");
                    // using EntityRes. instead of Strings. because the generated method includes 6 instead of 9 parameters
                    string message = EntityRes.GetString(EntityRes.ViewGen_Foreign_Key_ColumnOrder_Incorrect_8, 
                                                   ToUserString(),
                                                   MemberPath.PropertiesToUserString(ChildColumns, false), 
                                                   ChildTable.Name, 
                                                   MemberPath.PropertiesToUserString(childPaths, false),
                                                   childCell.CQuery.Extent.Name, 
                                                   MemberPath.PropertiesToUserString(ParentColumns, false),
                                                   ParentTable.Name,
                                                   MemberPath.PropertiesToUserString(parentPaths, false),
                                                   errorParentCell.CQuery.Extent.Name); 
                    ErrorLog.Record record = new ErrorLog.Record(true, ViewGenErrorCode.ForeignKeyColumnOrderIncorrect, message, new Cell[] { errorParentCell, childCell }, String.Empty);
                    errorLog.AddEntry(record); 
                    return false; 
                }
            } 
            Debug.Assert(foundParentCell == true, "Some cell that mapped the parent's key must be present!");
            Debug.Assert(foundChildCell == true, "Some cell that mapped the child's foreign key must be present according to the requires clause!");
            return true;
        } 

        private static List> GetSlotNumsForColumns(Cell cell, IEnumerable columns) { 
            List> slotNums = new List>(); 
            AssociationSet set = cell.CQuery.Extent as AssociationSet;
            //If it is an association set, the columns could be projected 
            //in either end so get the slotNums from both the ends
            if (set != null) {
                foreach (AssociationSetEnd setEnd in set.AssociationSetEnds) {
                    List endSlots = cell.CQuery.GetAssociationEndSlots(setEnd.CorrespondingAssociationEndMember); 
                    Debug.Assert(endSlots.Count > 0);
                    List localslotNums = cell.SQuery.GetProjectedPositions(columns, endSlots); 
                    if (localslotNums != null) { 
                        slotNums.Add(localslotNums);
                    } 
                }
            } else {
                List localslotNums = cell.SQuery.GetProjectedPositions(columns);
                if (localslotNums != null) { 
                    slotNums.Add(localslotNums);
                } 
            } 
            return slotNums;
        } 

        // effects: Returns true iff the foreign keys "cover" the primary key
        // in the child table, e.g.,  covers  (if k2 is the key
        // of the child table) 
        private bool IsForeignKeySuperSetOfPrimaryKeyInChildTable() {
            bool isForeignKeySuperSet = true; 
            foreach (EdmProperty keyMember in m_childTable.ElementType.KeyMembers) { 
                // Look for this member in the foreign key members
                bool memberFound = false; 
                foreach (MemberPath foreignKeyMember in m_childColumns) {
                    // Getting the last member is good enough since it
                    // effectively captures the path (we are not comparing
                    // string names here) 
                    if (foreignKeyMember.LastMember.Equals(keyMember)) {
                        memberFound = true; 
                        break; 
                    }
                } 
                if (memberFound == false) {
                    isForeignKeySuperSet = false;
                    break;
                } 
            }
            return isForeignKeySuperSet; 
        } 

        // effects: Returns true iff some cell in this refers to the 
        // constraint's parent table or child table
        private bool IsConstraintRelevantForCells(IEnumerable cells) {
            // if the constraint does not deal with any cell in this group,
            // return false 
            bool found = false;
            foreach (Cell cell in cells) { 
                EntitySetBase table = cell.SQuery.Extent; 
                if (table.Equals(m_parentTable) || table.Equals(m_childTable)) {
                    found = true; 
                    break;
                }
            }
            return found; 
        }
        #endregion 
 
        #region String methods
        internal string ToUserString() { 
            string childColsString = MemberPath.PropertiesToUserString(m_childColumns, false);
            string parentColsString = MemberPath.PropertiesToUserString(m_parentColumns, false);
            string result = System.Data.Entity.Strings.ViewGen_Foreign_Key_4(m_fKeySet.Name,
                                          m_childTable.Name, childColsString, m_parentTable.Name, parentColsString); 
            return result;
        } 
 
        internal override void  ToCompactString(StringBuilder builder) {
            builder.Append(m_fKeySet.Name + ": "); 
            builder.Append(ToUserString());
        }
        #endregion
    } 
}

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
//---------------------------------------------------------------------- 
// 
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// 
// 
// @owner [....]
// @backupOwner [....] 
//--------------------------------------------------------------------- 

using System.Data.Common.Utils; 
using System.Data.Common.Utils.Boolean;
using System.Data.Mapping.ViewGeneration.Structures;
using System.Data.Mapping.ViewGeneration.Utils;
using System.Data.Mapping.ViewGeneration.QueryRewriting; 
using System.Collections.Generic;
using System.Text; 
using System.Diagnostics; 
using System.Data.Metadata.Edm;
 
namespace System.Data.Mapping.ViewGeneration.Validation {

    using WrapperBoolExpr    = BoolExpr;
    using WrapperOrExpr      = OrExpr; 
    using WrapperNotExpr     = NotExpr;
    using WrapperTermExpr    = TermExpr; 
 
    // An abstraction that captures a foreign key constraint:
    //  -->  
    internal class ForeignConstraint : InternalBase {

        #region Constructor
        // effects: Creates a foreign key constraint of the form: 
        //  --> 
        // i_fkeySet is the name of the constraint 
        internal ForeignConstraint(AssociationSet i_fkeySet, EntitySet i_parentTable, EntitySet i_childTable, 
                                   ReadOnlyMetadataCollection i_parentColumns, ReadOnlyMetadataCollection i_childColumns, MetadataWorkspace workspace) {
            m_fKeySet = i_fkeySet; 
            m_parentTable = i_parentTable;
            m_childTable = i_childTable;
            m_childColumns = new List();
            // Create parent and child paths using the table names 
            foreach (EdmProperty property in i_childColumns) {
                MemberPath path = new MemberPath(m_childTable, property, workspace); 
                m_childColumns.Add(path); 
            }
 
            m_parentColumns = new List();
            foreach (EdmProperty property in i_parentColumns) {
                MemberPath path = new MemberPath(m_parentTable, property, workspace);
                m_parentColumns.Add(path); 
            }
        } 
        #endregion 

        #region Fields 
        private AssociationSet m_fKeySet; // Just for debugging
        private EntitySet m_parentTable;
        private EntitySet m_childTable;
        private List m_parentColumns; 
        private List m_childColumns;
        #endregion 
 
        #region Properties
        internal EntitySet ParentTable { 
            get { return m_parentTable;}
        }

        internal EntitySet ChildTable { 
            get { return m_childTable;}
        } 
 
        internal IEnumerable ChildColumns {
            get { return m_childColumns;} 
        }

        internal IEnumerable ParentColumns {
            get { return m_parentColumns;} 
        }
        #endregion 
 
        #region Externally available Methods
        // effects: Given a store-side container, returns all the foreign key 
        // constraints specified for different tables
        internal static List GetForeignConstraints(EntityContainer container, MetadataWorkspace workspace) {
            List foreignKeyConstraints = new List();
 
            // Go through all the extents and get the associations
            foreach (EntitySetBase extent in container.BaseEntitySets) { 
                AssociationSet relationSet = extent as AssociationSet; 

                if (relationSet == null) continue; 
                // Keep track of the end to EntitySet mapping
                Dictionary endToExtents = new Dictionary();

                foreach (AssociationSetEnd  end in relationSet.AssociationSetEnds) { 
                    endToExtents.Add(end.Name, end.EntitySet);
                } 
 
                AssociationType relationType = relationSet.ElementType;
                // Go through each referential constraint, determine the name 
                // of the tables that the constraint refers to and then
                // create the foreign key constraint between the tables
                // Wow! We go to great lengths to make it cumbersome for a
                // programmer to deal with foreign keys 
                foreach (ReferentialConstraint constraint in relationType.ReferentialConstraints) {
                    // Note: We are correlating the constraint's roles with 
                    // the ends above using the role names, i.e., 
                    // FromRole.Name and ToRole.Name here and end.Role above
                    EntitySet parentExtent = endToExtents[constraint.FromRole.Name]; 
                    EntitySet childExtent = endToExtents[constraint.ToRole.Name];
                    ForeignConstraint foreignKeyConstraint = new ForeignConstraint(relationSet, parentExtent, childExtent,
                                                                       constraint.FromProperties, constraint.ToProperties, workspace);
                    foreignKeyConstraints.Add(foreignKeyConstraint); 

                } 
            } 
            return foreignKeyConstraints;
        } 

        // requires: childNormalizer and parentNormalizer correspond to the
        // normalizers for ChildTable and ParentTable respectively (or null
        // if none exist) 
        // effects: Checks that this foreign key constraints for all the
        // tables are being ensured on the C-side as well. If not, adds 
        // errors to the errorLog 
        internal void CheckConstraint(Set cells, QueryRewriter childRewriter, QueryRewriter parentRewriter,
                                      ErrorLog errorLog, ConfigViewGenerator config, MetadataWorkspace workspace) { 
            if (IsConstraintRelevantForCells(cells) == false) {
                // if the constraint does not deal with any cell in this group, ignore it
                return;
            } 

            if (config.IsNormalTracing) { 
                Trace.WriteLine(String.Empty); 
                Trace.WriteLine(String.Empty);
                Trace.Write("Checking: "); 
                Trace.WriteLine(this);
            }

            if (childRewriter == null && parentRewriter == null) { 
                // Neither table is mapped - so we are fine
                return; 
            } 

            // If the child table has not been mapped, we used to say that we 
            // are fine. However, if we have SPerson(pid) and SAddress(aid,
            // pid), where pid is an FK into SPerson, we are in trouble if
            // SAddress is not mapped - SPerson could get deleted. So we
            // check for it as well 
            // if the parent table is not mapped, we also have a problem
 
            if (childRewriter == null) { 
                string message = System.Data.Entity.Strings.ViewGen_Foreign_Key_Missing_Table_Mapping_1(
                                               ToUserString(), ChildTable.Name); 
                // Get the cells from the parent table
                ErrorLog.Record record = new ErrorLog.Record(true, ViewGenErrorCode.ForeignKeyMissingTableMapping, message, parentRewriter.UsedCells, String.Empty);
                errorLog.AddEntry(record);
                return; 
            }
 
            if (parentRewriter == null) { 
                string message = System.Data.Entity.Strings.ViewGen_Foreign_Key_Missing_Table_Mapping_1(
                                               ToUserString(), ParentTable.Name); 
                // Get the cells from the child table
                ErrorLog.Record record = new ErrorLog.Record(true, ViewGenErrorCode.ForeignKeyMissingTableMapping, message, childRewriter.UsedCells, String.Empty);
                errorLog.AddEntry(record);
                return; 
            }
 
            // Note: we do not check if the parent columns correspond to the 
            // table's keys - metadata checks for that
 
            // Check if the foreign key in the child table corresponds to the primary key, i.e., if
            // the foreign key (e.g., pid, pid2) is a superset of the actual key members (e.g., pid), it means
            // that the foreign key is also the primary key for this table -- so we can propagate the queries upto C-Space
            // rather than doing the cell check 

            int initialErrorLogSize = errorLog.Count; 
            if (IsForeignKeySuperSetOfPrimaryKeyInChildTable()) { 
                GuaranteeForeignKeyConstraintInCSpace(childRewriter, parentRewriter, errorLog, config);
            } else { 
                GuaranteeMappedRelationshipForForeignKey(childRewriter, parentRewriter, cells, errorLog, config, workspace);
            }

            if (initialErrorLogSize == errorLog.Count) { 
                // Check if the order of columns in foreign key correponds to the
                // mappings in m_cellGroup, e.g., if  in SAddress is 
                // a foreign key into  of the SPerson table, make 
                // sure that this order is preserved through the mappings in m_cellGroup
                CheckForeignKeyColumnOrder(cells, errorLog); 
            }
        }
        #endregion
 
        #region Methods (mostly) for Query Containment Check via Keys
        // requires: constraint.ChildColumns form a key in 
        // constraint.ChildTable (actually they should subsume the primary key) 
        // The normalizer for both parent and child is present in m_cellNormalizerMap
        // effects: Projects the constraint into C-space and checks if it is 
        // being preserved in C-space -- if not, adds an error to m_errorLog
        private void GuaranteeForeignKeyConstraintInCSpace(QueryRewriter childRewriter, QueryRewriter parentRewriter,
                                                           ErrorLog errorLog, ConfigViewGenerator config) {
            CellNormalizer childNormalizer = childRewriter.Normalizer; 
            CellNormalizer parentNormalizer = parentRewriter.Normalizer;
            CellTreeNode cNode = childRewriter.BasicView; 
            CellTreeNode pNode = parentRewriter.BasicView; 

            FragmentQueryProcessor qp = FragmentQueryProcessor.Merge(childNormalizer.RightFragmentQP, parentNormalizer.RightFragmentQP); 
            bool cImpliesP = qp.IsContainedIn(cNode.RightFragmentQuery, pNode.RightFragmentQuery);

            if (false == cImpliesP)
            { 
                // Foreign key constraint not being ensured in C-space
                string childExtents = LeftCellWrapper.GetExtentListAsUserString(cNode.GetLeaves()); 
                string parentExtents = LeftCellWrapper.GetExtentListAsUserString(pNode.GetLeaves()); 
                string message = System.Data.Entity.Strings.ViewGen_Foreign_Key_Not_Guaranteed_InCSpace_0(
                                               ToUserString()); 
                // Add all wrappers into allWrappers
                Set allWrappers = new Set(pNode.GetLeaves());
                allWrappers.AddRange(cNode.GetLeaves());
                ErrorLog.Record record = new ErrorLog.Record(true, ViewGenErrorCode.ForeignKeyNotGuaranteedInCSpace, message, allWrappers, String.Empty); 
                errorLog.AddEntry(record);
            } 
        } 

        #endregion 

        #region Methods for Foreign Keys mapped to association
        // requires: The normalizer for both parent and child is present in m_cellNormalizerMap
        // effects: Ensures that there is a relationship mapped into the C-space for some cell in m_cellGroup. Else 
        // adds an error to errorLog
        private void GuaranteeMappedRelationshipForForeignKey(QueryRewriter childRewriter, QueryRewriter parentRewriter, 
                                                              IEnumerable cells, 
                                                              ErrorLog errorLog, ConfigViewGenerator config, MetadataWorkspace workspace) {
 
            CellNormalizer childNormalizer = childRewriter.Normalizer;
            CellNormalizer parentNormalizer = parentRewriter.Normalizer;

            // Find a cell where this foreign key is mapped as a relationship 
            MemberPath prefix = new MemberPath(ChildTable, workspace);
            ExtentKey primaryKey = ExtentKey.GetPrimaryKeyForEntityType(prefix, ChildTable.ElementType); 
            IEnumerable primaryKeyFields = primaryKey.KeyFields; 
            bool foundCell = false;
 
            bool foundValidParentColumnsForForeignKey = false; //we need to find only one, dont error on any one check being false
            List errorListForInvalidParentColumnsForForeignKey = null;
            foreach (Cell cell in cells) {
                if (cell.SQuery.Extent.Equals(ChildTable) == false) { 
                    continue;
                } 
 
                // The childtable is mapped to a relationship in the C-space in cell
                // Check that all the columns of the foreign key and the primary key in the child table are mapped to some 
                // property in the C-space

                AssociationEndMember parentEnd = GetRelationEndForColumns(cell, ChildColumns, workspace);
                if (parentEnd != null && CheckParentColumnsForForeignKey(cell, cells, parentEnd, ref errorListForInvalidParentColumnsForForeignKey) == false) 
                {
                    // Not an error unless we find no valid case 
                    continue; 
                }
                else 
                {
                    foundValidParentColumnsForForeignKey = true;
                }
 
                AssociationEndMember childEnd = GetRelationEndForColumns(cell, primaryKeyFields, workspace);
                Debug.Assert(childEnd == null || parentEnd != childEnd, 
                             "Ends are same => PKey and child columns are same - code should gone to other method"); 
                // Note: If both of them are not-null, they are mapped to the
                // same association set -- since we checked that particular cell 

                if (childEnd != null && parentEnd != null &&
                    FindEntitySetForColumnsMappedToEntityKeys(cells, primaryKeyFields) != null) {
                    foundCell = true; 
                    CheckConstraintWhenParentChildMapped(cell, errorLog, parentEnd, config);
                    break; // Done processing for the foreign key - either it was mapped correctly or it was not 
                } else if (parentEnd != null) { 
                    // At this point, we know cell corresponds to an association set
                    AssociationSet assocSet = (AssociationSet) cell.CQuery.Extent; 
                    EntitySet parentSet = MetadataHelper.GetEntitySetAtEnd(assocSet, parentEnd);
                    foundCell = CheckConstraintWhenOnlyParentMapped(cell, parentSet, assocSet, parentEnd, childRewriter, parentRewriter, config, workspace);
                    if (foundCell) {
                        break; 
                    }
                } 
            } 

            //CheckParentColumnsForForeignKey has returned no matches, Error. 
            if (!foundValidParentColumnsForForeignKey)
            {
                Debug.Assert(errorListForInvalidParentColumnsForForeignKey != null && errorListForInvalidParentColumnsForForeignKey.Count > 0);
                foreach(var errorRecord in errorListForInvalidParentColumnsForForeignKey) 
                {
                    errorLog.AddEntry(errorRecord); 
                } 
                return;
            } 

            if (foundCell == false) {
                // No cell found -- Declare error
                string message = System.Data.Entity.Strings.ViewGen_Foreign_Key_Missing_Relationship_Mapping_0(ToUserString()); 

                // m_cellNormalizerMap contains the normalizer for all tables unless they have not been mapped at all 
                IEnumerable parentWrappers = GetWrappersFromNormalizer(parentNormalizer, ParentTable); 
                IEnumerable childWrappers = GetWrappersFromNormalizer(childNormalizer, ChildTable);
                Set bothExtentWrappers = 
                    new Set(parentWrappers);
                bothExtentWrappers.AddRange(childWrappers);
                ErrorLog.Record record = new ErrorLog.Record(true, ViewGenErrorCode.ForeignKeyMissingRelationshipMapping, message, bothExtentWrappers, String.Empty);
                errorLog.AddEntry(record); 
            }
        } 
 
        // requires: IsForeignKeySuperSetOfPrimaryKeyInChildTable() is false
        // and primaryKeys of ChildTable are not mapped in cell. cell 
        // corresponds to an association set. parentSet is the set
        // corresponding to the end that we are looking at
        // effects: Checks if the constraint is correctly maintained in
        // C-space via an association set (being a subset of the 
        // corresponding entitySet)
        private bool CheckConstraintWhenOnlyParentMapped(Cell cell, EntitySet parentSet, AssociationSet assocSet, AssociationEndMember endMember, 
                                                         QueryRewriter childRewriter, QueryRewriter parentRewriter, 
                                                         ConfigViewGenerator config, MetadataWorkspace workspace) {
 
            CellNormalizer childNormalizer = childRewriter.Normalizer;
            CellNormalizer parentNormalizer = parentRewriter.Normalizer;

            CellTreeNode pNode = parentRewriter.BasicView; 
            Debug.Assert(pNode != null);
 
            RoleBoolean endRoleBoolean = new RoleBoolean(assocSet.AssociationSetEnds[endMember.Name]); 
            // use query in pNode as a factory to create a bool expression for the endRoleBoolean
            BoolExpression endCondition = pNode.RightFragmentQuery.Condition.Create(endRoleBoolean); 
            FragmentQuery cNodeQuery = FragmentQuery.Create(pNode.RightFragmentQuery.Attributes, endCondition);

            FragmentQueryProcessor qp = FragmentQueryProcessor.Merge(childNormalizer.RightFragmentQP, parentNormalizer.RightFragmentQP);
            bool cImpliesP = qp.IsContainedIn(cNodeQuery, pNode.RightFragmentQuery); 
            return cImpliesP;
        } 
 
        // requires: IsForeignKeySuperSetOfPrimaryKeyInChildTable() is false
        // effects: Given that both the ChildColumns in this and the 
        // primaryKey of ChildTable are mapped. Return true iff no error occurred
        private bool CheckConstraintWhenParentChildMapped(Cell cell, ErrorLog errorLog,
                                                          AssociationEndMember parentEnd, ConfigViewGenerator config) {
            bool ok = true; 

            // The foreign key constraint has been mapped to a 
            // relationship. Check if the multiplicities are consistent 
            // If all columns in the child table (corresponding to
            // the constraint) are nullable, the parent end can be 
            // 0..1 or 1..1. Else if must be 1..1
            if (parentEnd.RelationshipMultiplicity == RelationshipMultiplicity.Many) {
                // Parent should at most one since we are talking
                // about foreign keys here 
                string message = System.Data.Entity.Strings.ViewGen_Foreign_Key_UpperBound_MustBeOne_2(ToUserString(),
                                               cell.CQuery.Extent.Name, parentEnd.Name); 
                ErrorLog.Record record = new ErrorLog.Record(true, ViewGenErrorCode.ForeignKeyUpperBoundMustBeOne, message, cell, String.Empty); 
                errorLog.AddEntry(record);
                ok = false; 
            }

            if (MemberPath.AreAllMembersNullable(ChildColumns) == false && parentEnd.RelationshipMultiplicity != RelationshipMultiplicity.One) {
                // Some column in the constraint in the child table 
                // is non-nullable and lower bound is not 1
                string message = System.Data.Entity.Strings.ViewGen_Foreign_Key_LowerBound_MustBeOne_2(ToUserString(), 
                                               cell.CQuery.Extent.Name, parentEnd.Name); 
                ErrorLog.Record record = new ErrorLog.Record(true, ViewGenErrorCode.ForeignKeyLowerBoundMustBeOne, message, cell, String.Empty);
                errorLog.AddEntry(record); 
                ok = false;
            }

            if (config.IsNormalTracing && ok) { 
                Trace.WriteLine("Foreign key mapped to relationship " + cell.CQuery.Extent.Name);
            } 
            return ok; 
        }
 
        // effects: Given the foreign key constraint, checks if the
        // constraint.ParentColumns are mapped to the entity set E'e keys in
        // C-space where E corresponds to the entity set corresponding to end
        // Returns true iff such a mapping exists in cell 
        private bool CheckParentColumnsForForeignKey(Cell cell, IEnumerable cells, AssociationEndMember parentEnd, ref List errorList) {
            // The child columns are mapped to some end of cell.CQuery.Extent. ParentColumns 
            // must correspond to the EntitySet for this end 
            AssociationSet relationSet = (AssociationSet)cell.CQuery.Extent;
            EntitySet endSet = MetadataHelper.GetEntitySetAtEnd(relationSet, parentEnd); 

            // Check if the ParentColumns are mapped to endSet's keys

            // Find the entity set that they map to - if any 
            EntitySet entitySet = FindEntitySetForColumnsMappedToEntityKeys(cells, ParentColumns);
            if (entitySet == null || endSet.Equals(entitySet) == false) { 
 
                if (errorList == null) //lazily initialize only if there is an error
                { 
                    errorList = new List();
                }

                // childColumns are mapped to parentEnd but ParentColumns are not mapped to the end 
                // corresponding to the parentEnd -- this is an error
                string message = System.Data.Entity.Strings.ViewGen_Foreign_Key_ParentTable_NotMappedToEnd_5( 
                                               ToUserString(), ChildTable.Name, 
                                               cell.CQuery.Extent.Name, parentEnd.Name, ParentTable.Name, endSet.Name);
                ErrorLog.Record record = new ErrorLog.Record(true, ViewGenErrorCode.ForeignKeyParentTableNotMappedToEnd, message, cell, String.Empty); 
                errorList.Add(record);
                return false;
            }
            return true; 
        }
        #endregion 
 
        #region Static Helper Methods
        // effects: Returns the entity set to which tableFields are mapped 
        // and if the mapped fields correspond precisely to the entity set's
        // keys. Else returns null
        private static EntitySet FindEntitySetForColumnsMappedToEntityKeys(IEnumerable cells, IEnumerable tableColumns) {
            foreach (Cell cell in cells) { 
                CellQuery cQuery = cell.CQuery;
                if (cQuery.Extent is AssociationSet) { 
                    continue; 
                }
 
                Set cSideMembers = cell.GetCSlotsForTableColumns(tableColumns);
                if (cSideMembers == null) {
                    continue;
                } 
                // Now check if these fields correspond to the key fields of
                // the entity set 
 
                EntitySet entitySet = cQuery.Extent as EntitySet;
 
                // Construct a List
                List propertyList = new List();
                foreach (EdmProperty property in entitySet.ElementType.KeyMembers) {
                    propertyList.Add(property); 
                }
 
                Set keyMembers = new Set(propertyList).MakeReadOnly(); 
                if (keyMembers.SetEquals(cSideMembers)) {
                    return entitySet; 
                }
            }
            return null;
        } 

        // effects: Returns the end to which columns are exactly mapped in the 
        // relationship set given by cell.CQuery.Extent -- if this extent is 
        // an entityset returns null. If the columns are not mapped in this cell to an
        // end exactly or columns are not projected in cell, returns null 
        private static AssociationEndMember GetRelationEndForColumns(Cell cell, IEnumerable columns, MetadataWorkspace workspace) {
            if (cell.CQuery.Extent is EntitySet) {
                return null;
            } 

            AssociationSet relationSet = (AssociationSet)cell.CQuery.Extent; 
 
            // Go through all the ends and see if they are mapped in this cell
            foreach (AssociationSetEnd relationEnd in relationSet.AssociationSetEnds) { 

                AssociationEndMember endMember = relationEnd.CorrespondingAssociationEndMember;
                MemberPath prefix = new MemberPath(relationSet, endMember, workspace);
                // Note: primaryKey is the key for the entity set but 
                // prefixed with the relationship's path - we are trying to
                // check if the entity's keys are mapped in this cell as an end 
                ExtentKey primaryKey = ExtentKey.GetPrimaryKeyForEntityType(prefix, relationEnd.EntitySet.ElementType); 

                // Check if this end is mapped in this cell -- we are 
                // checking on the C-side -- we get all the indexes of the
                // end's keyfields
                List endIndexes = cell.CQuery.GetProjectedPositions(primaryKey.KeyFields);
                if (endIndexes != null) 
                {
                    // Get all the slots corresponding to the columns 
                    // But stick to the slots with in these ends since the same column might be 
                    //projected twice in different ends
                    List columnIndexes = cell.SQuery.GetProjectedPositions(columns, endIndexes); 
                    if (columnIndexes == null)
                    {
                        continue; // columns are not projected with in this end
                    } 
                    // Note that the positions need not match exactly - we have a
                    // separate test that will do that for us: CheckForeignKeyColumnOrder 
                    if (Helpers.IsSetEqual(columnIndexes, endIndexes, EqualityComparer.Default)) 
                    {
                        // The columns map exactly to this end -- return it 
                        return endMember;
                    }
                }
            } 
            return null;
        } 
 
        // effects: Returns wrappers for extent if there are some available in the normalizer. Else returns an empty enumeration
        private static List GetWrappersFromNormalizer(CellNormalizer normalizer, EntitySetBase extent) { 
            List wrappers;
            if (normalizer == null) {
                wrappers = new List();
            } else { 
                Debug.Assert(normalizer.Extent.Equals(extent), "Normalizer extent and expected extent different");
                wrappers = normalizer.AllWrappersForExtent; 
            } 
            return wrappers;
        } 
        #endregion

        #region Regular Helper Methods
        // requires: all columns in constraint.ParentColumns and 
        // constraint.ChildColumns must have been mapped in some cell in m_cellGroup
        // effects: Given the foreign key constraint, checks if the 
        // constraint.ChildColumns are mapped to the constraint.ParentColumns 
        // in m_cellGroup in the right oder. If not, adds an error to m_errorLog and returns
        // false. Else returns true 
        private bool CheckForeignKeyColumnOrder(Set cells, ErrorLog errorLog) {
            // Go through every cell and find the cells that are relevant to
            // parent and those that are relevant to child
            // Then for each cell pair (parent, child) make sure that the 
            // projected foreign keys columns in C-space are aligned
 
            List parentCells = new List(); 
            List childCells = new List();
 
            foreach (Cell cell in cells) {
                if (cell.SQuery.Extent.Equals(ChildTable)) {
                    childCells.Add(cell);
                } 

                if (cell.SQuery.Extent.Equals(ParentTable)) { 
                    parentCells.Add(cell); 
                }
            } 

            // Make sure that all child cells and parent cells align on
            // the columns, i.e., for each DISTINCT pair C and P, get the columns
            // on the S-side. Then get the corresponding fields on the 
            // C-side. The fields on the C-side should match
 
            bool foundParentCell = false; 
            bool foundChildCell = false;
 
            foreach (Cell childCell in childCells) {
                List> allChildSlotNums = GetSlotNumsForColumns(childCell, ChildColumns);

                if (allChildSlotNums.Count == 0) { // slots in present in S-side, ignore 
                    continue;
                } 
                bool isError = false; 

                List childPaths = null; 
                List parentPaths = null;
                Cell errorParentCell = null;

                foreach (List childSlotNums in allChildSlotNums) { 
                    foundChildCell = true;
 
                    // Get the fields on the C-side 
                    childPaths = new List(childSlotNums.Count);
                    foreach (int childSlotNum in childSlotNums) { 
                        // Initial slots only have JoinTreeSlots
                        JoinTreeSlot childSlot = (JoinTreeSlot)childCell.CQuery.ProjectedSlotAt(childSlotNum);
                        Debug.Assert(childSlot != null);
                        childPaths.Add(childSlot.MemberPath); 
                    }
 
                    foreach (Cell parentCell in parentCells) { 
                        List> allParentSlotNums = GetSlotNumsForColumns(parentCell, ParentColumns);
                        if (parentCell.Equals(childCell) || allParentSlotNums.Count == 0) { 
                            // * Parent and child cell are the same - we do not
                            // need to check since we want to check the foreign
                            // key constraint mapping across cells
                            // * Some slots not in present in S-side, ignore 
                            continue;
                        } 
                        foreach (List parentSlotNums in allParentSlotNums) { 
                            foundParentCell = true;
 
                            parentPaths = new List(parentSlotNums.Count);
                            foreach (int parentSlotNum in parentSlotNums) {
                                JoinTreeSlot parentSlot = (JoinTreeSlot)parentCell.CQuery.ProjectedSlotAt(parentSlotNum);
                                Debug.Assert(parentSlot != null); 
                                parentPaths.Add(parentSlot.MemberPath);
                            } 
 
                            // Make sure that the last member of each of these is the same
                            // or the paths are essentially equivalent via referential constraints 
                            // We need to check that the last member is essentially the same because it could
                            // be a regular scenario where aid is mapped to PersonAddress and Address - there
                            // is no ref constraint. So when projected into C-Space, we will get Address.aid
                            // and PersonAddress.Address.aid 
                            isError = childPaths.Count != parentPaths.Count;
                            if (isError == false) { 
                                for (int i = 0; i < childPaths.Count; i++) { 
                                    MemberPath parentPath = parentPaths[i];
                                    MemberPath childPath = childPaths[i]; 
                                    if (parentPath.LastMember.Equals(childPath.LastMember) == false) {
                                        if (parentPath.IsEquivalentViaRefConstraint(childPath)) {
                                            //Specifying the referential constraint once in the C space should be enough.
                                            //This is the only way possible today. 
                                            //We might be able to derive more knowledge by using boolean logic
                                            return true; 
                                        } else { 
                                            isError = true;
                                            errorParentCell = parentCell; 
                                        }
                                    }
                                }
                            } 
                        }
                    } 
                } 
                if (isError) {
                    Debug.Assert(childPaths != null, "child paths should be set"); 
                    Debug.Assert(parentPaths != null, "parent paths should be set");
                    Debug.Assert(errorParentCell != null, "errorParentCell should be set");
                    // using EntityRes. instead of Strings. because the generated method includes 6 instead of 9 parameters
                    string message = EntityRes.GetString(EntityRes.ViewGen_Foreign_Key_ColumnOrder_Incorrect_8, 
                                                   ToUserString(),
                                                   MemberPath.PropertiesToUserString(ChildColumns, false), 
                                                   ChildTable.Name, 
                                                   MemberPath.PropertiesToUserString(childPaths, false),
                                                   childCell.CQuery.Extent.Name, 
                                                   MemberPath.PropertiesToUserString(ParentColumns, false),
                                                   ParentTable.Name,
                                                   MemberPath.PropertiesToUserString(parentPaths, false),
                                                   errorParentCell.CQuery.Extent.Name); 
                    ErrorLog.Record record = new ErrorLog.Record(true, ViewGenErrorCode.ForeignKeyColumnOrderIncorrect, message, new Cell[] { errorParentCell, childCell }, String.Empty);
                    errorLog.AddEntry(record); 
                    return false; 
                }
            } 
            Debug.Assert(foundParentCell == true, "Some cell that mapped the parent's key must be present!");
            Debug.Assert(foundChildCell == true, "Some cell that mapped the child's foreign key must be present according to the requires clause!");
            return true;
        } 

        private static List> GetSlotNumsForColumns(Cell cell, IEnumerable columns) { 
            List> slotNums = new List>(); 
            AssociationSet set = cell.CQuery.Extent as AssociationSet;
            //If it is an association set, the columns could be projected 
            //in either end so get the slotNums from both the ends
            if (set != null) {
                foreach (AssociationSetEnd setEnd in set.AssociationSetEnds) {
                    List endSlots = cell.CQuery.GetAssociationEndSlots(setEnd.CorrespondingAssociationEndMember); 
                    Debug.Assert(endSlots.Count > 0);
                    List localslotNums = cell.SQuery.GetProjectedPositions(columns, endSlots); 
                    if (localslotNums != null) { 
                        slotNums.Add(localslotNums);
                    } 
                }
            } else {
                List localslotNums = cell.SQuery.GetProjectedPositions(columns);
                if (localslotNums != null) { 
                    slotNums.Add(localslotNums);
                } 
            } 
            return slotNums;
        } 

        // effects: Returns true iff the foreign keys "cover" the primary key
        // in the child table, e.g.,  covers  (if k2 is the key
        // of the child table) 
        private bool IsForeignKeySuperSetOfPrimaryKeyInChildTable() {
            bool isForeignKeySuperSet = true; 
            foreach (EdmProperty keyMember in m_childTable.ElementType.KeyMembers) { 
                // Look for this member in the foreign key members
                bool memberFound = false; 
                foreach (MemberPath foreignKeyMember in m_childColumns) {
                    // Getting the last member is good enough since it
                    // effectively captures the path (we are not comparing
                    // string names here) 
                    if (foreignKeyMember.LastMember.Equals(keyMember)) {
                        memberFound = true; 
                        break; 
                    }
                } 
                if (memberFound == false) {
                    isForeignKeySuperSet = false;
                    break;
                } 
            }
            return isForeignKeySuperSet; 
        } 

        // effects: Returns true iff some cell in this refers to the 
        // constraint's parent table or child table
        private bool IsConstraintRelevantForCells(IEnumerable cells) {
            // if the constraint does not deal with any cell in this group,
            // return false 
            bool found = false;
            foreach (Cell cell in cells) { 
                EntitySetBase table = cell.SQuery.Extent; 
                if (table.Equals(m_parentTable) || table.Equals(m_childTable)) {
                    found = true; 
                    break;
                }
            }
            return found; 
        }
        #endregion 
 
        #region String methods
        internal string ToUserString() { 
            string childColsString = MemberPath.PropertiesToUserString(m_childColumns, false);
            string parentColsString = MemberPath.PropertiesToUserString(m_parentColumns, false);
            string result = System.Data.Entity.Strings.ViewGen_Foreign_Key_4(m_fKeySet.Name,
                                          m_childTable.Name, childColsString, m_parentTable.Name, parentColsString); 
            return result;
        } 
 
        internal override void  ToCompactString(StringBuilder builder) {
            builder.Append(m_fKeySet.Name + ": "); 
            builder.Append(ToUserString());
        }
        #endregion
    } 
}

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.

                        

Link Menu

Network programming in C#, Network Programming in VB.NET, Network Programming in .NET
This book is available now!
Buy at Amazon US or
Buy at Amazon UK