Search code examples
c#neo4jneo4jclient

Neo4jClient ForEach merge


I am trying to write a query in neo4jclient to add/update all parts of a hierarchy tree into the database in 1 hit. I am having trouble when it comes to using merge on collections though. I see the foreach function but haven't been successful getting it to work.

So far I've tried the below I've tried writing it as a full string but keep getting an exception of 'Neo4jClient.NeoException: SyntaxException: Invalid input '.': expected an identifier character, whitespace, '}' or ':' (line 6, column 90 (offset: 266))'

Below the string is how I imagine the code to work if the foreach function worked like c# but don't know the correct syntax.

Any help would be really appreciated.

    public void Save(CypherAspirationsViewModel aspirations, string emailAddress)
    {
        var query = graphClient.Cypher
            .Match("(user:User)")
            .Where((CypherUser user) => user.EmailAddress == emailAddress)

            // Aspirations
            .Merge("user" + CypherRelationships.Aspirations + ">(aspirations:Aspirations {Guid: {aspirationsGuid}})")
            .OnCreate()
            .Set("aspirations = {aspirations}")
            .WithParams(new
            {
                aspirationsGuid = aspirations.CypherAspirations.Guid,
                aspirations = aspirations.CypherAspirations
            });


        // Permanent Remuneration
        if (aspirations.CypherPermanentRemuneration != null) {
            query = query.Merge("aspirations" + CypherRelationships.PermanentRemuneration + ">(permanentRemuneration:Remuneration {Guid: {permanentRemunerationGuid}})")
                .OnCreate()
                .Set("permanentRemuneration = {permanentRemuneration}")
                .WithParams(new
                {
                    permanentRemunerationGuid = aspirations.CypherPermanentRemuneration.Guid,
                    permanentRemuneration = aspirations.CypherPermanentRemuneration
                });
            }

        // Contract Remuneration
        if (aspirations.CypherContractRemuneration != null) {
            query = query.Merge("aspirations" + CypherRelationships.ContractRemuneration + ">(contractRemuneration:Remuneration {Guid: {contractRemunerationGuid}})")
                .OnCreate()
                .Set("contractRemuneration = {contractRemuneration}")
                .WithParams(new
                {
                    contractRemunerationGuid = aspirations.CypherContractRemuneration.Guid,
                    contractRemuneration = aspirations.CypherContractRemuneration
                });
            }

        // Consultant Remuneration
        if(aspirations.CypherConsultantRemuneration != null)
        {
            query = query.Merge("aspirations" + CypherRelationships.ConsultantRemuneration + ">(consultantRemuneration:Remuneration {Guid: {consultantRemunerationGuid}})")
                .OnCreate()
                .Set("consultantRemuneration = {consultantRemuneration}")
                .WithParams(new
                {
                    consultantRemunerationGuid = aspirations.CypherConsultantRemuneration.Guid,
                    consultantRemuneration = aspirations.CypherConsultantRemuneration
                });
        }

        // Locations
        if (aspirations.CypherLocations != null)
        {
            //string forEachString = "(n in {locations} | merge (aspirations " + CypherRelationships.Location + ">(location:Location {Guid: {n.Guid}})) on create set location.Name = n.Name, location.Longitude = n.Longitude, location.Latitude = n.Latitude)";
            //query = query
            //    .ForEach(forEachString)
            //    .WithParam("locations", aspirations.CypherLocations);

            query = query.ForEach("CypherLocation location in aspirations.CypherLocations")
                .Merge("aspirations" + CypherRelationships.Location + ">(location:Location {Guid: {locationGuid}})")
                .OnCreate()
                .Set("location = {location}")
                .WithParams(new
                {
                    locationGuid = location.Guid,
                    location = location
                });
        }

        query.ExecuteWithoutResults();
    }

Cypher Query:

MATCH (user:User)
WHERE (user.EmailAddress = "email")
MERGE user-[:ASPIRATIONS]->(aspirations:Aspirations {Guid: "0d700793-4702-41ee-99f1-685472e65e51"})
ON CREATE
SET aspirations = {
  "FullTime": true,
  "PartTime": false,
  "Permanent": false,
  "Contract": false,
  "Consultant": false,
  "WillingToRelocate": false,
  "CommuteEnum": 40,
  "Guid": "0d700793-4702-41ee-99f1-685472e65e51"
}


FOREACH (n in [
  {
    "Name": "Location1",
    "Longitude": 10.0,
    "Latitude": 1.0,
    "Guid": "a9f25fda-9559-4723-80ec-d8711a260adc"
  }
] | 
merge (aspirations -[:LOCATION]->(location:Location {Guid: {n.Guid}})) 
on create set location.Name = n.Name, location.Longitude = n.Longitude, location.Latitude = n.Latitude)

Solution

  • Ok, I think I've got it nailed. There are a few Cypher changes needed (depending on server version)

    First off, the original error is from the final Merge in the ForEach - you have {Guid: {n.Guid}} but you don't need the extra {} - so it should be: {Guid: n.Guid}.

    Once you've got that, if you're going against a 3.0 DB, you'll need to add some parentheses in your merge methods, for example:

    .Merge("user" + CypherRelationships.Aspirations + ">(aspirations:Aspirations {Guid: {aspirationsGuid}})")
    

    should become:

    .Merge("user" + CypherRelationships.AspirationsWithClosingParentheses + ">(aspirations:Aspirations {Guid: {aspirationsGuid}})")
    

    where AspirationsWithClosingParentheses is something like:

    var AspirationsWithClosingParentheses = "Aspirations)--"
    

    You'll need to do that for every merge, as 3.0 requires the identifiers to be surrounded!