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进行自动增量?
解决方法
现在,提炼你的问题,这听起来像你的主要目标是创建一个节点,然后返回一个引用来进一步的工作.
你可以在cypher中做到这一点:
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查询来获取一行的文件字节偏移量,然后参考以供将来更新? A:不你写一个查询,可以在一个命中中找到并操纵该行.
现在,我注意到你已经在节点上有一个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();
而且,作为一次性操作,让我们建立一个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)一个DB命中找到现有的节点,2)第二个DB命中创建一个新的,3)第三个DB命中来更新它上的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的内部ids作为一种方式来管理自己的身份. (但是他们可能会在以后发布某种形式的自动编号,即使是这样,域名身份如电子邮件地址或SKU或机场代码或…是首选,您甚至不需要一个id:你可以经常推断节点根据其在图中的位置.)
通常,Node< T>随着时间的推移会消失.如果你现在使用它,你只是累积了旧代码.
>查看标签和基于模式的索引.他们将使你的生活更轻松.
>在一个查询中尝试和做事情.这会快得多
希望有帮助!