/// <summary> /// Additional processing required for server deserialization. Flags a /// processed attribute or relationship as updated using <see cref="ITargetedFields"/>. /// </summary> /// <param name="resource">The resource that was constructed from the document's body.</param> /// <param name="field">The metadata for the exposed field.</param> /// <param name="data">Relationship data for <paramref name="resource"/>. Is null when <paramref name="field"/> is not a <see cref="RelationshipAttribute"/>.</param> protected override void AfterProcessField(IIdentifiable resource, ResourceFieldAttribute field, RelationshipEntry data = null) { bool isCreatingResource = IsCreatingResource(); bool isUpdatingResource = IsUpdatingResource(); if (field is AttrAttribute attr) { if (isCreatingResource && !attr.Capabilities.HasFlag(AttrCapabilities.AllowCreate)) { throw new JsonApiSerializationException( "Setting the initial value of the requested attribute is not allowed.", $"Setting the initial value of '{attr.PublicName}' is not allowed.", atomicOperationIndex: AtomicOperationIndex); } if (isUpdatingResource && !attr.Capabilities.HasFlag(AttrCapabilities.AllowChange)) { throw new JsonApiSerializationException( "Changing the value of the requested attribute is not allowed.", $"Changing the value of '{attr.PublicName}' is not allowed.", atomicOperationIndex: AtomicOperationIndex); } _targetedFields.Attributes.Add(attr); } else if (field is RelationshipAttribute relationship) { _targetedFields.Relationships.Add(relationship); } }
/// <summary> /// Gets the missing relationships. /// </summary> /// <param name="context">The context.</param> /// <returns></returns> public IEnumerable <RelationshipEntry> GetMissingRelationships(IProcessingContext context) { long tenantId = CallData <long> .GetValue("TargetTenantId"); Dictionary <Guid, CardinalityEnum_Enumeration> typeCardinalities = TypeCardinalities; ///// // Query entities that are part of the solution ///// const string sql = @"SELECT TypeUid, FromUid, ToUid FROM AppDeploy_Relationship WHERE AppVerUid = @appVer AND TenantId = @tenantId"; using (IDbCommand command = CreateCommand( )) { command.CommandText = sql; command.AddParameterWithValue("@appVer", AppVerId); command.AddParameterWithValue("@tenantId", tenantId); using (IDataReader reader = command.ExecuteReader( )) { while (reader.Read( )) { Guid typeId = reader.GetGuid(0); Guid fromId = reader.GetGuid(1); Guid toId = reader.GetGuid(2); CardinalityEnum_Enumeration cardinality; RelationshipEntry entry = typeCardinalities.TryGetValue(typeId, out cardinality) ? new RelationshipEntry(typeId, fromId, toId, cardinality) : new RelationshipEntry(typeId, fromId, toId); yield return(entry); } } } }
/// <summary> /// Builds the values of the relationships object on a resource object. /// The server serializer only populates the "data" member when the relationship is included, /// and adds links unless these are turned off. This means that if a relationship is not included /// and links are turned off, the entry would be completely empty, ie { }, which is not conform /// json:api spec. In that case we return null which will omit the entry from the output. /// </summary> protected override RelationshipEntry GetRelationshipData(RelationshipAttribute relationship, IIdentifiable entity) { RelationshipEntry relationshipEntry = null; List <List <RelationshipAttribute> > relationshipChains = null; if (relationship == _requestRelationship || ShouldInclude(relationship, out relationshipChains)) { relationshipEntry = base.GetRelationshipData(relationship, entity); if (relationshipChains != null && relationshipEntry.HasResource) { foreach (var chain in relationshipChains) { // traverses (recursively) and extracts all (nested) related entities for the current inclusion chain. _includedBuilder.IncludeRelationshipChain(chain, entity); } } } var links = _linkBuilder.GetRelationshipLinks(relationship, entity); if (links != null) { // if links relationshipLinks should be built for this entry, populate the "links" field. (relationshipEntry = relationshipEntry ?? new RelationshipEntry()).Links = links; } // if neither "links" nor "data" was popupated, return null, which will omit this entry from the output. // (see the NullValueHandling settings on <see cref="ResourceObject"/>) return(relationshipEntry); }
/// <summary> /// Sets a HasOne relationship on a parsed entity. If present, also /// populates the foreign key. /// </summary> /// <param name="entity"></param> /// <param name="entityProperties"></param> /// <param name="attr"></param> /// <param name="relationshipData"></param> /// <returns></returns> private object SetHasOneRelationship(IIdentifiable entity, PropertyInfo[] entityProperties, HasOneAttribute attr, RelationshipEntry relationshipData) { var rio = (ResourceIdentifierObject)relationshipData.Data; var relatedId = rio?.Id ?? null; // this does not make sense in the following case: if we're setting the dependent of a one-to-one relationship, IdentifiablePropertyName should be null. var foreignKeyProperty = entityProperties.FirstOrDefault(p => p.Name == attr.IdentifiablePropertyName); if (foreignKeyProperty != null) { /// there is a FK from the current entity pointing to the related object, /// i.e. we're populating the relationship from the dependent side. SetForeignKey(entity, foreignKeyProperty, attr, relatedId); } SetNavigation(entity, attr, relatedId); /// depending on if this base parser is used client-side or server-side, /// different additional processing per field needs to be executed. AfterProcessField(entity, attr, relationshipData); return(entity); }
/// <summary> /// Sets a HasOne relationship on a parsed resource. If present, also /// populates the foreign key. /// </summary> private void SetHasOneRelationship(IIdentifiable resource, PropertyInfo[] resourceProperties, HasOneAttribute attr, RelationshipEntry relationshipData) { var rio = (ResourceIdentifierObject)relationshipData.Data; var relatedId = rio?.Id; var relationshipType = relationshipData.SingleData == null ? attr.RightType : ResourceContextProvider.GetResourceContext(relationshipData.SingleData.Type).ResourceType; // this does not make sense in the following case: if we're setting the dependent of a one-to-one relationship, IdentifiablePropertyName should be null. var foreignKeyProperty = resourceProperties.FirstOrDefault(p => p.Name == attr.IdentifiablePropertyName); if (foreignKeyProperty != null) { // there is a FK from the current resource pointing to the related object, // i.e. we're populating the relationship from the dependent side. SetForeignKey(resource, foreignKeyProperty, attr, relatedId, relationshipType); } SetNavigation(resource, attr, relatedId, relationshipType); // depending on if this base parser is used client-side or server-side, // different additional processing per field needs to be executed. AfterProcessField(resource, attr, relationshipData); }
/// <summary> /// This is the implementation API for returning relationships. The default implementation stores the /// relationship in a table. Relationships are stored weakly, so they do not keep an object alive. Empty can be /// passed in for relationship to remove the relationship. /// </summary> protected virtual void SetRelationship(MemberRelationship source, MemberRelationship relationship) { if (!relationship.IsEmpty && !SupportsRelationship(source, relationship)) { ThrowRelationshipNotSupported(source, relationship); } _relationships[new RelationshipEntry(source)] = new RelationshipEntry(relationship); }
/// <summary> /// Handle case if one entity is a component of the other. /// </summary> /// <param name="relType">The relationship type.</param> /// <param name="fromEntity">The 'from' entity.</param> /// <param name="toEntity">The 'to' entity.</param> /// <returns></returns> private bool HandleContainedEntities(RelationshipEntry rel, RelationshipTypeEntry relType, EntityHierarchyEntry fromEntity, EntityHierarchyEntry toEntity) { if (relType == null) { return(false); } // Explicitly do not next certain relationships if (rel.TypeId == Guids.InSolution || rel.TypeId == Guids.IndirectInSolution) { return(false); } // if 'fwdComponent' then parent = FromId // if 'revComponent' then parent = ToId // "Clone action" is the best proxy to determine if something is a subcomponent, while still handling 'custom' reltypes. bool fwdComponent = relType.CloneAction == CloneActionEnum_Enumeration.CloneEntities; bool revComponent = !fwdComponent && relType.ReverseCloneAction == CloneActionEnum_Enumeration.CloneEntities; EntityHierarchyEntry parent = null; EntityHierarchyEntry child = null; if (fwdComponent) { parent = fromEntity; child = toEntity; } else if (revComponent) { parent = toEntity; child = fromEntity; } else { return(false); } if (child.ParentEntity != null) { return(false); // child already assigned to a different parent } if (WouldCauseCycle(parent, child)) { return(false); } if (parent.Children == null) { parent.Children = new List <EntityHierarchyEntry>( ); } parent.Children.Add(child); child.ParentEntity = parent; child.RelationshipFromParent = rel; child.Direction = revComponent ? Direction.Reverse : Direction.Forward; return(true); }
public override bool Equals(object o) { if (o is RelationshipEntry) { RelationshipEntry e = (RelationshipEntry)o; return(this == e); } return(false); }
/// <summary> /// Puts the relationships of the resource into the resource object. /// </summary> private void ProcessRelationships(IIdentifiable resource, IEnumerable <RelationshipAttribute> relationships, ResourceObject ro) { foreach (RelationshipAttribute rel in relationships) { RelationshipEntry relData = GetRelationshipData(rel, resource); if (relData != null) { (ro.Relationships ??= new Dictionary <string, RelationshipEntry>()).Add(rel.PublicName, relData); } } }
/// <summary> /// Serializes the relationship. /// </summary> /// <param name="xmlWriter">The XML writer.</param> /// <param name="relationship">The relationship.</param> /// <param name="defaultTypeName">Default name of the type.</param> /// <param name="xmlStack">The XML stack.</param> private void SerializeRelationship(XmlWriter xmlWriter, RelationshipEntry relationship, Stack <string> xmlStack) { using (WriteElementBlock(xmlWriter, relationship.TypeId, XmlConstants.RelationshipConstants.Rel, XmlConstants.RelationshipConstants.Rel, XmlConstants.Id, xmlStack)) { using (xmlWriter.WriteElementBlock(XmlConstants.RelationshipConstants.From, xmlStack)) { xmlWriter.WriteString(GetInnerText(relationship.FromId)); } using (xmlWriter.WriteElementBlock(XmlConstants.RelationshipConstants.To, xmlStack)) { xmlWriter.WriteString(GetInnerText(relationship.ToId)); } } }
private void ExcludeEntityKey() { RelationshipEntry relationship = this.ObjectContext.ObjectStateManager.FindRelationship(this.RelationshipSet, new KeyValuePair <string, EntityKey>(this.RelationshipNavigation.From, this.WrappedOwner.EntityKey), new KeyValuePair <string, EntityKey>(this.RelationshipNavigation.To, this.DetachedEntityKey)); if (relationship == null) { return; } relationship.Delete(false); if (relationship.State == EntityState.Detached) { return; } relationship.AcceptChanges(); }
protected RelationshipEntry CreateRelationshipData(string relatedType = null, bool isToManyData = false, string id = "10") { var entry = new RelationshipEntry(); var rio = relatedType == null ? null : new ResourceIdentifierObject { Id = id, Type = relatedType }; if (isToManyData) { entry.Data = relatedType != null?rio.AsList() : new List <ResourceIdentifierObject>(); } else { entry.Data = rio; } return(entry); }
/// <summary> /// Builds the values of the relationships object on a resource object. /// The server serializer only populates the "data" member when the relationship is included, /// and adds links unless these are turned off. This means that if a relationship is not included /// and links are turned off, the entry would be completely empty, ie { }, which is not conform /// JSON:API spec. In that case we return null which will omit the entry from the output. /// </summary> protected override RelationshipEntry GetRelationshipData(RelationshipAttribute relationship, IIdentifiable resource) { if (relationship == null) { throw new ArgumentNullException(nameof(relationship)); } if (resource == null) { throw new ArgumentNullException(nameof(resource)); } RelationshipEntry relationshipEntry = null; List <IReadOnlyCollection <RelationshipAttribute> > relationshipChains = null; if (Equals(relationship, _requestRelationship) || ShouldInclude(relationship, out relationshipChains)) { relationshipEntry = base.GetRelationshipData(relationship, resource); if (relationshipChains != null && relationshipEntry.HasResource) { foreach (var chain in relationshipChains) { // traverses (recursively) and extracts all (nested) related resources for the current inclusion chain. _includedBuilder.IncludeRelationshipChain(chain, resource); } } } if (!IsRelationshipInSparseFieldSet(relationship)) { return(null); } var links = _linkBuilder.GetRelationshipLinks(relationship, resource); if (links != null) { // if relationshipLinks should be built for this entry, populate the "links" field. relationshipEntry ??= new RelationshipEntry(); relationshipEntry.Links = links; } // if neither "links" nor "data" was populated, return null, which will omit this entry from the output. // (see the NullValueHandling settings on <see cref="ResourceObject"/>) return(relationshipEntry); }
public void Setting_ExposeData_To_RIO_Sets_SingleData() { // Arrange var relationshipData = new RelationshipEntry(); var relationship = new ResourceIdentifierObject { Id = "9", Type = "authors" }; // Act relationshipData.Data = relationship; // Assert Assert.NotNull(relationshipData.SingleData); Assert.Equal("authors", relationshipData.SingleData.Type); Assert.Equal("9", relationshipData.SingleData.Id); Assert.False(relationshipData.IsManyData); }
/// <summary> /// Serializes the inline relationship. /// </summary> /// <param name="xmlWriter">The XML writer.</param> /// <param name="relationship">The relationship.</param> /// <param name="defaultElementName">Default name of the element.</param> /// <param name="xmlStack">The XML stack.</param> private void SerializeInlineRelationship(XmlWriter xmlWriter, RelationshipEntry relationship, string defaultElementName, Stack <string> xmlStack) { string elementName; string nameSpace; bool resolved = TryGetElementName(relationship.TypeId, out elementName, out nameSpace); xmlWriter.WriteStartElement(elementName ?? defaultElementName, nameSpace, xmlStack); if (!resolved) { xmlWriter.WriteAttributeString(XmlConstants.Id, relationship.TypeId.ToString("B")); } xmlWriter.WriteString(GetInnerText(relationship.ToId)); xmlWriter.WriteEndElement(elementName ?? defaultElementName, xmlStack); }
private void ExcludeEntityKey() { EntityKey ownerKey = WrappedOwner.EntityKey; RelationshipEntry relationshipEntry = this.ObjectContext.ObjectStateManager.FindRelationship(RelationshipSet, new KeyValuePair <string, EntityKey>(RelationshipNavigation.From, ownerKey), new KeyValuePair <string, EntityKey>(RelationshipNavigation.To, DetachedEntityKey)); // we may have failed in adding the graph before we actually added this relationship, so make sure we actually found one if (relationshipEntry != null) { relationshipEntry.Delete(/*doFixup*/ false); // If entry was Added before, it is now Detached, otherwise AcceptChanges to detach it if (relationshipEntry.State != EntityState.Detached) { relationshipEntry.AcceptChanges(); } } }
/// <summary> /// Sets a HasMany relationship. /// </summary> private void SetHasManyRelationship( IIdentifiable entity, HasManyAttribute attr, RelationshipEntry relationshipData) { if (relationshipData.Data != null) { // if the relationship is set to null, no need to set the navigation property to null: this is the default value. var relatedResources = relationshipData.ManyData.Select(rio => { var relatedInstance = attr.RightType.New <IIdentifiable>(); relatedInstance.StringId = rio.Id; return(relatedInstance); }); var convertedCollection = relatedResources.Cast(attr.RightType); attr.SetValue(entity, convertedCollection); } AfterProcessField(entity, attr, relationshipData); }
/// <summary> /// Load relationships. /// </summary> /// <param name="context"></param> /// <returns></returns> public IEnumerable <RelationshipEntry> GetRelationships(IProcessingContext context) { ///// // Query entities that are part of the solution ///// const string sql = @"select TypeUid, FromUid, ToUid from _Relationship r"; using (IDbCommand command = CreateCommand( )) { command.CommandText = sql; using (IDataReader reader = command.ExecuteReader( )) { while (reader.Read( )) { Guid?typeId = GetGuid(reader, 0); if (typeId == null) { continue; } Guid?fromId = GetGuid(reader, 1); if (fromId == null) { continue; } Guid?toId = GetGuid(reader, 2); if (toId == null) { continue; } RelationshipEntry entry = new RelationshipEntry(typeId.Value, fromId.Value, toId.Value); yield return(entry); } } } }
/// <summary> /// This is the implementation API for returning relationships. The default implementation stores the /// relationship in a table. Relationships are stored weakly, so they do not keep an object alive. Empty can be /// passed in for relationship to remove the relationship. /// </summary> protected virtual void SetRelationship(MemberRelationship source, MemberRelationship relationship) { if (!relationship.IsEmpty && !SupportsRelationship(source, relationship)) { string sourceName = TypeDescriptor.GetComponentName(source.Owner); string relName = TypeDescriptor.GetComponentName(relationship.Owner); if (sourceName == null) { sourceName = source.Owner.ToString(); } if (relName == null) { relName = relationship.Owner.ToString(); } throw new ArgumentException(SR.Format(SR.MemberRelationshipService_RelationshipNotSupported, sourceName, source.Member.Name, relName, relationship.Member.Name)); } _relationships[new RelationshipEntry(source)] = new RelationshipEntry(relationship); }
/// <summary> /// Load relationships. /// </summary> /// <param name="context"></param> /// <returns></returns> IEnumerable <RelationshipEntry> IDataSource.GetRelationships(IProcessingContext context) { if (_relationshipCache == null) { var data = new List <RelationshipEntry>( ); ///// // Query entities that are part of the solution ///// using (IDbCommand command = CreateCommand( )) { command.CommandText = CommandText.TenantSourceGetRelationshipsCommandText; command.CommandType = CommandType.Text; command.AddParameterWithValue("@tenant", TenantId); using (IDataReader reader = command.ExecuteReader( )) { while (reader.Read( )) { if (reader.IsDBNull(0)) { context?.WriteWarning("Unexpected null UpgradeId in Entity."); continue; } Guid typeId = reader.GetGuid(0); Guid fromId = reader.GetGuid(1); Guid toId = reader.GetGuid(2); RelationshipEntry entry = new RelationshipEntry(typeId, fromId, toId); data.Add(entry); } } } _relationshipCache = data; } return(_relationshipCache); }
/// <summary> /// Sets a HasMany relationship. /// </summary> private void SetHasManyRelationship( IIdentifiable resource, HasManyAttribute attr, RelationshipEntry relationshipData) { if (relationshipData.Data != null) { // if the relationship is set to null, no need to set the navigation property to null: this is the default value. var relatedResources = relationshipData.ManyData.Select(rio => { var relatedInstance = (IIdentifiable)ResourceFactory.CreateInstance(attr.RightType); relatedInstance.StringId = rio.Id; return(relatedInstance); }); var convertedCollection = TypeHelper.CopyToTypedCollection(relatedResources, attr.Property.PropertyType); attr.SetValue(resource, convertedCollection, ResourceFactory); } AfterProcessField(resource, attr, relationshipData); }
public void Setting_ExposeData_To_List_Sets_ManyData() { // Arrange var relationshipData = new RelationshipEntry(); var relationships = new List <ResourceIdentifierObject> { new ResourceIdentifierObject { Id = "9", Type = "authors" } }; // Act relationshipData.Data = relationships; // Assert Assert.NotEmpty(relationshipData.ManyData); Assert.Equal("authors", relationshipData.ManyData[0].Type); Assert.Equal("9", relationshipData.ManyData[0].Id); Assert.True(relationshipData.IsManyData); }
public void Setting_ExposeData_To_JObject_Sets_SingleData() { // Arrange var relationshipData = new RelationshipEntry(); const string relationshipJson = @"{ ""id"": ""9"", ""type"": ""authors"" }"; var relationship = JObject.Parse(relationshipJson); // Act relationshipData.Data = relationship; // Assert Assert.NotNull(relationshipData.SingleData); Assert.Equal("authors", relationshipData.SingleData.Type); Assert.Equal("9", relationshipData.SingleData.Id); Assert.False(relationshipData.IsManyData); }
protected RelationshipEntry CreateRelationshipData(string relatedType = null, bool isToManyData = false) { var data = new RelationshipEntry(); var rio = relatedType == null ? null : new ResourceIdentifierObject { Id = "10", Type = relatedType }; if (isToManyData) { data.Data = new List <ResourceIdentifierObject>(); if (relatedType != null) { ((List <ResourceIdentifierObject>)data.Data).Add(rio); } } else { data.Data = rio; } return(data); }
/// <summary> /// Sets a HasMany relationship. /// </summary> private void SetHasManyRelationship( IIdentifiable resource, HasManyAttribute hasManyRelationship, RelationshipEntry relationshipData) { if (relationshipData.ManyData == null) { throw new JsonApiSerializationException("Expected data[] element for to-many relationship.", $"Expected data[] element for '{hasManyRelationship.PublicName}' relationship."); } var rightResources = relationshipData.ManyData .Select(rio => CreateRightResource(hasManyRelationship, rio)) .ToHashSet(IdentifiableComparer.Instance); var convertedCollection = TypeHelper.CopyToTypedCollection(rightResources, hasManyRelationship.Property.PropertyType); hasManyRelationship.SetValue(resource, convertedCollection); AfterProcessField(resource, hasManyRelationship, relationshipData); }
public void Setting_ExposeData_To_JArray_Sets_ManyData() { // Arrange var relationshipData = new RelationshipEntry(); const string relationshipsJson = @"[ { ""type"": ""authors"", ""id"": ""9"" } ]"; var relationships = JArray.Parse(relationshipsJson); // Act relationshipData.Data = relationships; // Assert Assert.NotEmpty(relationshipData.ManyData); Assert.Equal("authors", relationshipData.ManyData[0].Type); Assert.Equal("9", relationshipData.ManyData[0].Id); Assert.True(relationshipData.IsManyData); }
internal override void Exclude() { if (this._wrappedCachedValue.Entity != null) { TransactionManager transactionManager = this.ObjectContext.ObjectStateManager.TransactionManager; bool doFixup = transactionManager.PopulatedEntityReferences.Contains((EntityReference)this); bool flag = transactionManager.AlignedEntityReferences.Contains((EntityReference)this); if ((transactionManager.ProcessedEntities == null || !transactionManager.ProcessedEntities.Contains(this._wrappedCachedValue)) && (doFixup || flag)) { RelationshipEntry relationshipEntry = this.IsForeignKey ? (RelationshipEntry)null : this.FindRelationshipEntryInObjectStateManager(this._wrappedCachedValue); this.Remove(this._wrappedCachedValue, doFixup, false, false, false, true); if (relationshipEntry != null && relationshipEntry.State != EntityState.Detached) { relationshipEntry.AcceptChanges(); } if (doFixup) { transactionManager.PopulatedEntityReferences.Remove((EntityReference)this); } else { transactionManager.AlignedEntityReferences.Remove((EntityReference)this); } } else { this.ExcludeEntity(this._wrappedCachedValue); } } else { if (!(this.DetachedEntityKey != (EntityKey)null)) { return; } this.ExcludeEntityKey(); } }
/// <summary> /// Serializes the relationship. /// </summary> /// <param name="xmlWriter">The XML writer.</param> /// <param name="relationship">The relationship.</param> /// <param name="defaultTypeName">Default name of the type.</param> /// <param name="xmlStack">The XML stack.</param> private void SerializeRelationship(XmlWriter xmlWriter, RelationshipEntry relationship, string defaultTypeName, Stack <string> xmlStack) { string typeAlias; string typeNamespace; bool resolvedType = TryGetElementName(relationship.TypeId, out typeAlias, out typeNamespace); xmlWriter.WriteStartElement(typeAlias ?? defaultTypeName, typeNamespace, xmlStack); if (!resolvedType) { xmlWriter.WriteAttributeString(XmlConstants.Id, relationship.TypeId.ToString("B")); } xmlWriter.WriteStartElement(XmlConstants.RelationshipConstants.From, xmlStack); xmlWriter.WriteString(GetInnerText(relationship.FromId)); xmlWriter.WriteEndElement(XmlConstants.RelationshipConstants.From, xmlStack); xmlWriter.WriteStartElement(XmlConstants.RelationshipConstants.To, xmlStack); xmlWriter.WriteString(GetInnerText(relationship.ToId)); xmlWriter.WriteEndElement(XmlConstants.RelationshipConstants.To, xmlStack); xmlWriter.WriteEndElement(typeAlias ?? defaultTypeName, xmlStack); }
protected override void AfterProcessField(IIdentifiable resource, ResourceFieldAttribute field, RelationshipEntry data = null) { }
/// <summary> /// Determines whether the specified entry is allowed. /// </summary> /// <param name="entry">The entry.</param> /// <returns></returns> public bool IsAllowed(RelationshipEntry entry) { return(RestrictionCheck(entry)); }
/// <devdoc> /// This is the implementation API for returning relationships. The default implementation stores the /// relationship in a table. Relationships are stored weakly, so they do not keep an object alive. Empty can be /// passed in for relationship to remove the relationship. /// </devdoc> protected virtual void SetRelationship(MemberRelationship source, MemberRelationship relationship) { if (!relationship.IsEmpty && !SupportsRelationship(source, relationship)) { string sourceName = TypeDescriptor.GetComponentName(source.Owner); string relName = TypeDescriptor.GetComponentName(relationship.Owner); if (sourceName == null) { sourceName = source.Owner.ToString(); } if (relName == null) { relName = relationship.Owner.ToString(); } throw new ArgumentException(SR.GetString(SR.MemberRelationshipService_RelationshipNotSupported, sourceName, source.Member.Name, relName, relationship.Member.Name)); } if (_relationships == null) { _relationships = new Dictionary<RelationshipEntry,RelationshipEntry>(); } _relationships[new RelationshipEntry(source)] = new RelationshipEntry(relationship); }