如何在 .NET 中使用 Neo4jClient 返回 Neo4j 路径? 总结技术版本数据结构示例数据密码查询回复Neo4jClient 查询底线

问题描述

总结

我正在尝试使用 .NET 中的 Neo4jClient 包从 Neo4j 获取 path。我想知道如何反序列化它,同时保持 table 响应中的强大值,但我似乎只能访问 text 响应。

免责声明 - 我见过 this answer,但它已经超过 7 年了,几乎不再相关了——更不用说最终结果看起来令人难以置信。

技术版本

  • Neo4j - 4.2.3
  • APOC - 4.2.0.1
  • Neo4jClient - 4.1.5
  • .NET 框架 - 4.7.2

数据结构

我有一个 Neo4j 数据库,其中包含三种类型的节点。为了回答这个问题,我将把它们归结为一个公司示例,因此我们将节点标签称为 EmployeeDepartmentProject。这些节点通过以下方式互连:

  • 每个 Employee 都与 EMPLOYED_BYDepartment 关系。
  • 每个 Project 都与 OWNED_BYDepartment 关系。
  • Employee 可以与 WORKS_ONProject 关系。

示例数据

为了这个问题,这个示例数据提供了数据结构的基线演示。

(:Employee {name:"Sarah Bradshaw"})
-[:EMPLOYED_BY {startDate:"2020-01-01"}]->
(:Department {name:"Finance"})
<-[:OWNED_BY {startDate:"2020-01-01"}]-
(:Project {name:"Quarterly Earnings"})
<-[:WORKS_ON {startDate:"2020-06-01"}]-
(:Employee {name:"Thomas Mitchell"})
-[:EMPLOYED_BY {startDate:"2019-01-01"}]->
(:Department {name:"Administration"})

A visual representation of the above data.

密码查询

这是我试图在 .NET 中使用 Neo4jClient 包复制的查询

MATCH (from:Employee {name:"Sarah Bradshaw"})
MATCH (to:Employee {name:"Thomas Mitchell"})
CALL apoc.algo.dijkstra(from,to,'','d')
YIELD path
RETURN path

回复

表格响应

{
    "start": {
        "identity": 0,"labels": [ "Employee" ],"properties": {
            "name": "Sarah Bradshaw"
        }
    },"end": {
        "identity": 3,"properties": {
            "name": "Thomas Mitchell"
        }
    },"segments": [
        {
            "start": {
                "identity": 0,"properties": {
                    "name": "Sarah Bradshaw"
                }
            },"relationship": {
                "identity": 0,"start": 0,"end": 1,"type": "EMPLOYED_BY","properties": {
                    "startDate": "2020-01-01"
                }
            },"end": {
                "identity": 1,"labels": [ "Department" ],"properties": {
                    "name": "Finance"
                }
            }
        },{
            "start": {
                "identity": 1,"properties": {
                    "name": "Finance"
                }
            },"relationship": {
                "identity": 1,"start": 2,"type": "OWNED_BY","end": {
                "identity": 2,"labels": [ "Project" ],"properties": {
                    "name": "Quarterly Earnings"
                }
            }
        },{
            "start": {
                "identity": 2,"properties": {
                    "name": "Quarterly Earnings"
                }
            },"relationship": {
                "identity": 2,"start": 3,"end": 2,"type": "WORKS_ON","properties": {
                    "startDate": "2020-06-01"
                }
            },"end": {
                "identity": 3,"properties": {
                    "name": "Thomas Mitchell"
                }
            }
        }
    ],"length": 3.0
}

文本回复

[
    {"name":"Sarah Bradshaw"},{"startDate":"2020-01-01"},{"name":"Finance"},{"name":"Quarterly Earnings"},{"startDate":"2020-06-01"},{"name":"Thomas Mitchell"}
]

如您所见,text 响应基本上没有用。不幸的是,这似乎是我能够通过 Neo4jClient 检索到的唯一响应值。

Neo4jClient 查询

这是基于上述查询派生的 Neo4jClient 语法。因为我只能获取 text 响应,所以我将其反序列化为 List 类型的 Datanode - 一个反映节点结构及其关系的简单模型。

client.Cypher
.Match("(from:Employee {name:\"Sarah Bradshaw\"})")
.Match("(to:Employee {name:\"Thomas Mitchell\"})")
.Call("apoc.algo.dijkstra(from,'d')")
.Yield("path")
.Return<List<Datanode>>("path")
.ResultsAsync
.Result;

底线

虽然这确实给我带来了一些东西,但问题是在 text 响应中没有返回任何使路径相关的东西。我有一组节点和关系,但我不知道它们是如何相互关联的。 table 响应列出了开始节点和结束节点以及我关心的信息。有什么方法可以让我查询 table 响应而不是 text 响应吗?

解决方法

好的,我已经假设您正在使用 BoltGraphClient - 与 GraphClient 一样,您几乎被卡住了,因为 REST 端点没有给出您想要的 ID。

PathsResultBolt 中有一个名为 Neo4jClient 的类 - 但是 它给出 System.Object 作为查询结果中每个属性的答案 - 即不太有用,所以你应该试试这个类:

public class PathsResultBolt<TNode,TRel>
{
    public PathsResultBolt()
    {
        Nodes = new List<PathsResultBoltNode<TNode>>();
        Relationships = new List<PathsResultBoltRelationship<TRel>>();
    }

    internal PathsResultBolt(IPath path)
    {
        Start = new PathsResultBoltNode<TNode>(path.Start);
        End = new PathsResultBoltNode<TNode>(path.End);
        Relationships = path.Relationships.Select(r => new PathsResultBoltRelationship<TRel>(r)).ToList();
        Nodes = path.Nodes.Select(r => new PathsResultBoltNode<TNode>(r)).ToList();
    }

    [JsonProperty("Start")]
    public PathsResultBoltNode<TNode> Start { get; set; }

    [JsonProperty("End")]
    public PathsResultBoltNode<TNode> End { get; set; }

    [JsonIgnore]
    public int Length => Relationships.Count();

    [JsonProperty("Nodes")]
    public List<PathsResultBoltNode<TNode>> Nodes { get; set; }

    [JsonProperty("Relationships")]
    public List<PathsResultBoltRelationship<TRel>> Relationships { get; set; }

    public class PathsResultBoltRelationship<T>
    {
        public long Id { get; set; }
        public string Type { get; set; }
        public long StartNodeId { get; set; }
        public long EndNodeId { get; set; }

        public object this[string key] => Properties[key];

        public Dictionary<string,T> Properties { get; set; }

        public PathsResultBoltRelationship() { Properties = new Dictionary<string,T>(); }

        public PathsResultBoltRelationship(IRelationship relationship)
        {
            Id = relationship.Id;
            StartNodeId = relationship.StartNodeId;
            EndNodeId = relationship.EndNodeId;
            Type = relationship.Type;
            Properties = relationship.Properties.ToDictionary(kvp => kvp.Key,kvp => kvp.Value.As<T>());
        }

        public bool Equals(PathsResultBoltRelationship<T> other)
        {
            if (other == null)
                return false;

            return Id == other.Id
                   && StartNodeId == other.StartNodeId
                   && EndNodeId == other.EndNodeId
                   && Type == other.Type
                   && Properties.ContentsEqual(other.Properties);
        }

        public override bool Equals(object obj)
        {
            return Equals(obj as PathsResultBoltRelationship<T>);
        }

        public override int GetHashCode()
        {
            var hashCode = 2105322407;
            hashCode = hashCode * -1521134295 + Id.GetHashCode();
            hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(Type);
            hashCode = hashCode * -1521134295 + StartNodeId.GetHashCode();
            hashCode = hashCode * -1521134295 + EndNodeId.GetHashCode();
            hashCode = hashCode * -1521134295 + EqualityComparer<IReadOnlyDictionary<string,T>>.Default.GetHashCode(Properties);
            return hashCode;
        }
    }

    public class PathsResultBoltNode<T>
    {
        public long Id { get; set; }
        public List<string> Labels { get; set; }
        public object this[string key] => Properties[key];
        public Dictionary<string,T> Properties { get; set; }

        public PathsResultBoltNode() { Properties = new Dictionary<string,T>(); }

        internal PathsResultBoltNode(INode node)
        {
            Id = node.Id;
            Labels = node.Labels?.ToList();
            Properties = node.Properties.ToDictionary(kvp => kvp.Key,kvp => kvp.Value.As<T>());
        }

        public bool Equals(PathsResultBoltNode<T> other)
        {
            if (other == null)
                return false;

            return Id == other.Id
                   && Labels.ContentsEqual(other.Labels)
                   && Properties.ContentsEqual(other.Properties);
        }

        public override bool Equals(object obj)
        {
            return Equals(obj as PathsResultBoltNode<T>);
        }

        public override int GetHashCode()
        {
            var hashCode = 1343812023;
            hashCode = hashCode * -1521134295 + Id.GetHashCode();
            hashCode = hashCode * -1521134295 + EqualityComparer<IReadOnlyList<string>>.Default.GetHashCode(Labels);
            hashCode = hashCode * -1521134295 + EqualityComparer<IReadOnlyDictionary<string,T>>.Default.GetHashCode(Properties);
            return hashCode;
        }
    }
}

你会像这样使用:

client.Cypher
  .Match("(from:Employee {name:\"Sarah Bradshaw\"})")
  .Match("(to:Employee {name:\"Thomas Mitchell\"})")
  .Call("apoc.algo.dijkstra(from,to,'','d')")
  .Yield("path")
  .Return<PathsResultBolt<string,string>>("path")
  .ResultsAsync
  .Result;

你问这个<string,string>是什么?第一个是节点的属性类型,第二个是关系。

现在。这有点垃圾 - 这归结为我不确定如何使实际对象值正确 objects 工作的事实。因此,目前最好的方法 (!!) 是对 node 和 rels 使用 string。在我使用标准电影数据库的测试中,我得到了这个:

Results of the query in LinqPad

born 属性是 string 但这是基本级别,使用 int 会导致错误,因为 name not 是 int .