Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Port: Fix exception on creating a bidirectional link to a new resource #486

Merged
merged 5 commits into from
Nov 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ protected ResourceModel ToModel(Resource current, bool partially)
Description = current.Description,

// Use simplified type reference
Type = current.ResourceType()
Type = current.ResourceType(),
};

// Set partial flag or load complex properties depending on details depth
Expand Down Expand Up @@ -197,14 +197,8 @@ internal ResourceTypeModel ConvertType(IResourceTypeNode node, ResourceTypeModel
Creatable = node.Creatable,
Name = node.Name,
BaseType = baseType?.Name,

// Read display name of the type otherwise use type short name
DisplayName = resType.GetCustomAttribute<DisplayNameAttribute>(false)?.DisplayName ??
Regex.Replace(resType.Name, @"`\d", string.Empty),

// Read description of the type
Description = resType.GetCustomAttribute<DescriptionAttribute>(false)?.Description,

DisplayName = resType.GetDisplayName() ?? Regex.Replace(resType.Name, @"`\d", string.Empty),
Description = resType.GetDescription(),
// Convert resource constructors
Constructors = node.Constructors.Select(ctr => EntryConvert.EncodeMethod(ctr, Serialization)).ToArray()
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<PropertyGroup>
<TargetFrameworks>net8.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<IsPackable>true</IsPackable>
<Description>ResourceManagement module composing and maintaining the resource graph as the habitat for digital twins of manufacturing assets.</Description>
<PackageTags>MORYX;IIoT;IoT;Manufacturing;API;Resource</PackageTags>
</PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,12 +181,12 @@ internal class ResourceRelationAccessor
public ResourceRelationType RelationType => (ResourceRelationType)Entity.RelationType;

/// <summary>
/// Id of the referenced resource
/// Id of the resource with the specified <see cref="Role"/> in the relation
/// </summary>
public long ReferenceId => Role == ResourceReferenceRole.Target ? Entity.TargetId : Entity.SourceId;

/// <summary>
/// References entity
/// Entity of the resource with the specified <see cref="Role"/> in the relation
/// </summary>
public ResourceEntity ReferenceEntity => Role == ResourceReferenceRole.Target ? Entity.Target : Entity.Source;

Expand Down
72 changes: 43 additions & 29 deletions src/Moryx.Resources.Management/Resources/ResourceLinker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,15 +98,15 @@ private void SaveReferences(ReferenceSaverContext context, Resource instance, Di
if(dict != null)
dict.Add(instance, entity);

var relations = ResourceRelationAccessor.FromEntity(context.UnitOfWork, entity)
var referenceAccessors = ResourceRelationAccessor.FromEntity(context.UnitOfWork, entity)
.Union(ResourceRelationAccessor.FromQueryable(context.CreatedRelations.AsQueryable(), entity))
.ToList();

var createdResources = new List<Resource>();
foreach (var referenceProperty in ReferenceProperties(instance.GetType(), false))
{
var matches = MatchingRelations(relations, referenceProperty);
var typeMatches = TypeFilter(matches, referenceProperty, context.ResolveReference).ToList();
var matches = MatchingRelations(referenceAccessors, referenceProperty);
var typeMatches = TypeFilter(matches, referenceProperty, context.ResolveReferencedResource).ToList();
if (typeof(IEnumerable<IResource>).IsAssignableFrom(referenceProperty.PropertyType))
{
// Save a collection reference
Expand Down Expand Up @@ -135,7 +135,7 @@ public IReadOnlyList<Resource> SaveSingleCollection(IUnitOfWork uow, Resource in

var context = new ReferenceSaverContext(uow, Graph, instance, entity);
var matches = MatchingRelations(relations, property);
var typeMatches = TypeFilter(matches, property, context.ResolveReference).ToList();
var typeMatches = TypeFilter(matches, property, context.ResolveReferencedResource).ToList();
var created = UpdateCollectionReference(context, entity, instance, property, typeMatches);

foreach (var resource in created)
Expand Down Expand Up @@ -169,27 +169,27 @@ private Resource UpdateSingleReference(ReferenceSaverContext context, ResourceEn
throw new ValidationException($"Property {referenceProperty.Name} is flagged 'Required' and was null!");

// Check if there is a relation that represents this reference
if (referencedResource != null && matches.Any(m => referencedResource == context.ResolveReference(m)))
if (referencedResource != null && matches.Any(m => referencedResource == context.ResolveReferencedResource(m)))
return null;

// Get all references of this resource with the same relation information
var currentReferences = CurrentReferences(resource, referenceAtt);

// Try to find a match that is not used in any reference
var relMatch = (from match in matches
where currentReferences.All(cr => cr != context.ResolveReference(match))
where currentReferences.All(cr => cr != context.ResolveReferencedResource(match))
select match).FirstOrDefault();
var relEntity = relMatch?.Entity;
if (relEntity == null && referencedResource != null)
{
// Create a new relation
relEntity = CreateRelationForProperty(context, relationRepo, referenceAtt);
SetOnTarget(referencedResource, resource, referenceAtt);
SetOnTarget(referencedResource, resource, referenceAtt, relEntity);
}
else if (relEntity != null && referencedResource == null)
{
// Delete a relation, that no longer exists
ClearOnTarget(context.ResolveReference(relMatch), resource, referenceAtt);
ClearOnTarget(context.ResolveReferencedResource(relMatch), resource, referenceAtt);
relationRepo.Remove(relEntity);
return null;
}
Expand All @@ -202,8 +202,8 @@ where currentReferences.All(cr => cr != context.ResolveReference(match))
// Relation was updated, make sure the backlinks match
else
{
ClearOnTarget(context.ResolveReference(relMatch), resource, referenceAtt);
SetOnTarget(referencedResource, resource, referenceAtt);
ClearOnTarget(context.ResolveReferencedResource(relMatch), resource, referenceAtt);
SetOnTarget(referencedResource, resource, referenceAtt, relEntity);
}

// Set source and target of the relation depending on the reference roles
Expand Down Expand Up @@ -237,19 +237,19 @@ private IEnumerable<Resource> UpdateCollectionReference(ReferenceSaverContext co

// First delete references that are not used by ANY property of the same configuration
var currentReferences = CurrentReferences(resource, referenceAtt);
var deleted = relationTemplates.Where(m => currentReferences.All(cr => cr != context.ResolveReference(m))).ToList();
var deleted = relationTemplates.Where(m => currentReferences.All(cr => cr != context.ResolveReferencedResource(m))).ToList();
foreach (var relation in deleted)
{
ClearOnTarget(context.ResolveReference(relation), resource, referenceAtt);
ClearOnTarget(context.ResolveReferencedResource(relation), resource, referenceAtt);
relationRepo.Remove(relation.Entity);
}

// Now create new relations
var created = referencedResources.Where(rr => relationTemplates.All(m => rr != context.ResolveReference(m))).ToList();
var created = referencedResources.Where(rr => relationTemplates.All(m => rr != context.ResolveReferencedResource(m))).ToList();
foreach (var createdReference in created)
{
SetOnTarget(createdReference, resource, referenceAtt);
var relEntity = CreateRelationForProperty(context, relationRepo, referenceAtt);
SetOnTarget(createdReference, resource, referenceAtt, relEntity);
var referencedEntity = GetOrCreateEntity(context, createdReference);
UpdateRelationEntity(entity, referencedEntity, relEntity, referenceAtt);
}
Expand Down Expand Up @@ -299,8 +299,7 @@ private static PropertyInfo FindBackLink(Resource target, Resource value, Resour
var propOnTarget = (from prop in ReferenceProperties(target.GetType(), false)
where IsInstanceOfReference(prop, value)
let backAtt = prop.GetCustomAttribute<ResourceReferenceAttribute>()
where backAtt.Name == referenceAtt.Name // Compare name
&& backAtt.RelationType == referenceAtt.RelationType // Compare relation type
where backAtt.RelationType == referenceAtt.RelationType // Compare relation type
&& backAtt.Role != referenceAtt.Role // Validate inverse role
select prop).FirstOrDefault();
return propOnTarget;
Expand All @@ -320,7 +319,7 @@ private static bool IsInstanceOfReference(PropertyInfo property, Resource value)
/// <summary>
/// Update backlink if possible
/// </summary>
private static void SetOnTarget(Resource target, Resource value, ResourceReferenceAttribute referenceAtt)
private static void SetOnTarget(Resource target, Resource value, ResourceReferenceAttribute referenceAtt, ResourceRelationEntity relationEntity)
{
var prop = FindBackLink(target, value, referenceAtt);
if (prop == null)
Expand All @@ -330,13 +329,11 @@ private static void SetOnTarget(Resource target, Resource value, ResourceReferen
{
prop.SetValue(target, value);
}
else
{
var collection = prop.GetValue(target) as IReferenceCollection;
if (collection != null && !collection.UnderlyingCollection.Contains(value))
collection.UnderlyingCollection.Add(value);
}

else if (prop.GetValue(target) is IReferenceCollection collection && !collection.UnderlyingCollection.Contains(value))
collection.UnderlyingCollection.Add(value);

var backAttr = prop.GetCustomAttribute<ResourceReferenceAttribute>();
UpdateRelationEntity(relationEntity, backAttr);
}

/// <summary>
Expand Down Expand Up @@ -404,18 +401,35 @@ private static void UpdateRelationEntity(ResourceEntity resource, ResourceEntity
{
if (att.Role == ResourceReferenceRole.Source)
{
relEntity.SourceId = referencedResource.Id;
relEntity.Source = referencedResource;
relEntity.TargetId = resource.Id;
relEntity.Target = resource;
relEntity.SourceName = att.Name;
}
else
{
relEntity.Source = resource;
relEntity.SourceId = resource.Id;
relEntity.Target = referencedResource;
relEntity.TargetId = referencedResource.Id;
relEntity.TargetName = att.Name;
}
}

/// <summary>
/// Set <see cref="ResourceRelationEntity.SourceName"/> and <see cref="ResourceRelationEntity.TargetName"/> depending on the <see cref="ResourceReferenceRole"/>
/// of the reference property
/// </summary>
private static void UpdateRelationEntity(ResourceRelationEntity relEntity, ResourceReferenceAttribute att)
{
if (att.Role == ResourceReferenceRole.Source)
relEntity.SourceName = att.Name;
else
relEntity.TargetName = att.Name;
}


/// <inheritdoc />
public void RemoveLinking(IResource deletedInstance, IResource reference)
{
Expand Down Expand Up @@ -477,8 +491,8 @@ public static IEnumerable<ResourceRelationAccessor> TypeFilter(IEnumerable<Resou
Func<ResourceRelationAccessor, Resource> instanceResolver)
{
return from relation in relations
let target = instanceResolver(relation)
where IsInstanceOfReference(property, target)
let other = instanceResolver(relation)
where IsInstanceOfReference(property, other)
select relation;
}

Expand Down Expand Up @@ -533,11 +547,11 @@ public ReferenceSaverContext(IUnitOfWork uow, IResourceGraph graph)
public IList<ResourceRelationEntity> CreatedRelations { get; }

/// <summary>
/// Resolve a relation reference
/// Resolve a referenced resource from a <paramref name="refAccessor"/>
/// </summary>
public Resource ResolveReference(ResourceRelationAccessor relation)
public Resource ResolveReferencedResource(ResourceRelationAccessor refAccessor)
{
return relation.ReferenceId > 0 ? _graph.Get(relation.ReferenceId) : ResourceLookup[relation.ReferenceEntity];
return refAccessor.ReferenceId > 0 ? _graph.Get(refAccessor.ReferenceId) : ResourceLookup[refAccessor.ReferenceEntity];
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ public ISimpleResource Reference
[ResourceReference(ResourceRelationType.CurrentExchangablePart)]
public DerivedResource Reference2 { get; set; }

[ResourceReference(ResourceRelationType.Extension, ResourceReferenceRole.Target, nameof(TargetReference))]
public BidirectionalReferenceResource TargetReference { get; set; }

[ResourceReference(ResourceRelationType.Extension, ResourceReferenceRole.Target, nameof(NewTargetReference))]
public BidirectionalReferenceResource NewTargetReference { get; set; }

[ResourceReference(ResourceRelationType.PossibleExchangablePart)]
public IReferences<ISimpleResource> References { get; set; }

Expand Down Expand Up @@ -98,11 +104,16 @@ public void SetMany(IReadOnlyList<ISimpleResource> references)
References.Add(reference);
}


public INonPublicResource NonPublic { get; set; }

public event EventHandler<ISimpleResource> ReferenceChanged;

public event EventHandler<ISimpleResource[]> SomeChanged;
}

public class BidirectionalReferenceResource : Resource
{
[ResourceReference(ResourceRelationType.Extension, ResourceReferenceRole.Source, nameof(SourceReference))]
public ReferenceResource SourceReference { get; set; }
}
}
Loading
Loading