Head's Up! These forums are read-only. All users and content have migrated. Please join us at community.neo4j.com.
03-17-2021 06:49 AM
Hi
I am new to both Neo4j and the Neo4j .NET driver. I am serializing and deserializing specific “patterns” from .NET to Neo4j and back (which is why I am trying to keep it simple without any other libraries). An example is this:
public class Car
{
public string Name { get; set; }
public IList<Engine> Engines { get; set; }
}
public class Engine
{
public string Name { get; set; }
public Maker Maker { get; set; }
public Material Material { get; set; }
}
public class Maker
{
public string Name { get; set; }
}
public class Material
{
public string Name { get; set; }
}
My data in Neo4j looks like this (create statement attached in the end):
I am trying to re-create the Car based upon the above data. However, my query:
MATCH(from:Car { Name: "Hybrid" })-[r:USES|CREATOR|MADE_OF*1..2]->(to) RETURN from, r, to
Returns an IList with 5 records that seems overly difficult to use to re-create the above Car object, which suggest I am on the wrong path. My current attempt is not working yet and growing in complexity and works by iterating through the List and looking at the type of relationship, the to node and ascertaining where it is in the pattern.
I can re-create the Car as this is the from Node, however the Engine, Maker and Material is causing me problems. It seems like I need a combination of Node and Relationship to re-create the correct object, however I don't see how.
Is there a better path? Either by using Cypher to return an object that is easier to deserialize (for example containing the hierarchy of the objects) or some way to return subgraphs so the results are easier to work with?
Thank you in advance!
Best regards
Andreas:
Create.txt (478 Bytes)
03-17-2021 09:47 AM
Hello!
What does your C# look like at the moment?
All the best
Chris
03-17-2021 10:38 AM
Hi Chris!
Thank you for your reply
I was afraid you were going to ask, as it is not pretty but here goes (and as I wrote it is not working yet):
I have split up my query in two parts to better control / understand it:
//First let's get the Car and convert it to a dictionary so we can add properties for later deserialization.
var carQuery = "MATCH (car:Car { Name:'Hybrid' }) RETURN car";
var carResult = await session.RunAsync(carQuery);
var carRecord = await carResult.ToListAsync();
var carView = carRecord?.First()["car"].As<INode>().Properties.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
//Lets get all the connected nodes needed to build up the rest of the Car
var relatedObjectsQuery = "MATCH(car:Car { Name: 'Hybrid' })-[r:USES|CREATOR|MADE_OF*1..2]->(to) RETURN r, to";
var relatedObjectsResult = await session.RunAsync(relatedObjectsQuery);
var relatedObjectsRecords = await relatedObjectsResult.ToListAsync();
//Here is where it gets fuzzy and the below is NOT working and feels like a wrong way to go about it.
foreach (var relatedObjectsRecord in relatedObjectsRecords)
{
var relationships = relatedObjectsRecord["r"].As<List<IRelationship>>();
if (relationships.Count == 1)
{
//If count is 1 and relationship is USES then it must be an Engine
if (relationships.Single().Type == "USES")
{
carView.Add("engine", relatedObjectsRecord["to"].As<INode>().Properties);
}
}
if (relationships.Count == 2)
{
//more wrong stuff...
}
}
I try to build up my object from traversing the results of the query, where the correct way (or at least the one in control) seems to be able to specifically pinpoint "patterns", for example, this is an Engine with Maker and Material and connect that to the car as in the below pseudocode:
var engines = Somehow parse the result to get a list of Engines with Maker and Material.
var car = new Car
{
Name = Gotton from the Neo4j result,
Engines = engines,
}
Am I not sure if the best way is to solve this using fewer Cypher statements and parse the results in C# or try to write multiple Cypher queries and then put the results together?
Best regards
Andreas
03-17-2021 02:09 PM
I got something working, by splitting up the query. As written above I use this query to get the root node Car:
MATCH (car:Car { Name:'Hybrid' })
RETURN car
Although this makes my application "chattier" I can get the individual Engines and their data by using the below query:
MATCH (from:Car {Name: 'Hybrid'})-[r1:USES]->(engine)-[r2:CREATOR|MADE_OF]->(data)
RETURN engine, collect(data)
I can convert the result of the above query to Engine objects and then add them to the List of Engines in the car gotten in the first query. Not pretty or effective, but it works until I become more experienced 🙂
Best regards
Andreas
03-18-2021 04:38 AM
Are you expecting to get a single 'car' instance from it?
03-18-2021 11:03 AM
Yes the "Car" instance is unique. In the real application I am using a unique Guid for each instance of Car.
03-22-2021 09:21 AM
Hey Andreas,
Sorry for the time delay - I got waylaid - ok, so you can do it in one query, and to make it a bit easier to read, I've used Neo4j.Driver.Extensions - which you can find in Nuget - and allows you to do a bit of ToObject
type stuff.
Anyhews, the only difference to your classes was the addition of the [Neo4jProperty]
attribute on the Name
properties - as you use lowercase property names for some, and Uppercase for others. (As an aside - from a .NET perspective - it's easier to use Uppercase, as it matches the coding style).
All the best
Chris
async Task Main()
{
var driver = GraphDatabase.Driver("neo4j://localhost:7687", AuthTokens.Basic("neo4j", "neo"));
var session = driver.AsyncSession();
var executionResult = await session.RunAsync(@"MATCH (car:Car { Name: 'Hybrid' })-[:USES]->(engine:Engine)-[:CREATOR]->(maker:Maker)
OPTIONAL MATCH(engine) -[:MADE_OF]->(material: Material)
WITH car, { Engine: engine, Maker: maker, Material: material}
AS bits
WITH car, collect(bits) AS bits
RETURN car, bits");
var results = await executionResult.ToListAsync();
var cars = new List<Car>();
foreach (var result in results)
{
var car = result["car"].As<INode>().ToObject<Car>();
car.Engines = new List<Engine>();
foreach (IDictionary<string, object> bit in result["bits"].As<List<IDictionary<string, object>>>())
{
var engine = bit["Engine"].As<INode>().ToObject<Engine>();
engine.Maker = bit["Maker"].As<INode>().ToObject<Maker>();
bit["Material"].TryAs<INode>(out var materialNode);
engine.Material = materialNode?.ToObject<Material>();
car.Engines.Add(engine);
}
cars.Add(car);
}
}
public class Car
{
public string Name { get; set; }
public IList<Engine> Engines { get; set; } = new List<Engine>();
}
public class Engine
{
[Neo4jProperty(Name="name")]
public string Name { get; set; }
public Maker Maker { get; set; }
public Material Material { get; set; }
}
public class Maker
{
[Neo4jProperty(Name="name")]
public string Name { get; set; }
}
public class Material
{
[Neo4jProperty(Name="name")]
public string Name { get; set; }
}
03-23-2021 06:15 AM
Interesting, Neo4j.Driver.Extensions is oddly similar to my NODES 2019 submission SchematicNeo4j that's been available as a nuget package since mid 2019.
https://www.nuget.org/packages/SchematicNeo4j
@charlotte.skardon .NET support and mapping from graph to C# is one of the things I really enjoy (enough to spend my own free time on it). You all hiring for your .NET team?
10-17-2022 07:50 PM
Hi Charlotte,
Looks like the Neo4j.Driver.Extensions nuget package needs an old version (4.3.2.2) of Neo4j.Driver. Does that sound right to you? I am wondering if there are plans to get the Neo4j.Driver.Extensions Package up to date with the current nuget Neo4j.Driver package... (5.1.0)
Thanks!!
03-28-2021 01:52 PM
Hey Chris
Sorry for my late reply.
Thank you very much for your help! By following your example, I managed to reduce the complexity of my code and handle it all in a single query
Best regards
Andreas
All the sessions of the conference are now available online