2016-05-16 4 views
1

Я уже несколько дней играю с neo4JClient и имею рабочую площадку с небольшим выбором объектов данных, которые я хочу моделировать. То, что вы видите здесь, является изолированным примером, чтобы попытаться выяснить, что не так.C# neo4jClient добавляет два новых узла с новыми отношениями внутри транзакции

прилагаемый код должен копировать и вставлять и запускать только доступные ссылки;

Код иллюстрирует, что основные методы работают правильно за пределами транзакции, но с первой попытки создать новую взаимосвязь между двумя новыми узлами внутри одной транзакции не сработает.

Я не знаю, попал ли я в ловушку для новичков для a) neo4j вообще, neo4jClient конкретно, или есть настоящая проблема с обработкой транзакций. Я предполагаю, что я ошибаюсь, и что мое мышление отсутствует, но я не могу найти другую связанную с этим проблему, чтобы дать мне ключ.

Проще говоря, это мой случай использования;

Как новый пользователь Я хочу зарегистрироваться в качестве владельца с моим удостоверением и иметь возможность добавить свой список соответствующих активов в моем портфолио.

Я знаю, что это может быть не правильным, а самый эффективный способ сделать это предложение очень понравится.

Следующий код должен иллюстрировать используемые прецеденты, и они не работают;

Исключением я получаю;

System.InvalidOperationException был необработанное HResult = -2146233079
Сообщение = Не может быть сделано внутри области транзакции.
Источник = 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 клиент, владелец владельца, идентичность идентичности) в . . . InnerException:

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(); 
     } 
    } 
} 

UPDATE:

OK, еще немного информации, но не решение, пока.

Как всегда, пожалуйста, взвешивайте, если я лаю неправильное дерево.

Более подробная информация о том, почему транзакции так сложно работать с (пока).

Я исправил свой код в соответствии с Chris Skardons Ответ, который является правильным и решает проблему с созданием, но не решает принципиальной проблемы создания необходимых объектов внутри транзакции и получения ссылки на узел.Однако он фактически создает узлы и отношения. Я думаю, что в neo4jClient есть ошибка.

Запрос на самом деле преуспевает, но обработка на стороне клиента падает, возможно, потому, что я прошу ссылку на узел использовать позже в моем коде.

Эта, надеюсь, окончательная проблема теперь сосредоточена на следующем методе обработки GraphClient для транзакций.

Похоже, что когда вы завершаете транзакцию вокруг запроса Cypher, все это выходит за рамки стандартного метода обработки Cypher API и передает все через API транзакций.

Это приводит к совсем другому отклику, и я верю, что это происходит неправильно.

Я загрузил всю базу кода Neo4jClient и подключил ее непосредственно к моему примеру, вместо пакета NuGet, поэтому, если необходимо, я мог бы пройти через весь код к HttpClient.

Я также подключился к скрипачу, чтобы посмотреть сообщения 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); 

возвращает corrctly сформированный «разобранный» переменную, когда НЕ в сделке (то есть. Он вызывает Cypher URL API), но не заполняет свойства этой переменной, когда внутри транзакции и использует API транзакций ,

Мой вопрос в том, что транзакция возвращает совсем другие данные, если он вообще вызывает этот метод?

После этого все это взрывается в вашем лице. Я не знаю достаточно о намерении кода в этот момент, чтобы сказать больше. У меня меньше недели под моим поясом в качестве пользователя neo4jClient.

Использование neo4jClient с стелькой и локальным хостом

В моих исследованиях я также нашел другие вопросы, связанные с подключением 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); 

Быстрая работа по этой проблеме заключается в том, что имя приложения, которое вы используете в правиле скрипача, соответствует его длине с «localhost: 7474»

Лучшее исправление может быть (и я проверил его для < == и> relative адресные длины) в качестве средства cutti ng out адрес сервера протокола и порт полностью из-за беспокойства, но это зависит от владельца кода, которого я предполагаю;

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); 
    ... 
+0

Вы правы - конечная точка TX возвращает другой ответ - она ​​обрезает все данные, которые вы получаете из конечной точки Cypher. Я думаю, что ваша проблема проистекает из того факта, что вы пытаетесь принудить ответ к типу «Node », когда вы не можете, как это не так. Вы должны поднять свои проблемы на странице GitHub для Neo4jClient - так как иначе они будут потеряны в stackoverflow. –

+0

Спасибо Крису. Сделаю. Я подозревал, что это может быть так, я хотел закончить историю здесь, хотя для любого другого новичка, идущего позади :-) – user2960136

ответ

1

Вы получаете исключение, потому что вы используете старые вызовы API, то TransactionScope реализация для Cypher звонков, в основном что-нибудь в пределах: client.Cypher.

В целом Neo4jClient отошел от Node<T> (полезен в некоторых конкретных случаях) и 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 бит - двойная проверка, чтобы убедиться, что это действительно что вы хотите сделать.

+0

А, у меня есть много, чтобы догнать. Я понял, что IGraphClient.CreateRelationship НЕ использует Cypher под обложками. Я только что проследил код Neo4JClient и узнал, что вы на самом деле на 100% прав. Он выполняет прямой вызов API REST. Тем не менее, я предположил, что, поскольку клиентский API-интерфейс Neo4j rest API говорит, что его транзакционный подход будет охватывать этот сценарий смешанного режима. Возможно, слишком далеко. Спасибо за ваш вклад. Я попробую сейчас. – user2960136

+0

До сих пор я включил это предложение и отлично работает за пределами транзакции, но в рамках транзакции он сработал над этим исключением, которое я сейчас собираюсь исследовать. 'Message = Исключение было выбрано целью вызова. Источник = Neo4jClient StackTrace: на Neo4jClient.GraphClient.Neo4jClient.IRawGraphClient.ExecuteGetCypherResults [TResult] (CypherQuery запроса) в D: \ Temp \ 384a765 \ Neo4jClient \ GraphClient.cs: линия 966 в Neo4jClient.Cypher.CypherFluentQuery '' 1.get_Results() в D: \ temp \ 384a765 \ Neo4jClient \ Cypher \ CypherFluentQuery''TResult.cs: строка 51 ' – user2960136

+0

Я немного смущен о «Как правило, Neo4jClient отошел от узла (полезен в некоторых специфические случаи) и типы отношений. Ваш CreateOwnerNode, я думаю, должен выглядеть следующим образом: «У меня создалось впечатление, что neo4j - это все узлы и отношения, и без ссылок на них жизнь может быстро обременять налоги. – user2960136

 Смежные вопросы

  • Нет связанных вопросов^_^