0

I am currently trying to unwind a list of objects that I want to merge to the database using the Neo4J Client. What I would like to do is unwind the list and create the nodes with a label generated based on a property from the items themselves instead of hardcoding a label name. From what I can find I have to use the APOC merge method to do so. However, I am unable to translate this to the Neo4J client. In the neo4J explanation they yield a node after the apoc.merge.node call and then return the node. However, I cannot simply return the node nor can I set the node (I got to the point of just messing about, and at one point I got the labels to work but it overwrote all properties with the last item in the list).

I seem to miss something fundamental but i'm not quite sure what. Does anyone here know how to do this with neo4J client (and if possible, give a bit of an explanation what is going on)? I am very new to the development world and I feel I am just missing a crucial piece of understanding when it comes to this..

The code that I tried that turned all properties into the last node's properties but at least created the labels as I expected:

        public async void CreateBatchItems(List<TToDataBase> itemList)
        {
            await Client.Cypher
            .Unwind(itemList, "row")
            .Merge("(n)")
            .With("row, n")
            .Call("apoc.merge.node([n.Name], n)").Yield("node")
            .Set("n += node")
            .ExecuteWithoutResultsAsync();
        }

Thank you in advance!

Edit:

Some clarification about the input: The objects are actually very basic, as (at least for now), they merely contain a name and an objectID (and these object ID's are later used to create relations). So its a very basic class with two properties:

 public class Neo4JBaseClass
    {
        public Neo4JBaseClass() { }

        public Neo4JBaseClass(string name, string objectId)
        {
            Name = name;
            ObjectId = objectId;
        }

        [JsonProperty(PropertyName = "ObjectId")]
        public string ObjectId { get; set; }

        [JsonProperty(PropertyName = "Name")]
        public string Name { get; set; }
    }

I have also tried a slight variation where this class also has the added property

        [JsonProperty(PropertyName = "PropertyMap")]
        public IProperty PropertyMap { get; set; }  

where PropertyMap is another basic object holding the name and objectId. This seemed like a good idea for future proofing anyway, so the propertylist can be easily expanded without having to change the base object.

1 Answers1

0

[EDITED]

The main issue is that Merge("(n)") matches any arbitrary node that already exists.

You have not shown the data structure for each element of itemList, so this answer will assume it looks like this:

{Name: 'SomeLabel', id: 123, Props: {foo: 'xyz', bar: true}}

With above data structure, this should work:

public async void CreateBatchItems(List<TToDataBase> itemList)
{
    await Client.Cypher
      .Unwind(itemList, "row")
      .Call("apoc.merge.node([row.ObjectId], row.id)").Yield("node")
      .Set("node += row.Props")
      .ExecuteWithoutResultsAsync();
}

[UPDATE]

The data structure you added to your question is very different than what I had imagined. Since neither of the properties in a row is a map, .Set("node += row.Props") would generate an error.

Using your data structure for each row, this might work:

public async void CreateBatchItems(List<TToDataBase> itemList)
{
    await Client.Cypher
      .Unwind(itemList, "row")
      .Merge("(n:Foo {id: row.ObjectId})")
      .Set("n += row.Name")
      .ExecuteWithoutResultsAsync();
}

This code assigns the node label Foo to all the generated nodes. A node should always have a label, which improves clarity and also tends to improve efficiency -- especially if you also create indexes. For example, an index on :Foo(id) would make the above query more efficient.

This code also assumes that the id property is supposed to contain a unique Foo node identifier.

cybersam
  • 63,203
  • 6
  • 53
  • 76
  • Thank you for the reply! Unfortunately, this didn't work for me.. I have added an edit with an explanation of the objects I use in my List. I get the "Can't coerce `String("1")` to Map" message. I am unsure what it is expecting as the answer from Chris Skardon here made me think classes could be understood as map: https://stackoverflow.com/questions/52680320/neo4j-client-unwind-with-datetime I was also wondering about the row.Props.id part in your answer. From your example, the id seems to exist outside of the props, but is called as a part of the Props property as far as I can tell? – Jasper Krom Nov 16 '20 at 11:40
  • I corrected my original answer to use `row.id`, but my original answer turns out to not be helpful given your actual data structure. I have added an `UPDATE` with a more helpful answer. – cybersam Nov 16 '20 at 19:27
  • Thanks for the update. The problem is that I get data from an outside source, so I do not know the labels beforehand. The data is grouped by labels in my source, but I cannot hardcode the labels as they aren't static. Right now i'm using a self-build queue and just single node and edge creation methods, but its a bit of a gimmicky solution and not as efficient as i'd expect. Something else i'm considering is creating very broad predefined labels for first loading and then tagging more specific dynamic labeling on the files afterwards. – Jasper Krom Nov 23 '20 at 09:07