在Neo4j v1.9.x下,我使用了以下类型的代码。
private Category CreateNodeCategory(Category cat)
{
var node = client.Create(cat,
new IRelationshipAllowingParticipantNode<Category>[0],
new[]
{
new IndexEntry(NeoConst.IDX_Category)
{
{ NeoConst.PRP_Name, cat.Name },
{ NeoConst.PRP_Guid, cat.Nguid.ToString() }
}
});
cat.Nid = node.Id;
client.Update<Category>(node, cat);
return cat;
}
原因是节点ID是自动生成的,我可以稍后使用它来快速查找,在其他查询中启动位等。如下所示:
private Node<Category> CategoryGet(long nodeId)
{
return client.Get<Category>((NodeReference<Category>)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<Category>>("n")
.Results.FirstOrDefault();
if (node != null) { cat = node.Data; }
return cat;
}
现在cypher Wiki,示例和坏习惯建议在所有CRUD中使用.ExecuteWithoutResults()。
所以问题是你如何获得节点ID的自动增量值?
答案 0 :(得分:20)
首先,对于Neo4j 2及以后,你总是需要从参考框架开始“我将如何在Cypher中做到这一点?”。然后,只有这样,你是否担心C#。
现在,提炼您的问题,听起来您的主要目标是创建一个节点,然后返回对它的引用以便进一步工作。
您可以使用以下命令在密码中执行此操作:
CREATE (myNode)
RETURN myNode
在C#中,这将是:
var categoryNode = graphClient.Cypher
.Create("(category {cat})")
.WithParams(new { cat })
.Return(cat => cat.Node<Category>())
.Results
.Single();
然而,这仍然不是您在原始CreateNodeCategory
方法中所做的100%。您正在DB中创建节点,获取Neo4j的内部标识符,然后将该标识符保存回同一节点。基本上,您正在使用Neo4j为您生成自动递增数字。这是功能性的,但不是一个好方法。我会解释更多...
首先,Neo4j甚至为您提供节点ID的概念正在消失。它是内部标识符,实际上恰好是磁盘上的文件偏移量。它可以改变。这是低水平。如果您考虑SQL一秒钟,您是否使用SQL查询来获取行的文件字节偏移量,然后引用它以供将来更新?答:不;你编写一个查询,在一次点击中找到并操纵该行。
现在,我注意到节点上已经有Nguid
属性。为什么你不能用它作为id?或者,如果名称始终是唯一的,请使用它? (域相关ID总是比魔术数字更好。)如果两者都不合适,您可能希望查看像SnowMaker这样的项目来帮助您。
接下来,我们需要查看索引。您正在使用的索引类型在2.0文档中称为"Legacy Indexing",并且错过了一些很酷的Neo4j 2.0功能。
对于本答复的其余部分,我将假设您的Category
课程如下:
public class Category
{
public Guid UniqueId { get; set; }
public string Name { get; set; }
}
让我们首先使用label:
创建我们的类别节点var category = new Category { UnqiueId = Guid.NewGuid(), Name = "Spanners" };
graphClient.Cypher
.Create("(category:Category {category})")
.WithParams(new { category })
.ExecuteWithoutResults();
并且,作为一次性操作,让我们在Name
标签的任何节点的Category
属性上建立schema-based index:
graphClient.Cypher
.Create("INDEX ON :Category(Name)")
.ExecuteWithoutResults();
现在,我们无需担心手动保持索引最新。
我们还可以在UniqueId
上引入索引和unique constraint:
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<Category>())
.Results
.Single();
而不是查找类别节点,然后再进行另一个查询,只需一次完成:
var productsInCategory = graphClient.Cypher
.Match("(c:Category)<-[:IN_CATEGORY]-(p:Product)")
.Where((Category c) => c.UniqueId == someGuidVariable)
.Return(p => p.As<Product>())
.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
keyword:
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<Category>())
.Results
.Single();
}
基本上,
不要使用Neo4j的内部ID作为管理自己身份的方法。 (但是他们可能会在未来发布某种形式的自动编号。即使他们这样做,也会优先考虑电子邮件地址或SKU或机场代码或......等域名身份。您甚至不需要一个ID:您通常可以推断出节点基于其在图中的位置。)
通常,Node<T>
会随着时间消失。如果你现在使用它,你只是累积遗留代码。
查看标签和基于模式的索引。它们会让你的生活更轻松。
在一个查询中尝试并执行操作。它会快得多。
希望有所帮助!