public MappingAssociation(EditingContext context, Association assoc, MappingEFElement parent) : base(context, assoc, parent) { Debug.Assert(assoc != null, "MappingAssociation cannot accept a null Association"); Debug.Assert( assoc.AssociationSet != null, "MappingAssociation cannot accept an Association " + assoc.ToPrettyString() + " with a null AssociationSet"); }
AddAssociationSetMappingForConceptualAssociation( CommandProcessorContext cpc, EFArtifact existingArtifact, Association assocInTempArtifact, Association assocInExistingArtifact, Dictionary<EntityType, EntityType> tempArtifactCEntityTypeToNewCEntityTypeInExistingArtifact) { // first find the AssociationSetMapping in the tempArtifact for this association var asmInTempArtifact = ModelHelper.FindAssociationSetMappingForConceptualAssociation(assocInTempArtifact); if (asmInTempArtifact == null) { if (!EdmFeatureManager.GetForeignKeysInModelFeatureState(existingArtifact.SchemaVersion).IsEnabled() || assocInTempArtifact.IsManyToMany) { // this is an error condition - we should have an association set mapping in this case, so assert and throw an exception throw new UpdateModelFromDatabaseException( string.Format( CultureInfo.CurrentCulture, Resources.UpdateFromDatabaseAssociationSetMappingCannotFind, assocInTempArtifact.ToPrettyString())); } else { // we don't expect an association set mapping here return; } } // next find the S-side EntitySet in the tempArtifact for this AssociationSetMapping var storeEntitySetInTempArtifact = asmInTempArtifact.StoreEntitySet.Target; if (storeEntitySetInTempArtifact == null) { throw new UpdateModelFromDatabaseException( string.Format( CultureInfo.CurrentCulture, Resources.UpdateFromDatabaseAssociationSetMappingCannotFindTempSSideEntitySet, asmInTempArtifact.ToPrettyString())); } // now find the S-side EntitySet in the existingArtifact which matches this // Note: LocalName's will be the same as the SSDL has been replaced StorageEntitySet storeEntitySetInExistingArtifact = null; foreach (var es in existingArtifact.StorageModel().FirstEntityContainer.EntitySets()) { if (es.LocalName.Value == storeEntitySetInTempArtifact.LocalName.Value) { storeEntitySetInExistingArtifact = es as StorageEntitySet; break; } } if (storeEntitySetInExistingArtifact == null) { throw new UpdateModelFromDatabaseException( string.Format( CultureInfo.CurrentCulture, Resources.UpdateFromDatabaseAssociationSetMappingCannotFindMatchingSSideEntitySet, storeEntitySetInTempArtifact.LocalName.Value)); } // now create a new AssociationSetMapping in the existingArtifact using the data // accumulated above CloneAssociationSetMapping( cpc, asmInTempArtifact, existingArtifact.MappingModel().FirstEntityContainerMapping, assocInExistingArtifact.AssociationSet, assocInExistingArtifact, storeEntitySetInExistingArtifact, tempArtifactCEntityTypeToNewCEntityTypeInExistingArtifact ); }
private bool HasBeenReplacedByInheritanceOrSplitEntity( Association assoc, AssociationIdentity assocId, Dictionary<EntityType, EntityType> tempArtifactCEntityTypeToNewCEntityTypeInExistingArtifact) { if (2 != assoc.AssociationEnds().Count) { Debug.Fail( "Received incorrect number of AssociationEnds (" + assoc.AssociationEnds().Count + ") for Association " + assoc.ToPrettyString() + " should be 2."); return false; } // check 1:1 or 1:0..1 var assocEnds = assoc.AssociationEnds(); var assocEnd1 = assocEnds[0]; var assocEnd2 = assocEnds[1]; if (!(ModelConstants.Multiplicity_One == assocEnd1.Multiplicity.Value && (ModelConstants.Multiplicity_One == assocEnd2.Multiplicity.Value || ModelConstants.Multiplicity_ZeroOrOne == assocEnd2.Multiplicity.Value)) || (ModelConstants.Multiplicity_One == assocEnd2.Multiplicity.Value && (ModelConstants.Multiplicity_One == assocEnd1.Multiplicity.Value || ModelConstants.Multiplicity_ZeroOrOne == assocEnd1.Multiplicity.Value))) { return false; } // get C-side EntityTypes for each AssociationEnd var et1 = assocEnd1.Type.Target as ConceptualEntityType; var et2 = assocEnd2.Type.Target as ConceptualEntityType; if (null == et1) { Debug.Fail("EntityType et1 is not a ConceptualEntityType"); return false; } else if (null == et2) { Debug.Fail("EntityType et2 is not a ConceptualEntityType"); return false; } // check not both pointing to same entity type (i.e. self-association) if (et1.Equals(et2)) { return false; } // check inheritance relationship // First identify the association table and which end // contains that table var et1Id = _modelRepresentingDatabase.GetEntityTypeIdentityForEntityType(et1); var et2Id = _modelRepresentingDatabase.GetEntityTypeIdentityForEntityType(et2); if (null == et1Id) { Debug.Fail("Could not find EntityTypeIdentity for et1 " + et1.ToPrettyString()); return false; } else if (null == et2Id) { Debug.Fail("Could not find EntityTypeIdentity for et2 " + et2.ToPrettyString()); return false; } EntityTypeIdentity etIdNotContainingAssocTable = null; var tables = assocId.AssociationTables.GetEnumerator(); tables.MoveNext(); var assocTable = tables.Current; if (tables.MoveNext()) { // // If we get here, the properties involved in a Ref Constraint were mapped to multiple tables. This implies inheritance // or horizontal partitioning on the database (ie, the dependent end of the RC is an entity mapped to more than one table, // so the RC spans tables). // // We don't ever expect this to be the case for a model generated from model-gen APIs, so assert. // Debug.Fail( "An association is mapped to more than one table on the dependent via a referential constraint. We didn't expect this to happen for a model generated from model gen APIs"); // just return false here. It should be OK for us to include this association, and the user can make the call on what to do. return false; } if (et1Id.ContainsDatabaseObject(assocTable)) { etIdNotContainingAssocTable = et2Id; } else if (et2Id.ContainsDatabaseObject(assocTable)) { etIdNotContainingAssocTable = et1Id; } else { // Neither end of a 1:1 or 1:0..1 Association contains the association table. // This is an error. Debug.Fail( "Neither end of the Association " + assoc.ToPrettyString() + " contains the association table " + assocTable.ToString()); return false; } // now find the C-side EntityTypes in the existing artifact // for that association table var existingEntityTypesForAssocTable = _preExistingModel.GetConceptualEntityTypesForDatabaseObject(assocTable); // now if any of these EntityTypes has an ancestor which maps // to any of the tables/views in the EntityTypeIdentity of // the _other_ AssociationEnd then we have found the inheritance // relationship which replaced this Association // Note: this covers the case where one or the other end is a new table/view: // if assocTable is "new" then existingEntityTypesForAssocTable will be null, // if the other end of the association is "new" then no existing EntityType // will have an ancestor type that maps to the tables/views in the other end. if (null != existingEntityTypesForAssocTable) { foreach (var cet in existingEntityTypesForAssocTable) { foreach (var tableOrView in etIdNotContainingAssocTable.TablesAndViews) { if (_preExistingModel.HasAncestorTypeThatMapsToDbObject(cet, tableOrView)) { return true; } } } } // check split-entity // find the C-Side EntityTypes in the existing artifact for both ends var existingEntityType1 = FindMatchingConceptualEntityTypeInExistingArtifact( et1, tempArtifactCEntityTypeToNewCEntityTypeInExistingArtifact); var existingEntityType2 = FindMatchingConceptualEntityTypeInExistingArtifact( et2, tempArtifactCEntityTypeToNewCEntityTypeInExistingArtifact); // if either there are no matching EntityTypes or the 2 matching EntityTypes // are different in the existing artifact then this is not split-entity if (null == existingEntityType1 || null == existingEntityType2 || !existingEntityType1.Equals(existingEntityType2)) { return false; } // check referential constraint var refConstraint = assoc.ReferentialConstraint; if (null == refConstraint) { return false; } return (HasIdenticalKeyToEntityType(refConstraint.Principal) && HasIdenticalKeyToEntityType(refConstraint.Dependent)); }
/// <summary> /// Clone the Association with its ReferentialConstraint (if available), AssociationSet /// and AssociationSetMapping. If the ReferentialConstraint is available in the temp artifact /// but we cannot find matching properties in the existing artifact then the whole Association /// (and AssociationSet and AssociationSetMapping) will not be cloned, a warning message will /// be issued but otherwise the process is not stopped. /// But if the Association cannot be created for any other reason that's an error and an /// UpdateModelFromDatabaseException will be thrown. /// </summary> /// <param name="cpc">CommandProcessorContext for the commands to be issued</param> /// <param name="existingArtifact">the existing artifact in which to make these changes</param> /// <param name="assocInTempArtifact">the Association in the temp artifact to be cloned</param> /// <param name="end1InTempArtifact">the end of the Association in the temp artifact to be cloned to be treated as End1</param> /// <param name="end2InTempArtifact">the end of the Association in the temp artifact to be cloned to be treated as End2</param> /// <param name="navProp1InTempArtifact">the NavigationProperty for End1 in the temp artifact to be cloned</param> /// <param name="navProp2InTempArtifact">the NavigationProperty for End2 in the temp artifact to be cloned</param> /// <param name="end1EntityTypeInExistingArtifact">the EntityType in the existing artifact matching the End1 target in the temp artifact</param> /// <param name="end2EntityTypeInExistingArtifact">the EntityType in the existing artifact matching the End2 target in the temp artifact</param> /// <param name="tempArtifactCEntityTypeToNewCEntityTypeInExistingArtifact"> /// a Dictionary mapping temp artifact EntityTypes which have /// been identified as new to their equivalent in the existing artifact /// </param> private void CloneAssociation( CommandProcessorContext cpc, EFArtifact existingArtifact, Association assocInTempArtifact, AssociationEnd end1InTempArtifact, AssociationEnd end2InTempArtifact, NavigationProperty navProp1InTempArtifact, NavigationProperty navProp2InTempArtifact, ConceptualEntityType end1EntityTypeInExistingArtifact, ConceptualEntityType end2EntityTypeInExistingArtifact, Dictionary<EntityType, EntityType> tempArtifactCEntityTypeToNewCEntityTypeInExistingArtifact) { Association newAssocInExistingArtifact = null; AssociationEnd newAssocEnd1 = null; AssociationEnd newAssocEnd2 = null; if (assocInTempArtifact.ReferentialConstraint == null) { // if there is no ReferentialConstraint to clone then just try to // create the Association and AssociationSet var cmd = new CreateConceptualAssociationCommand( assocInTempArtifact.LocalName.Value, end1EntityTypeInExistingArtifact, end1InTempArtifact.Multiplicity.Value, navProp1InTempArtifact.LocalName.Value, end2EntityTypeInExistingArtifact, end2InTempArtifact.Multiplicity.Value, navProp2InTempArtifact.LocalName.Value, true, false); CommandProcessor.InvokeSingleCommand(cpc, cmd); newAssocInExistingArtifact = cmd.CreatedAssociation; newAssocEnd1 = cmd.End1; newAssocEnd2 = cmd.End2; } else { // There is a ReferentialConstraint - so we need to check whether we can find matching properties // for the Principal and Dependent roles of the ReferentialConstraint. If we can't find // matching properties then log a warning message and return. Otherwise attempt to create // the Association (and its AssociationSet) followed by a matching ReferentialConstraint. // // Note: ShouldCreateAssociationGivenReferentialConstraint() produces 2 sets of information: // (1) the sets of matching properties to use if we can find matching properties for the Principal // and Dependent roles, and (2) the sets of property names to include in the error message if // we cannot find matching properties for the Principal and Dependent roles. // If ShouldCreateAssociationGivenReferentialConstraint() returns true we use the first set of // information to create the ReferentialConstraint after having created the Association and // AssociationSet. If it returns false we use the second set of information to construct the // warning message. var refConstraintInTempArtifact = assocInTempArtifact.ReferentialConstraint; bool end1IsPrincipalEnd; List<Property> principalPropertiesInExistingArtifact; List<Property> dependentPropertiesInExistingArtifact; List<string> unfoundPrincipalProperties; List<string> unfoundDependentProperties; if (ShouldCreateAssociationGivenReferentialConstraint( refConstraintInTempArtifact, end1InTempArtifact, end2InTempArtifact, end1EntityTypeInExistingArtifact, end2EntityTypeInExistingArtifact, out end1IsPrincipalEnd, out principalPropertiesInExistingArtifact, out dependentPropertiesInExistingArtifact, out unfoundPrincipalProperties, out unfoundDependentProperties)) { // create the new Association and AssociationSet var cmd = new CreateConceptualAssociationCommand( assocInTempArtifact.LocalName.Value, end1EntityTypeInExistingArtifact, end1InTempArtifact.Multiplicity.Value, navProp1InTempArtifact.LocalName.Value, end2EntityTypeInExistingArtifact, end2InTempArtifact.Multiplicity.Value, navProp2InTempArtifact.LocalName.Value, true, false); CommandProcessor.InvokeSingleCommand(cpc, cmd); newAssocInExistingArtifact = cmd.CreatedAssociation; newAssocEnd1 = cmd.End1; newAssocEnd2 = cmd.End2; // Add in the ReferentialConstraint for the new Association if (null != newAssocInExistingArtifact) { AddReferentialConstraintForAssociation( cpc, cmd, end1IsPrincipalEnd, principalPropertiesInExistingArtifact, dependentPropertiesInExistingArtifact); } } else { // Unable to find matching properties for the Principal and Dependent roles of // the ReferentialConstraint. So log a warning message. newAssocInExistingArtifact = null; EntityType principalEndEntityType = end1IsPrincipalEnd ? end1EntityTypeInExistingArtifact : end2EntityTypeInExistingArtifact; EntityType dependentEndEntityType = end1IsPrincipalEnd ? end2EntityTypeInExistingArtifact : end1EntityTypeInExistingArtifact; LogWarningMessageForReferentialConstraintProperties( assocInTempArtifact.LocalName.Value, principalEndEntityType, dependentEndEntityType, unfoundPrincipalProperties, unfoundDependentProperties); // having logged the warning message we do not need to attempt to create // the AssociationSetMapping nor throw an exception due to the lack of the // created Association - so just return return; } } // if we have failed to create an Association at this stage that's a serious error // so throw an exception to indicate the failure if (null == newAssocInExistingArtifact) { throw new UpdateModelFromDatabaseException( string.Format( CultureInfo.CurrentCulture, Resources.UpdateFromDatabaseCannotCreateAssociation, assocInTempArtifact.ToPrettyString())); } // update OnDeleteActions to match temp artifact UpdateOnDeleteAction(cpc, newAssocEnd1, end1InTempArtifact); UpdateOnDeleteAction(cpc, newAssocEnd2, end2InTempArtifact); // add a new AssociationSetMapping for the new Association AddAssociationSetMappingForConceptualAssociation( cpc, existingArtifact, assocInTempArtifact, newAssocInExistingArtifact, tempArtifactCEntityTypeToNewCEntityTypeInExistingArtifact); }