【问题标题】:c# neo4jClient adding two new nodes with a new relationship inside a transactionc# neo4jClient 在事务中添加两个具有新关系的新节点
【发布时间】: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&lt;T&gt; 类型的事实 - 当您不能这样做时。您应该在 Neo4jClient 的 GitHub 页面上提出您的问题 - 否则它们会在 stackoverflow 中丢失。
  • 谢谢克里斯。会做。我怀疑可能是这种情况,我想在这里结束这个故事,不过对于后面的任何其他新手:-)

标签: c# neo4j transactions relationship neo4jclient


【解决方案1】:

您会收到异常,因为您使用的是旧 API 调用,TransactionScope 实现适用于 Cypher 调用,基本上是:client.Cypher 中的任何内容。

通常,Neo4jClient 已从 Node&lt;T&gt;(在某些特定情况下有用)和 Relationship 类型中移出。你的CreateOwnerNode我觉得应该更像这样:

public Node<Owner> CreateOwnerNode(IGraphClient client, Owner owner, Identity identity)
{
    var query = client.Cypher
        .Create($"(o:{GetLabel<Owner>()} {{owner}})")
        .Create($"(i:{GetLabel<Identity>()} {{identity}})")
        .WithParams(new {owner, identity})
        .Create("(o)-[:HAS]->(i)")
        .Return(o => o.As<Node<Owner>>());

    return query.Results.Single();
}

private string GetLabel<TObject>()
{
    return typeof(TObject).Name;
}

您希望尽可能多地在单个查询中完成,如果您正在尝试 - 您将调用 3 次数据库,这将一次性完成。

我认为首先使用简单的 Cypher 来做这些事情是值得的,不应该有任何理由需要使用 CreateRelationship 的东西 - 事实上,我会说任何时候你留下.Cypher 位 - 仔细检查以确保它真的是你想要做的。

【讨论】:

  • 啊,我有很多事情要赶上。我没有意识到 IGraphClient.CreateRelationship 并没有在幕后使用 Cypher。我刚刚浏览了 Neo4JClient 代码,发现您实际上是 100% 正确的。它执行直接的 REST API 调用。尽管如此,我还是假设 Neo4j REST API 调用文档说它的核心事务将涵盖混合模式场景。也许一个假设太远了。感谢您的输入。我现在就试试。
  • 到目前为止,我插入了该建议,并且它在事务之外工作正常,但在事务中它绊倒了这个异常,我现在将对其进行调查。 `Message=Exception 已被调用的目标抛出。 Source=Neo4jClient StackTrace: 在 Neo4jClient.GraphClient.Neo4jClient.IRawGraphClient.ExecuteGetCypherResults[TResult](CypherQuery 查询) 在 D:\temp\384a765\Neo4jClient\GraphClient.cs:line 966 at Neo4jClient.Cypher.CypherFluentQuery''1.get_Results( ) 在 D:\temp\384a765\Neo4jClient\Cypher\CypherFluentQuery''TResult.cs:line 51'
  • 我对“一般来说,Neo4jClient 已经远离 Node(在某些特定情况下有用)和关系类型有点困惑。我认为你的 CreateOwnerNode 应该看起来更像这样:”我的印象是 neo4j 完全是关于节点和关系的,如果不引用它们,生活会很快变得繁重。
  • Neo4j is 都是关于节点和关系的 - 但是 Node&lt;T&gt; 类让您可以访问不应使用的 Id 之类的东西 - 通常你不会不需要知道某个东西是一个节点,如果在 Cypher 中你要求一个用户和公司(例如)你应该构造查询以获得你想要的东西。您不想将数据库拉到您的代码中,然后自己处理它
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多