Update node properties dynamically

I have a C# WebAPI in front of my database and a react app that serves as the front end. When I first started using neo4j, I created the api with a HttpPatch method so I could update individual properties. It works for most of the properties, however I was recently notified that it's having problems with single and double quotes within the text. When I pulled it up, I realized immediately what the problem was. I was just simply using a string builder to build out the query to call (I know...it sucks, but I was trying to learn and get things working quickly and now I'm paying for it).

Now, I'm trying to fix it. I found a blog entry from 2016 that explained that neo4j didn't support dynamic properties and I would have to use apoc. Well, since that was 2016 I decided to keep looking and found this as a recommendation on SO:

MATCH (n:Node)
UNWIND keys(n) as key
WITH n,key where key contains 'property.'
SET n['key'] = n['key'] + 1

Based off of that, I created this:

MATCH (p:Person {userId: $userId})
UNWIND keys(p) as key
WITH p, key where key contains $propertyName
SET p[$propertyName] = $propertyValue

I tested it with propertyName of "headline" and propertyValue of "Let's see if this works".

I get the following error:

Neo4j.Driver.V1.ClientException: Invalid input '[': expected an identifier character, whitespace, '{', node labels, a property map, a relationship pattern, '.', '(', '=' or "+=" (line 1, column 108 (offset: 107))
"MATCH (p:Person {userId: $userId}) UNWIND keys(p) as key WITH p, key where key contains $propertyName SET p[$propertyName] = $propertyValue"

What I'm trying to achieve is to be able to pass in a userId, property name and a value and dynamically update the proper node. Is this something that can be done without apoc??? I'm not against using apoc, just haven't had the chance to install it on the server and I've always been a fan of just using the base tools :).

TIA for any help/guidance.
Leeland

While I'd love for this approach to work, it's not implemented at this time. You'll need APOC for setting properties dynamically.

One exception is when you have a map of parameters to update (not increment, update to the value exactly). If you set a map parameter where the key(s) are the properties to update, after filtering to the nodes to update you can use SET p += $updatedProperties and they will overwrite just the properties contained in the map.

The trick then is to ensure that you can set the right keys into the map for the properties you want to update. You can't do that with Cypher (you can with APOC), so you'd have to construct that map in your code and pass it as a parameter to your query.

Thanks for responding. If I'm understanding correctly, then I think the map solution is exactly what I'm needing. I pass in a parameter of the property that I need to update as well as the exact value I want to overwrite it with. The first bit of code is just what I found as a starting point...I don't need to increment.

I'm not familiar with working with maps though. Would it be something along the lines of:

var updatedProperties = { propertyName: 'New Value' };

Then make the query like so:

MATCH (p:Person {userId: $userId}) SET p += $updatedProperties

Yep, that should do the trick

1 Like

That works perfectly. In case anyone else comes here looking for the final solution that works with the .Net driver in Core 2.2...this is what I used:

var updatedProperties = new Dictionary<string, object> { { patchDto.PropertyName, patchDto.PropertyValue } };
var statementText = "MATCH (p:Person {userId: $userId}) SET p += $updatedProperties";
var statementParameters = new Dictionary<string, object>
{
    { "userId", userId },
    { "updatedProperties", updatedProperties }
};

using (var session = _driver.Session())
{
    var results = session.Run(statementText, statementParameters).Consume();
    success = results.Counters.ContainsUpdates;
}

The patchDto object is defined as:

public class PatchDto
{
    [JsonProperty("propertyName")]
    public string PropertyName { get; set; }
    [JsonProperty("propertyValue")]
    public object PropertyValue { get; set; }
}

Oh, one other thing I had to add was data type conversion. By default it was creating everything as strings. I added a test to see what the data type needed to be and convert it prior to assigning it to the statementParameters. This isn't a Neo4j issue, it's from the conversion that is made to transfer the data from the client to the server.