Как создать узел с Neo4jClient в Neo4j v2?

В 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;
}

Причина в том, что идентификатор узла был сгенерирован автоматически, и я мог использовать его позже для быстрого поиска, стартовых битов в других запросах и т. д. Например:

    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, примеры и вредные привычки рекомендуют использовать .ExecuteWithoutResults() во всех CRUD.

Итак, у меня есть вопрос, как у вас есть значение автоматического увеличения для идентификатора узла?


person Cheval    schedule 23.10.2013    source источник


Ответы (1)


Во-первых, для Neo4j 2 и выше вам всегда нужно начинать с системы отсчета «как бы я сделал это в Cypher?». Тогда и только тогда вы беспокоитесь о C#.

Теперь, уточняя ваш вопрос, похоже, что ваша основная цель - создать узел, а затем вернуть ссылку на него для дальнейшей работы.

Вы можете сделать это в шифре с помощью:

CREATE (myNode)
RETURN myNode

В С# это будет:

var categoryNode = graphClient.Cypher
    .Create("(category {cat})")
    .WithParams(new { cat })
    .Return(cat => cat.Node<Category>())
    .Results
    .Single();

Однако это все еще не на 100 % то, что вы делали в исходном методе CreateNodeCategory. Вы создаете узел в БД, получаете для него внутренний идентификатор Neo4j, а затем сохраняете этот идентификатор обратно в тот же узел. По сути, вы используете Neo4j для генерации автоматически увеличивающихся чисел. Это функциональный, но не очень хороший подход. Я объясню больше...

Во-первых, концепция Neo4j, даже возвращающая вам идентификатор узла, уходит. Это внутренний идентификатор, который фактически представляет собой смещение файла на диске. Это может измениться. Это низкий уровень. Если вы на секунду задумаетесь о SQL, используете ли вы SQL-запрос для получения смещения строки в байтах файла, а затем ссылаетесь на него для будущих обновлений? О: Нет; вы пишете запрос, который находит строку и обрабатывает ее одним нажатием.

Теперь я заметил, что у вас уже есть свойство Nguid на узлах. Почему вы не можете использовать это как идентификатор? Или, если имя всегда уникально, используйте его? (Идентификаторы, соответствующие домену, всегда предпочтительнее, чем магические числа.) Если ни один из них не подходит, вы можете посмотреть проект, например SnowMaker. чтобы помочь вам.

Далее нам нужно посмотреть на индексацию. Используемый вами тип индексации упоминается в документации версии 2.0 как "Устаревшее индексирование" и упускает некоторые интересные функции Neo4j 2.0.

Для остальной части этого ответа я предполагаю, что ваш класс Category выглядит следующим образом:

public class Category
{
    public Guid UniqueId { get; set; }
    public string Name { get; set; }
}

Начнем с создания узла категории с меткой:

var category = new Category { UnqiueId = Guid.NewGuid(), Name = "Spanners" };
graphClient.Cypher
    .Create("(category:Category {category})")
    .WithParams(new { category })
    .ExecuteWithoutResults();

И в качестве разовой операции давайте создадим индекс на основе схемы в свойстве Name любых узлов с меткой Category:

graphClient.Cypher
    .Create("INDEX ON :Category(Name)")
    .ExecuteWithoutResults();

Теперь нам не нужно беспокоиться о ручном обновлении индексов.

Мы также можем ввести индекс и уникальное ограничение для UniqueId:

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) третье обращение к БД, чтобы обновить идентификатор на нем. Вместо этого вы можете сжать все это в один вызов, используя MERGE ключевое слово:

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

В принципе,

  1. Не используйте внутренние идентификаторы Neo4j как способ обойти управление собственной личностью. (Но они могут выпустить некоторую форму автонумерации в будущем. Даже если они это сделают, идентификаторы домена, такие как адреса электронной почты или SKU, или коды аэропортов, или ... предпочтительнее. Вам даже не всегда нужен идентификатор: вы часто можете вывести узел в зависимости от его положения на графике.)

  2. Как правило, Node<T> со временем исчезает. Если вы используете его сейчас, вы просто накапливаете устаревший код.

  3. Изучите метки и индексацию на основе схемы. Они сделают вашу жизнь проще.

  4. Попробуйте сделать что-то в одном запросе. Это будет намного быстрее.

Надеюсь, это поможет!

person Tatham Oddie    schedule 23.10.2013
comment
Я предполагаю, что код graphClient.Client должен быть graphClient.Cypher. Когда я пробую запрос, похожий на тот, что запросить легко... Я получаю сообщение об ошибке {SyntaxException: этот синтаксис больше не поддерживается (отсутствующие свойства теперь возвращаются как null). (строка 2, столбец 10)\n\WHERE (o.Name! = {p0})\r\\n ^} - person Cheval; 24.10.2013
comment
Используйте 1.0.0.625 или выше. Кроме того, я исправил опечатки в ответе выше. Вот что происходит, когда вы вводите все из памяти прямо в текстовое поле. :) - person Tatham Oddie; 25.10.2013
comment
Это сработало для вас, @Cheval? Я потратил немного времени на написание этого ответа, поэтому было бы признательно, если бы вы могли отметить его как принятый. - person Tatham Oddie; 28.10.2013
comment
Да, спасибо, это был очень подробный ответ. Я бы указал требуемую версию в заголовке вашего ответа, как и в случае с другим проектом, я забыл обновить ее и получил ошибки. - person Cheval; 29.10.2013
comment
Это был первый удар, когда я пытался найти правильный способ создания узлов с помощью C# cypher quereis, в документации neo4jclient есть только создание индекса и создание отношений, но ничего об узлах. - person flindeberg; 04.03.2014