Skip to content

Commit

Permalink
feat(search): search graph (#56)
Browse files Browse the repository at this point in the history
  • Loading branch information
SwimmingRieux authored Aug 28, 2024
1 parent 9333db9 commit 004afb3
Show file tree
Hide file tree
Showing 16 changed files with 238 additions and 19 deletions.
8 changes: 4 additions & 4 deletions RelationshipAnalysis/Controllers/AttributesController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ namespace RelationshipAnalysis.Controllers;
public class AttributesController([FromKeyedServices("node")] IAttributesReceiver nodeAttributeReceiver,[FromKeyedServices("edge")] IAttributesReceiver edgeAttributeReceiver) : ControllerBase
{
[HttpGet("nodes")]
public async Task<IActionResult> GetNodeAttributes(int nodeCategoryId)
public async Task<IActionResult> GetNodeAttributes(string nodeCategoryName)
{
var result = await nodeAttributeReceiver.GetAllAttributes(nodeCategoryId);
var result = await nodeAttributeReceiver.GetAllAttributes(nodeCategoryName);
return Ok(result);
}
[HttpGet("edges")]
public async Task<IActionResult> GetEdgeAttributes(int edgeCategoryId)
public async Task<IActionResult> GetEdgeAttributes(string edgeCategoryName)
{
var result = await edgeAttributeReceiver.GetAllAttributes(edgeCategoryId);
var result = await edgeAttributeReceiver.GetAllAttributes(edgeCategoryName);
return Ok(result);
}
}
11 changes: 10 additions & 1 deletion RelationshipAnalysis/Controllers/Graph/GraphController.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using RelationshipAnalysis.Dto.Graph;
using RelationshipAnalysis.Services.GraphServices.Abstraction;
using RelationshipAnalysis.Services.GraphServices.Graph.Abstraction;

Expand All @@ -9,13 +10,21 @@ namespace RelationshipAnalysis.Controllers.Graph;
[Authorize]
[Route("api/[controller]")]
public class GraphController(IGraphReceiver graphReceiver,
IExpansionGraphReceiver expansionGraphReceiver) : ControllerBase
IExpansionGraphReceiver expansionGraphReceiver,
IGraphSearcherService searchService) : ControllerBase
{
[HttpGet]
public async Task<IActionResult> GetGraph()
{
return Ok(await graphReceiver.GetGraph());
}

[HttpPost]
public async Task<IActionResult> SearchGraph(SearchGraphDto searchGraphDto)
{
var result = await searchService.Search(searchGraphDto);
return StatusCode((int)result.StatusCode, result.Data);
}
[HttpGet("expansion")]
public async Task<IActionResult> GetExpansionGraph([FromQuery] int nodeId,[FromQuery] string sourceCategoryName,[FromQuery] string targetCategoryName,[FromQuery] string edgeCategoryName)
{
Expand Down
1 change: 1 addition & 0 deletions RelationshipAnalysis/Dto/Graph/GraphDto.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ public class GraphDto
{
public List<NodeDto> Nodes { get; set; } = [];
public List<EdgeDto> Edges { get; set; } = [];
public string Message { get; set; }
}
19 changes: 19 additions & 0 deletions RelationshipAnalysis/Dto/Graph/SearchGraphDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace RelationshipAnalysis.Dto.Graph;
using System.ComponentModel.DataAnnotations;


public class SearchGraphDto
{
[Required]
public string SourceCategoryName { get; set; }
[Required]
public string TargetCategoryName { get; set; }
[Required]
public string EdgeCategoryName { get; set; }
[Required]
public Dictionary<string, string> SourceCategoryClauses { get; set; }
[Required]
public Dictionary<string, string> TargetCategoryClauses { get; set; }
[Required]
public Dictionary<string, string> EdgeCategoryClauses { get; set; }
}
3 changes: 3 additions & 0 deletions RelationshipAnalysis/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@
using RelationshipAnalysis.Services.Panel.UserPanelServices.UserUpdatePasswordService;
using RelationshipAnalysis.Services.Panel.UserPanelServices.UserUpdatePasswordService.Abstraction;
using RelationshipAnalysis.Settings.JWT;
using EdgeAttributesReceiver = RelationshipAnalysis.Services.GraphServices.Edge.EdgeAttributesReceiver;
using NodeAttributesReceiver = RelationshipAnalysis.Services.GraphServices.Node.NodeAttributesReceiver;

var builder = WebApplication.CreateBuilder(args);
builder.Configuration
Expand Down Expand Up @@ -112,6 +114,7 @@
.AddSingleton<ICsvValidatorService, CsvValidatorService>()
.AddSingleton<IExpansionGraphReceiver, ExpansionGraphReceiver>()
.AddSingleton<IGraphDtoCreator, GraphDtoCreator>()
.AddSingleton<IGraphSearcherService, GraphSearcherService>()
.AddKeyedSingleton<IInfoReceiver, NodeInfoReceiver>("node")
.AddKeyedSingleton<IInfoReceiver, EdgeInfoReceiver>("edge")
.AddKeyedSingleton<IAttributesReceiver, NodeAttributesReceiver>("node")
Expand Down
18 changes: 17 additions & 1 deletion RelationshipAnalysis/Resources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions RelationshipAnalysis/Resources.resx
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>

<root>
<data name="InvalidClauseInSourceCategory">
<value>One or more given attributes in source category is invalid</value>
</data>
<data name="InvalidClauseInDestinationCategory">
<value>One or more given attributes in target category is invalid</value>
</data>
<data name="InvalidClauseInEdgeCategory">
<value>One or more given attributes in edge category is invalid</value>
</data>

<data name="InvalidTargetNodeCategory">
<value>Target Node category is invalid!</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ namespace RelationshipAnalysis.Services.GraphServices.Abstraction;

public interface IAttributesReceiver
{
Task<List<string>> GetAllAttributes(int id);
Task<List<string>> GetAllAttributes(string name);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@
using RelationshipAnalysis.Context;
using RelationshipAnalysis.Services.GraphServices.Abstraction;

namespace RelationshipAnalysis.Services.GraphServices;
namespace RelationshipAnalysis.Services.GraphServices.Edge;

public class EdgeAttributesReceiver(IServiceProvider serviceProvider) : IAttributesReceiver
{
public async Task<List<string>> GetAllAttributes(int id)
public async Task<List<string>> GetAllAttributes(string name)
{

using var scope = serviceProvider.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();

return await context.EdgeAttributes
.Where(ea => ea.EdgeValues.Any(v => v.Edge.EdgeCategoryId == id))
.Where(ea => ea.EdgeValues.Any(v => v.Edge.EdgeCategory.EdgeCategoryName == name))
.Select(ea => ea.EdgeAttributeName).ToListAsync();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using RelationshipAnalysis.Dto;
using RelationshipAnalysis.Dto.Graph;

namespace RelationshipAnalysis.Services.GraphServices.Graph.Abstraction;

public interface IGraphSearcherService
{
Task<ActionResponse<GraphDto>> Search(SearchGraphDto searchGraphDto);
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,32 +14,41 @@ public async Task<GraphDto> GetExpansionGraph(int nodeId, string sourceCategoryN
using var scope = serviceProvider.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();

var inputNodes = await GetInputNodes(sourceCategoryName, context);
var outputNodes = await GetOutputNodes(targetCategoryName, context);
var validEdges = await GetValidEdges(edgeCategoryName, context, inputNodes, outputNodes);
var inputNodes = await GetInputNodes(sourceCategoryName);
var outputNodes = await GetOutputNodes(targetCategoryName);
var validEdges = await GetValidEdges(edgeCategoryName, inputNodes, outputNodes);

return graphDtoCreator.CreateResultGraphDto(inputNodes.Union(outputNodes).ToList(), validEdges);
}

private async Task<List<Models.Graph.Edge.Edge>> GetValidEdges(string edgeCategoryName, ApplicationDbContext context, List<Models.Graph.Node.Node> inputNodes,
private async Task<List<Models.Graph.Edge.Edge>> GetValidEdges(string edgeCategoryName, List<Models.Graph.Node.Node> inputNodes,
List<Models.Graph.Node.Node> outputNodes)
{
using var scope = serviceProvider.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();

var validEdges = await context.Edges.Where(e =>
e.EdgeCategory.EdgeCategoryName == edgeCategoryName &&
inputNodes.Contains(e.NodeSource) &&
outputNodes.Contains(e.NodeDestination)).ToListAsync();
return validEdges;
}

private async Task<List<Models.Graph.Node.Node>> GetOutputNodes(string targetCategoryName, ApplicationDbContext context)
private async Task<List<Models.Graph.Node.Node>> GetOutputNodes(string targetCategoryName)
{
using var scope = serviceProvider.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();

var outputNodes =
await context.Nodes.Where(n => n.NodeCategory.NodeCategoryName == targetCategoryName).ToListAsync();
return outputNodes;
}

private async Task<List<Models.Graph.Node.Node>> GetInputNodes(string sourceCategoryName, ApplicationDbContext context)
private async Task<List<Models.Graph.Node.Node>> GetInputNodes(string sourceCategoryName)
{
using var scope = serviceProvider.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();

var inputNodes =
await context.Nodes.Where(n => n.NodeCategory.NodeCategoryName == sourceCategoryName).ToListAsync();
return inputNodes;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using RelationshipAnalysis.Dto;
using RelationshipAnalysis.Dto.Graph;
using RelationshipAnalysis.Enums;
using RelationshipAnalysis.Models.Graph.Edge;
using RelationshipAnalysis.Models.Graph.Node;
using RelationshipAnalysis.Services.GraphServices.Abstraction;
using RelationshipAnalysis.Services.GraphServices.Graph.Abstraction;
using ApplicationDbContext = RelationshipAnalysis.Context.ApplicationDbContext;

namespace RelationshipAnalysis.Services.GraphServices.Graph;

public class GraphSearcherService(
IGraphDtoCreator graphDtoCreator,
IServiceProvider serviceProvider,
[FromKeyedServices("node")] IAttributesReceiver nodeCategoryReceiver,
[FromKeyedServices("edge")] IAttributesReceiver edgeCategoryReceiver) : IGraphSearcherService
{
public async Task<ActionResponse<GraphDto>> Search(SearchGraphDto searchGraphDto)
{
using var scope = serviceProvider.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();


var sourceAttributes = await nodeCategoryReceiver.GetAllAttributes(searchGraphDto.SourceCategoryName);
var targetAttributes = await nodeCategoryReceiver.GetAllAttributes(searchGraphDto.TargetCategoryName);
var edgeAttributes = await edgeCategoryReceiver.GetAllAttributes(searchGraphDto.EdgeCategoryName);

var validation = await AreClausesValid(searchGraphDto, sourceAttributes, targetAttributes, edgeAttributes);
if (validation.StatusCode != StatusCodeType.Success)
{
return validation;
}

var sourceNodes = await context.Nodes
.Where(n => searchGraphDto.SourceCategoryName == (n.NodeCategory.NodeCategoryName)).ToListAsync();
var targetNodes = await context.Nodes
.Where(n => searchGraphDto.TargetCategoryName == (n.NodeCategory.NodeCategoryName)).ToListAsync();

sourceNodes = sourceNodes.Where(sn => IsNodeValid(sn, searchGraphDto.SourceCategoryClauses)).ToList();
targetNodes = targetNodes.Where(tn => IsNodeValid(tn, searchGraphDto.TargetCategoryClauses)).ToList();

var edges = await GetValidEdges(sourceNodes, targetNodes, searchGraphDto.SourceCategoryName,
searchGraphDto.TargetCategoryName, searchGraphDto.EdgeCategoryName);

edges = edges.Where(e => IsEdgeValid(e, searchGraphDto.EdgeCategoryClauses)).ToList();

validation.Data = graphDtoCreator.CreateResultGraphDto(sourceNodes.Union(targetNodes).ToList(), edges);
return validation;
}


private bool IsNodeValid(Models.Graph.Node.Node node, Dictionary<string, string> clauses)
{
var attributeValues = new Dictionary<string, string>();
node.Values.ToList().ForEach(nv => attributeValues.Add(nv.NodeAttribute.NodeAttributeName, nv.ValueData));

foreach (var kvp in clauses)
{
var actualValue = attributeValues[kvp.Key];
if (!actualValue.StartsWith(kvp.Value)) return false;
}

return true;
}

private bool IsEdgeValid(Models.Graph.Edge.Edge edge, Dictionary<string, string> clauses)
{
var attributeValues = new Dictionary<string, string>();
edge.EdgeValues.ToList().ForEach(ev => attributeValues.Add(ev.EdgeAttribute.EdgeAttributeName, ev.ValueData));

foreach (var kvp in clauses)
{
var actualValue = attributeValues[kvp.Key];
if (!actualValue.StartsWith(kvp.Value)) return false;
}

return true;
}

private async Task<List<Models.Graph.Edge.Edge>> GetValidEdges(List<Models.Graph.Node.Node> sourceNodes,
List<Models.Graph.Node.Node> targetNodes, string sourceCategory, string targetCategory,
string edgeCategory)
{
var sourceNodeIds = sourceNodes.Select(n => n.NodeId).ToList();
var targetNodeIds = targetNodes.Select(n => n.NodeId).ToList();

using var scope = serviceProvider.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();

var edges = await context.Edges.Include(e => e.EdgeValues)
.ThenInclude(ev => ev.EdgeAttribute)
.Where(e => edgeCategory == (e.EdgeCategory.EdgeCategoryName) &&
sourceNodeIds.Contains(e.EdgeSourceNodeId) &&
targetNodeIds.Contains(e.EdgeDestinationNodeId))
.ToListAsync();
return edges;
}

private async Task<ActionResponse<GraphDto>> AreClausesValid(SearchGraphDto searchGraphDto,
List<string> sourceAttributes, List<string> targetAttributes,
List<string> edgeAttributes)
{
if (!searchGraphDto.SourceCategoryClauses.Keys.All(item => sourceAttributes.Contains(item)))
{
return NotFoundResult(Resources.InvalidClauseInSourceCategory);
}

if (!searchGraphDto.TargetCategoryClauses.Keys.All(item => targetAttributes.Contains(item)))
{
return NotFoundResult(Resources.InvalidClauseInDestinationCategory);
}

if (!searchGraphDto.EdgeCategoryClauses.Keys.All(item => edgeAttributes.Contains(item)))
{
return NotFoundResult(Resources.InvalidClauseInDestinationCategory);
}

return SuccessResult();
}

private ActionResponse<GraphDto> SuccessResult()
{
return new ActionResponse<GraphDto>()
{
StatusCode = StatusCodeType.Success,
Data = null
};
}

private ActionResponse<GraphDto> NotFoundResult(string message)
{
return new ActionResponse<GraphDto>()
{
StatusCode = StatusCodeType.NotFound,
Data = new GraphDto()
{
Message = message
}
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@
using RelationshipAnalysis.Context;
using RelationshipAnalysis.Services.GraphServices.Abstraction;

namespace RelationshipAnalysis.Services.GraphServices;
namespace RelationshipAnalysis.Services.GraphServices.Node;

public class NodeAttributesReceiver(IServiceProvider serviceProvider) : IAttributesReceiver
{
public async Task<List<string>> GetAllAttributes(int id)
public async Task<List<string>> GetAllAttributes(string name)
{
using var scope = serviceProvider.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();

return await context.NodeAttributes
.Where(na => na.Values.Any(v => v.Node.NodeCategoryId == id))
.Where(na => na.Values.Any(v => v.Node.NodeCategory.NodeCategoryName == name))
.Select(na => na.NodeAttributeName).ToListAsync();
}
}

0 comments on commit 004afb3

Please sign in to comment.