如何在Neo4j v2中使用Neo4jClient创建节点?

在Neo4j v1.9.x下,我使用了以下类型的代码。

private Category CreateNodeCategory(Category cat) { var node = client.Create(cat, new IRelationshipAllowingParticipantNode[0], new[] { new IndexEntry(NeoConst.IDX_Category) { { NeoConst.PRP_Name, cat.Name }, { NeoConst.PRP_Guid, cat.Nguid.ToString() } } }); cat.Nid = node.Id; client.Update(node, cat); return cat; } 

原因是节点ID是自动生成的,我可以稍后使用它来快速查找,在其他查询中启动位等。如下所示:

  private Node CategoryGet(long nodeId) { return client.Get((NodeReference)nodeId); } 

这使得以下看起来效果很好。

  public Category CategoryAdd(Category cat) { cat = CategoryFind(cat); if (cat.Nid != 0) { return cat; } return CreateNodeCategory(cat); } public Category CategoryFind(Category cat) { if (cat.Nid != 0) { return cat; } var node = client.Cypher.Start(new { n = Node.ByIndexLookup(NeoConst.IDX_Category, NeoConst.PRP_Name, cat.Name)}) .Return<Node>("n") .Results.FirstOrDefault(); if (node != null) { cat = node.Data; } return cat; } 

现在,密码Wiki,示例和坏习惯建议在所有CRUD中使用.ExecuteWithoutResults()。

所以我的问题是你如何获得节点ID的自动增量值?

首先,对于Neo4j 2及其后,您始终需要从参考框架开始“如何在Cypher中执行此操作?”。 那么,只有这样,你是否担心C#。

现在,提炼您的问题,听起来您的主要目标是创建一个节点,然后返回对它的引用以进行进一步的工作。

你可以在cypher中执行以下操作:

 CREATE (myNode) RETURN myNode 

在C#中,这将是:

 var categoryNode = graphClient.Cypher .Create("(category {cat})") .WithParams(new { cat }) .Return(cat => cat.Node()) .Results .Single(); 

但是 ,这仍然不是您在原始CreateNodeCategory方法中所做的100%。 您正在DB中创建节点,获取Neo4j的内部标识符,然后将该标识符保存回同一节点。 基本上,您正在使用Neo4j为您生成自动递增数字。 这是function性的,但不是一个好方法。 我会解释更多……

首先,Neo4j甚至为您提供节点ID的概念正在消失。 它是一个内部标识符,实际上恰好是磁盘上的文件偏移量。 它可以改变。 这是低水平。 如果您考虑SQL一秒钟,您是否使用SQL查询来获取行的文件字节偏移量,然后引用它以用于将来的更新? 答:不; 您编写一个查询,在一次点击中查找和操作该行。

现在,我注意到你已经在节点上有一个Nguid属性。 为什么你不能用它作为id? 或者,如果名称始终是唯一的,请使用它? (域相关ID总是比幻数更好。)如果两者都不合适,你可能想看看像SnowMaker这样的项目来帮助你。

接下来,我们需要查看索引。 您正在使用的索引类型在2.0文档中称为“Legacy Indexing”,并且错过了一些很酷的Neo4j 2.0function。

对于本答案的其余部分,我将假设您的Category类看起来像这样:

 public class Category { public Guid UniqueId { get; set; } public string Name { get; set; } } 

让我们从创建带有标签的类别节点开始:

 var category = new Category { UnqiueId = Guid.NewGuid(), Name = "Spanners" }; graphClient.Cypher .Create("(category:Category {category})") .WithParams(new { category }) .ExecuteWithoutResults(); 

并且,作为一次性操作,让我们在具有Category标签的任何节点的Name属性上建立基于模式的索引 :

 graphClient.Cypher .Create("INDEX ON :Category(Name)") .ExecuteWithoutResults(); 

现在,我们不必担心手动保持索引是最新的。

我们还可以在UniqueId上引入索引和唯一约束 :

 graphClient.Cypher .Create("CONSTRAINT ON (category:Category) ASSERT category.UniqueId IS UNIQUE") .ExecuteWithoutResults(); 

现在查询非常简单:

 graphClient.Cypher .Match("(c:Category)") .Where((Category c) => c.UniqueId == someGuidVariable) .Return(c => c.As()) .Results .Single(); 

而不是查找类别节点,然后再做另一个查询,只需一次完成所有操作:

 var productsInCategory = graphClient.Cypher .Match("(c:Category)<-[:IN_CATEGORY]-(p:Product)") .Where((Category c) => c.UniqueId == someGuidVariable) .Return(p => p.As()) .Results; 

如果要更新类别,请同时执行此操作:

 graphClient.Cypher .Match("(c:Category)") .Where((Category c) => c.UniqueId == someGuidVariable) .Update("c = {category}") .WithParams(new { category }) .ExecuteWithoutResults(); 

最后,您的CategoryAdd方法目前1)执行一次数据库命中以查找现有节点,2)执行第二次数据库命中以创建新节点,3)第三次数据库命中以更新其上的ID。 相反,您可以使用MERGE关键字将所有这些压缩为单个调用:

 public Category GetOrCreateCategoryByName(string name) { return graphClient.Cypher .WithParams(new { name, newIdIfRequired = Guid.NewGuid() }) .Merge("(c:Category { Name = {name})") .OnCreate("c") .Set("c.UniqueId = {newIdIfRequired}") .Return(c => c.As()) .Results .Single(); } 

基本上,

  1. 不要使用Neo4j的内部ID来破解管理自己的身份。 (但是他们可能会在未来发布某种forms的自动编号。即使他们这样做,也会优先考虑电子邮件地址或SKU或机场代码或……等域名身份。您甚至不需要一个ID:您通常可以推断出节点基于其在图中的位置。)

  2. 通常, Node会随着时间的推移而消失。 如果你现在使用它,你只是累积遗留代码。

  3. 查看标签和基于模式的索引。 他们会让你的生活更轻松。

  4. 尝试在一个查询中执行操作。 它会快得多。

希望有所帮助!