/// <inheritdoc /> public IReadOnlyList <Resource> SaveReferences(IUnitOfWork uow, Resource instance, ResourceEntity entity) { var context = new ReferenceSaverContext(uow, Graph, instance, entity); SaveReferences(context, instance); return(context.EntityCache.Keys.Where(i => i.Id == 0).ToList()); }
/// <summary> /// Create a <see cref="ResourceRelation"/> entity for a property match /// </summary> private static ResourceRelation CreateRelationForProperty(ReferenceSaverContext context, IResourceRelationRepository relationRepo, ResourceReferenceAttribute att) { var relationType = att.RelationType; var relEntity = relationRepo.Create((int)relationType); context.CreatedRelations.Add(relEntity); return(relEntity); }
/// <inheritdoc /> public IReadOnlyList <Resource> SaveRoots(IUnitOfWork uow, IReadOnlyList <Resource> instances) { var context = new ReferenceSaverContext(uow, Graph); foreach (var instance in instances) { SaveReferences(context, instance); } return(context.EntityCache.Keys.ToArray()); }
/// <inheritdoc /> public IReadOnlyList <Resource> SaveSingleCollection(IUnitOfWork uow, Resource instance, PropertyInfo property) { var entity = uow.GetEntity <ResourceEntity>(instance); var relations = ResourceRelationAccessor.FromEntity(uow, entity); var context = new ReferenceSaverContext(uow, Graph, instance, entity); var matches = MatchingRelations(relations, property); var typeMatches = TypeFilter(matches, property, context.ResolveReference).ToList(); var created = UpdateCollectionReference(context, entity, instance, property, typeMatches); foreach (var resource in created) { SaveReferences(context, resource); } return(context.EntityCache.Keys.Where(i => i.Id == 0).ToList()); }
/// <summary> /// Make sure our resource-relation graph in the database is synced to the resource object graph. This method /// updates a collection of references /// </summary> /// <example> /// [ResourceReference(ResourceRelationType.TransportRoute, ResourceReferenceRole.Source)] /// public IReferences<Resource> FriendResources { get; set; } /// </example> private IEnumerable <Resource> UpdateCollectionReference(ReferenceSaverContext context, ResourceEntity entity, Resource resource, PropertyInfo referenceProperty, IReadOnlyList <ResourceRelationAccessor> relationTemplates) { var relationRepo = context.UnitOfWork.GetRepository <IResourceRelationRepository>(); var referenceAtt = referenceProperty.GetCustomAttribute <ResourceReferenceAttribute>(); // Get the value stored in the reference property var propertyValue = referenceProperty.GetValue(resource); var referencedResources = ((IEnumerable <IResource>)propertyValue).Cast <Resource>().ToList(); // Check required attribute against empty collections if (referencedResources.Count == 0 && referenceAtt.IsRequired) { throw new ValidationException($"Property {referenceProperty.Name} is flagged 'Required' and was empty!"); } // 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(); foreach (var relation in deleted) { ClearOnTarget(context.ResolveReference(relation), resource, referenceAtt); relationRepo.Remove(relation.Entity); } // Now create new relations var created = referencedResources.Where(rr => relationTemplates.All(m => rr != context.ResolveReference(m))).ToList(); foreach (var createdReference in created) { SetOnTarget(createdReference, resource, referenceAtt); var relEntity = CreateRelationForProperty(context, relationRepo, referenceAtt); var referencedEntity = GetOrCreateEntity(context, createdReference); UpdateRelationEntity(entity, referencedEntity, relEntity, referenceAtt); } return(created.Where(cr => cr.Id == 0)); }
/// <summary> /// Get or create an entity for a resource instance /// </summary> private static ResourceEntity GetOrCreateEntity(ReferenceSaverContext context, Resource instance) { // First check if the context contains an entity for the instance if (context.EntityCache.ContainsKey(instance)) { return(context.EntityCache[instance]); } ResourceEntity entity; if (instance.Id > 0) { entity = context.UnitOfWork.GetEntity <ResourceEntity>(instance); } else { entity = ResourceEntityAccessor.SaveToEntity(context.UnitOfWork, instance); context.ResourceLookup[entity] = instance; } // Get or create an entity for the instance return(context.EntityCache[instance] = entity); }
private void SaveReferences(ReferenceSaverContext context, Resource instance) { var entity = GetOrCreateEntity(context, instance); var relations = 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(); if (typeof(IEnumerable <IResource>).IsAssignableFrom(referenceProperty.PropertyType)) { // Save a collection reference var created = UpdateCollectionReference(context, entity, instance, referenceProperty, typeMatches); createdResources.AddRange(created); } else { // Save a single reference var createdResource = UpdateSingleReference(context, entity, instance, referenceProperty, typeMatches); if (createdResource != null) { createdResources.Add(createdResource); } } } // Recursively save references for new resources foreach (var resource in createdResources) { SaveReferences(context, resource); } }
/// <summary> /// Make sure our resource-relation graph in the database is synced to the resource object graph. This method /// updates single references like in the example below /// </summary> /// <example> /// [ResourceReference(ResourceRelationType.TransportRoute, ResourceReferenceRole.Source)] /// public Resource FriendResource { get; set; } /// </example> private Resource UpdateSingleReference(ReferenceSaverContext context, ResourceEntity entity, Resource resource, PropertyInfo referenceProperty, IReadOnlyList <ResourceRelationAccessor> matches) { var relationRepo = context.UnitOfWork.GetRepository <IResourceRelationRepository>(); var value = referenceProperty.GetValue(resource); var referencedResource = value as Resource; // Validate if object assigned to the property is a resource if (value != null && referencedResource == null) { throw new ArgumentException($"Value of property {referenceProperty.Name} on resource {resource.Id}:{resource.GetType().Name} must be a Resource"); } var referenceAtt = referenceProperty.GetCustomAttribute <ResourceReferenceAttribute>(); // Validate if required property is set if (referencedResource == null && referenceAtt.IsRequired) { 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))) { 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)) 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); } else if (relEntity != null && referencedResource == null) { // Delete a relation, that no longer exists ClearOnTarget(context.ResolveReference(relMatch), resource, referenceAtt); relationRepo.Remove(relEntity); return(null); } // ReSharper disable once ConditionIsAlwaysTrueOrFalse <<- To identify the remaining case else if (relEntity == null && referencedResource == null) { // Relation did not exist before and still does not return(null); } // Relation was updated, make sure the backlinks match else { ClearOnTarget(context.ResolveReference(relMatch), resource, referenceAtt); SetOnTarget(referencedResource, resource, referenceAtt); } // Set source and target of the relation depending on the reference roles var referencedEntity = GetOrCreateEntity(context, referencedResource); UpdateRelationEntity(entity, referencedEntity, relEntity, referenceAtt); // Return referenced resource if it is new return(referencedResource.Id == 0 ? referencedResource : null); }