【发布时间】:2016-05-16 12:54:29
【问题描述】:
我已经使用 neo4JClient 玩了几天,并且有一个工作场所,其中包含我想要建模的一小部分数据实体。您在此处看到的是一个孤立的示例,用于尝试找出问题所在。
所提供的代码应复制和粘贴,并使用正确的可用引用运行;
代码说明了核心方法在事务之外正常工作,但在第一次尝试在同一事务内的两个新节点之间创建新关系时失败。
我不知道我是否陷入了新手陷阱 a) 一般的 neo4j,neo4jClient 具体来说,或者事务处理存在真正的问题。我假设我错了,我的想法很缺乏,但我在任何地方都找不到另一个相关的问题来给我一个线索。
简单来说我的用例是这样的;
作为新用户,我想以当前身份注册为所有者 并能够将我的相关资产列表添加到我的投资组合中。
我知道这可能不是正确的或最有效的方法,非常感谢。
以下代码应说明有效的用例和失败的用例;
我得到的例外是;
System.InvalidOperationException 未处理 HResult=-2146233079
Message=无法在事务范围内完成。
来源=Neo4jClient StackTrace: 在 Neo4jClient.GraphClient.CheckTransactionEnvironmentWithPolicy(IExecutionPolicy 策略)在 D:\temp\384a765\Neo4jClient\GraphClient.cs:797 行 在 Neo4jClient.GraphClient.CreateRelationship[TSourceNode,TRelationship](NodeReference`1 sourceNodeReference,TRelationship 关系)中 D:\temp\384a765\Neo4jClient\GraphClient.cs:350 行 在 ConsoleApplication1.Example.CreateOwnerNode(IGraphClient client, Owner owner, Identity identity) 中 . . .内部异常:
using System;
using System.Linq;
using System.Transactions;
using Neo4jClient;
namespace ConsoleApplication1
{
internal class Program
{
private static void Main(string[] args)
{
var example = new Example();
}
}
public class Example
{
public Example()
{
var rootUri = new Uri("http://localhost:7474/db/data/");
var username = "neo4j";
var neo4jneo4j = "neo4j";
IGraphClient client = new GraphClient(rootUri, username, neo4jneo4j);
client.Connect();
Node<Owner> ownerNode;
Node<Identity> identityNode;
// whole thing outside tranaction
ownerNode = CreateOwnerNode(client, new Owner(), new Identity());
// individually outside transaction
ownerNode = CreateOwner(client, new Owner());
identityNode = CreateIdentity(client, new Identity());
GiveOwnerAnIdentity(client, ownerNode, identityNode);
// individually inside a transaction
using (var scope = new TransactionScope())
{
ownerNode = CreateOwner(client, new Owner());
identityNode = CreateIdentity(client, new Identity());
GiveOwnerAnIdentity(client, ownerNode, identityNode);
scope.Complete();
}
// whole thing inside a transaction
using (var scope = new TransactionScope())
{
ownerNode = CreateOwnerNode(client, new Owner(), new Identity());
scope.Complete();
}
//TODO: Something else with ownerNode
}
public void GiveOwnerAnIdentity(IGraphClient client, Node<Owner> ownerNode, Node<Identity> identityNode)
{
client.CreateRelationship(ownerNode.Reference, new Has(identityNode.Reference));
}
public Node<Identity> CreateIdentity(IGraphClient client, Identity identity)
{
var identityKey = KeyFor<Identity>();
return client.Cypher.Create(identityKey)
.WithParams(new { identity })
.Return(o => o.Node<Identity>())
.Results
.Single();
}
public Node<Owner> CreateOwner(IGraphClient client, Owner owner)
{ var ownerKey = KeyFor<Owner>();
return client.Cypher.Create(ownerKey)
.WithParams(new { owner })
.Return(o => o.Node<Owner>())
.Results.Single();
}
/// <summary>
/// Create a node for an owner along with its nominated identity, relate the owner as having an identity
/// </summary>
/// <param name="client">The <see cref="Neo4jClient" /> instance</param>
/// <param name="owner">The <see cref="Identity" /> instance</param>
/// <param name="identity">The <see cref="Identity" /> instance</param>
/// <returns>The created <see cref="Owner" /> node instance for additional relationships</returns>
public Node<Owner> CreateOwnerNode(IGraphClient client, Owner owner, Identity identity)
{
var ownerKey = KeyFor<Owner>();
var identityKey = KeyFor<Identity>();
var ownerNode =
client.Cypher.Create(ownerKey)
.WithParams(new {owner})
.Return(o => o.Node<Owner>())
.Results.Single();
var identityNode = client.Cypher.Create(identityKey)
.WithParams(new {identity})
.Return(o => o.Node<Identity>())
.Results
.Single();
client.CreateRelationship(ownerNode.Reference, new Has(identityNode.Reference));
return ownerNode;
}
/// <summary>
/// Conform a Cypher create text for a type
/// </summary>
/// <typeparam name="TObject">The type to handle</typeparam>
/// <returns>A string like "{o:TObject {tobject})</returns>
public string KeyFor<TObject>()
{
var name = typeof(TObject).Name;
return $"(o:{name} {{{name.ToLower()}}})";
}
public abstract class Nodebase
{
public Guid Id { get; set; }
public Nodebase()
{
Id = Guid.NewGuid(); // make sure each node is always uniquely identifiable
}
}
/// <summary>
/// Owner node , properties to be added later
/// </summary>
public class Owner
{
}
/// <summary>
/// Identity node , properties to be added later
/// </summary>
public class Identity
{
}
/// <summary>
/// The <see cref="Owner" /> Has an <see cref="Identity" />
/// </summary>
public class Has : Relationship,
IRelationshipAllowingSourceNode<Owner>,
IRelationshipAllowingTargetNode<Identity>
{
internal Has(NodeReference<Identity> targetNode)
: base(targetNode)
{
}
public override string RelationshipTypeKey => GetType().Name.ToUpper();
}
}
}
更新:
好的,还有更多信息,但还不是解决方案。
一如既往,如果我找错树了,请权衡一下。
更多关于为什么交易如此难以处理的线索(到目前为止)。
我根据Chris Skardons 回复更正了我的代码,这是正确的,它解决了创建问题,但没有解决在事务中创建所需对象并取回节点引用的原理问题。然而,它确实创建了节点和关系。我认为 neo4jClient 的某个地方存在错误。
请求实际上成功了,但客户端的处理失败了,可能是因为我要求稍后在我的代码中使用节点引用。
这个有希望的最终问题现在集中在 GraphClient 处理事务中的以下方法。
似乎当您围绕 Cypher 查询包装事务时,它的所有步骤都超出了标准 Cypher API 方法处理的范围,并将所有内容都通过 Transaction API 进行处理。
这会导致非常不同的反应,我认为这就是问题所在。
我下载了整个 Neo4jClient 代码库并将其直接连接到我的示例代码解决方案而不是 NuGet 包中,因此我可以在必要时将所有代码单步执行到 HttpClient。
我还用 fiddler 来观看 REST 消息。更多内容如下。
这两个提琴手会话更详细地显示了正在发生的事情; (请原谅长张贴作为其相关数据以了解正在发生的事情。)
交易之外
POST http : //localhost:7474/db/data/cypher HTTP/1.1
Accept : application / json;
stream = true
X - Stream : true
User - Agent : Neo4jClient / 0.0.0.0
Authorization : Basic bmVvNGo6bmVvNGpuZW80ag ==
Content - Type : application / json;
charset = utf - 8
Host : localhost : 7474
Content - Length : 174
Expect : 100 - continue
{
"query" : "CREATE (o:Owner {owner})\r\nCREATE (i:Identity {identity})\r\nCREATE (o)-[:HAS]->(i)\r\nRETURN o",
"params" : {
"owner" : {},
"identity" : {}
}
}
HTTP / 1.1 200 OK
Date : Wed, 18 May 2016 12 : 03 : 57 GMT
Content - Type : application / json;
charset = UTF - 8;
stream = true
Access - Control - Allow - Origin : *
Content - Length : 1180
Server : Jetty(9.2.9.v20150224)
{
"columns" : ["o"],
"data" : [[{
"extensions" : {},
"metadata" : {
"id" : 53044,
"labels" : ["Owner"]
},
"paged_traverse" : "http://localhost:7474/db/data/node/53044/paged/traverse/{returnType}{?pageSize,leaseTime}",
"outgoing_relationships" : "http://localhost:7474/db/data/node/53044/relationships/out",
"outgoing_typed_relationships" : "http://localhost:7474/db/data/node/53044/relationships/out/{-list|&|types}",
"create_relationship" : "http://localhost:7474/db/data/node/53044/relationships",
"labels" : "http://localhost:7474/db/data/node/53044/labels",
"traverse" : "http://localhost:7474/db/data/node/53044/traverse/{returnType}",
"all_relationships" : "http://localhost:7474/db/data/node/53044/relationships/all",
"all_typed_relationships" : "http://localhost:7474/db/data/node/53044/relationships/all/{-list|&|types}",
"property" : "http://localhost:7474/db/data/node/53044/properties/{key}",
"self" : "http://localhost:7474/db/data/node/53044",
"incoming_relationships" : "http://localhost:7474/db/data/node/53044/relationships/in",
"properties" : "http://localhost:7474/db/data/node/53044/properties",
"incoming_typed_relationships" : "http://localhost:7474/db/data/node/53044/relationships/in/{-list|&|types}",
"data" : {}
}
]]
}
交易内部
POST http : //localhost:7474/db/data/transaction HTTP/1.1
Accept : application / json;
stream = true
X - Stream : true
User - Agent : Neo4jClient / 0.0.0.0
Authorization : Basic bmVvNGo6bmVvNGpuZW80ag ==
Content - Type : application / json;
charset = utf - 8
Host : localhost : 7474
Content - Length : 273
Expect : 100 - continue
{
"statements" : [{
"statement" : "CREATE (o:Owner {owner})\r\nCREATE (i:Identity {identity})\r\nCREATE (o)-[:HAS]->(i)\r\nRETURN o",
"resultDataContents" : [],
"parameters" : {
"owner" : {},
"identity" : {}
}
}
]
}
HTTP / 1.1 201 Created
Date : Wed, 18 May 2016 12 : 04 : 00 GMT
Location : http : //localhost:7474/db/data/transaction/585
Content - Type : application / json
Access - Control - Allow - Origin : *
Content - Length : 241
Server : Jetty(9.2.9.v20150224)
{
"commit" : "http://localhost:7474/db/data/transaction/585/commit",
"results" : [{
"columns" : ["o"],
"data" : [{
"row" : [{}
],
"meta" : [{
"id" : 53046,
"type" : "node",
"deleted" : false
}
]
}
]
}
],
"transaction" : {
"expires" : "Wed, 18 May 2016 12:05:00 +0000"
},
"errors" : []
}
我追踪到这个方法的问题;
public class CypherJsonDeserializer<TResult>
IEnumerable<TResult> ParseInSingleColumnMode(DeserializationContext context, JToken root, string[] columnNames, TypeMapping[] jsonTypeMappings)
接近该方法末尾的那一行;
var parsed = CommonDeserializerMethods.CreateAndMap(context, newType, elementToParse, jsonTypeMappings, 0);
在不在事务中时返回格式正确的“已解析”变量(即,它调用 Cypher API URL),但在事务中并使用事务 API 时不填充该变量的属性。
我的问题是,考虑到事务返回的数据非常不同,它应该调用这个方法吗?
在那之后,一切都会在你的脸上爆炸。在这一点上,我对代码意图的了解还不够多。作为 neo4jClient 用户,我还有不到一周的时间。
将 neo4jClient 与 fiddler 和 localhost 一起使用
在我的调查中,我还发现了与连接 Fiddler 以查看发生了什么有关的其他问题。
我使用 Fiddler 规则技巧将我的本地 URL 命名为“localneoj4”而不是 localhost:7474,并在 client.Connect 方法中使用这个新名称,以便我可以看到本地流量。
var rootUri = "http://localneo4j/db/data";
IGraphClient client = new GraphClient(rootUri, username, password);
client.Connect();
按照here 的建议并将其添加到我的规则中;
if (oSession.HostnameIs("localneo4j")) {
oSession.host = "localhost:7474"; }
这导致一个错误在内部蔓延
public class NeoServerConfiguration
internal static async Task<NeoServerConfiguration> GetConfigurationAsync(Uri rootUri, string username, string password, ExecutionConfiguration executionConfiguration)
这可能影响了许多走这条路的开发人员。
因为 fiddlers 代理效应是在 neo4jClient 的地址概念和服务器的地址概念之间造成脱节。
处理从以下几行的连接响应返回的 URI 时出现字符串长度不匹配问题,因为所有结果。* 属性都以“http://localhost:7474/db/data/”开头,但 rootUriWithoutUserInfo 以 http://localneo4j/db/data/'开头
var baseUriLengthToTrim = rootUriWithoutUserInfo.AbsoluteUri.Length - 1;
result.Batch = result.Batch.Substring(baseUriLengthToTrim);
result.Node = result.Node.Substring(baseUriLengthToTrim);
result.NodeIndex = result.NodeIndex.Substring(baseUriLengthToTrim);
result.Relationship = "/relationship"; //Doesn't come in on the Service Root
result.RelationshipIndex = result.RelationshipIndex.Substring(baseUriLengthToTrim);
result.ExtensionsInfo = result.ExtensionsInfo.Substring(baseUriLengthToTrim);
快速解决此问题的方法是,您在 fiddler 规则中使用的应用名称与其长度匹配 'localhost:7474'
一个更好的解决方法可能是(我已经针对 相对地址长度对其进行了测试)作为一种完全消除协议服务器地址和端口的方法,但我猜这取决于代码所有者;
private static string CombineTrailingSegments(string result, int uriSegementsToSkip)
{
return new Uri(result).Segments.Skip(uriSegementsToSkip).Aggregate(@"/", (current, item) =>{ return current += item;});
}
then
var uriSegementsToSkip = rootUriWithoutUserInfo.Segments.Length; // which counts the db/data and adjusts for server configuration
result.Batch = CombineTrailingSegments(result.Batch, uriSegementsToSkip);
...
【问题讨论】:
-
您是对的 - TX 端点返回不同的响应 - 它会修剪您从 Cypher 端点获得的所有数据。我认为您的问题源于您试图将响应强制转换为
Node<T>类型的事实 - 当您不能这样做时。您应该在 Neo4jClient 的 GitHub 页面上提出您的问题 - 否则它们会在 stackoverflow 中丢失。 -
谢谢克里斯。会做。我怀疑可能是这种情况,我想在这里结束这个故事,不过对于后面的任何其他新手:-)
标签: c# neo4j transactions relationship neo4jclient