Neo4j with C# : Create relation between 2 existing nodes, otherwise dont - neo4jclient

I'm making an application which is used for managing chat.
I have 2 C# classes :
public class Person
{
public string Name { get; set; }
public string Password { get; set; }
}
public class PrivateMessage
{
public long Id { get; set; }
public string Content { get; set; }
public long Stamp { get; set; }
public bool Received { get; set; }
}
All I want is to create a relation with PrivateMessage's properties between 2 people.
I created a cypher query like this :
// Create a message from 'Doctor B' to 'Patient B'.
MATCH (Sender:Person), (Recipient:Person)
WHERE Sender.Name = 'Doctor B' AND Recipient.Name = 'Patient B'
CREATE (Sender)-[Message:SENT {Id: 0, Content: 'Hello', Stamp: 1000, Received: false}]->(Recipient)
But I don't know how to archive with using C#.
Also, I want the relation can only be created as 2 nodes exist, otherwise, it can't.
You can watch my image for more detail:
As you can see, I have 2 entities : Doctor B and Patient B, I can create a SENT relationship between them.
But if I use this query:
MATCH (Sender:Person), (Recipient:Person)
WHERE Sender.Name = 'Doctor A' AND Recipient.Name = 'Patient A'
CREATE (Sender)-[Message:SENT {Id: 0, Content: 'Hello', Stamp: 1000, Received: false}]->(Recipient)
Because there is no Doctor A and Patient A, neo4j create 2 grey nodes with a SENT relationship between them.
My question is : How I can prevent this, I don't want to create grey nodes in this image.
Can anyone help me please ?
Thank you.

A relationship would only ever be created if the two nodes exist, otherwise it'd be a hanging line :)
C# wise - you're looking at:
var client = new GraphClient(new Uri("http://localhost.:7474/db/data"));
//The message to be sent.
var message = new Message {Id = 0, Content = "Hello", Stamp = 1000, Recieved = false};
var query = client.Cypher
.Match("(sender:Person)", "(recipient:Person)")
//These 'Wheres' create parameters in the query
.Where((Person sender) => sender.Name == "Person A")
.AndWhere((Person recipient) => recipient.Name == "Person B")
.Create("(sender)-[msg:SENT {message}]->(recipient)")
//The message is added as a parameter here
.WithParam("message", message)
.Return(msg => msg.As<Message>());
var msg = query.Results.Single();
I'm not sure why you're returning the message, if you decide not to, just change the code to:
var query = client.Cypher
.Match("(sender:Person)", "(recipient:Person)")
.Where((Person sender) => sender.Name == "Person A")
.AndWhere((Person recipient) => recipient.Name == "Person B")
.Create("(sender)-[msg:SENT {message}]->(recipient)")
.WithParam("message", message);
query.ExecuteWithoutResults();
Either way, the relationship will only be created if both sender and recipient exist.

Related

LINQ JOIN with WHERE condition

I have a problem with the creation of LINQ query with lambda expression. I need join two tables and make some conditions. I have two tables MSR and BOMDetail.
MSR had theese columns -> MSRID, PN, Buyer,Plant EditDate.
BomDetail had theese columns -> BOMID, PN, AltQty, Plant, EditDate.
And i need to write this query into LINQ.
SELECT MSR.PN, Buyer, MSR.EditDate, MSR.Plant FROM MSR
JOIN BomDetail bd ON MSR.PN = bd.PN AND MSR.Plant = bd.Plant
WHERE LEN(ISNULL(bd.AltQty,''))>0
I need to make 2 conditions PN must equals between tables and Plant's too.
I have for result ViewModel in asp.net MVC.
public class MSRViewModel
{
public string PN { get; set; }
public string Buyer { get; set; }
public string Plant { get; set; }
public DateTime EditDate { get; set; }
}
And here is my sample, it works fine, but i don't know where i must write the second condition for bd.Plant = MSR.Plant.
var data = DbContext.BomDetails.Where(x => !string.IsNullOrEmpty(x.AltQty))
.Join(DbContext.MSRs
, bd => bd.PN,
msr => msr.PN,
(bd, msr) => new MSRViewModel
{
PN = msr.PN,
Buyer = msr.Buyer,
Plant = msr.Plant,
EditDate = msr.EditDate
}).ToList().AsEnumerable();
Thanks.
You can do this as follows:
var data = DbContext.BomDetails.Where(x => !string.IsNullOrEmpty(x.AltQty))
.Join(DbContext.MSRs
, bd => new { bd.PN, bd.Plant },
msr => new { msr.PN, msr.Plant },
(bd, msr) => new MSRViewModel
{
PN = msr.PN,
Buyer = msr.Buyer,
Plant = msr.Plant,
EditDate = msr.EditDate
}).ToList().AsEnumerable();

How to create and related multiple node to another node

Imagine these classes in C#:
public class User
{
public int Id { get; set; }
public string Username { get; set; }
}
public class Post
{
public int Id { get; set; }
public string Text { get; set; }
public string[] HashTags { get; set; }
}
each user can add a post and the relation between them would be the Author, each post could have an array of hash-tags which each of them is going to be a separate node in graph.
when i am going to save each post, I would find the user in grph, create a post node and relate them with a Author Relationship.
the Question is how can I create and relate each hashTag to the post in the same query. (to be inside a transaction).
How could I dynamically add item to query to create it.
the problem is that it could not create node and the relation in one line of create.
Here is what I have tried so far:
var cypherQuery = Db.Instance.Cypher
.Match("(user:User)")
.Where((User user) => user.Username == "XYZ")
.Create("user-[:Author]->(post:Post {newPost})")
.WithParam("newPost", new Post() {Id = 1, Text = "Here is my post about #someHashTag"});
//How to relate this node to the number of hashTags in Post Object???
cypherQuery.ExecuteWithoutResults();
is it good to be in single query or should i divide it in multiple round trips.
I Have tried something with foeach but it seams that the post does not have any value inside the foreach loop:
I have tried something like this:
var cypherCommand = Db.Instance.Cypher
.Match("(user:User)")
.Where((User user) => user.Username == "farvashani")
.Create("user-[:Author]->(post:Post {newPost})")
.WithParam("newPost", "here is my post about #Tag1 and Tag2")
.ForEach(#"(hashtag in {hashTags}|
MERGE post-[:Mentioned]->(hash:HashTag {Text: hashtag}))")
.WithParam("hashTags", new string[] {"Tag1", "Tag2"});
cypherCommand.ExecuteWithoutResults();
In my opinion, i think you have to pre-process the Text property first.
String text = ""Here is my post about #someHashTag""; // For example
List<String> hashTags = new List<String>();
int cnt = 0;
foreach (Match match in Regex.Matches(text, #"(?<!\w)#\w+"))
{
hashTags.Add(match.Value);
}
Then create new instance of Post:
Post newPost = new Post
{
Id = 1,
Text = "Here is my post about #someHashTag",
hashTags = hashTags
};
So, you can use this Cypher:
var cypherCommand = Db.Instance.Cypher
.Match("(user:User)")
.Where((User user) => user.Username == "farvashani")
.Create("user-[:Author]->(post:Post {newPost})")
.WithParam(new {newPost}).ExecuteWithoutResults();
Hope this help.
P/s: Could I ask you a question? Do you think it is better for you to retrieve the separated graph if you use each hashTag as a label of Post? newPost:someHashTag for example?

Neo4jClient: How to deserialize a collect result to a c# class

I have a query where I want only Id and name of the collection back to reduce the network traffic. I am able to get what i want from the database with the following part of the query
ShipToCities = Return.As<IEnumerable<string>>("COLLECT( [shipTo.InternalId, shipTo.Name])")
but the issue is i get back the data like this:
[ [ "IN.KA.MANG", "Mangalore" ], [ "IN.KA.MANG", "Mangalore" ], [ "IN.KA.BANG", "Bangalore" ] ]
but how can I map it to a C# object like
public class CityFound
{
public string CityId { get; set; }
public string CityName { get; set; }
}
is there a way to use some converter to achieve this without me having to use some ugly string manipulation myself?
UPDATE 1:
Actually my query is fairly complex and only way to get the data that I can think of is to handcraft the query like below to reduce the :
//selectedLoadQuery below is a complex query based on user selection...
var query = selectedLoadQuery
.Match("(load)-[:SHIPPED_BY]->(shipper)-[r:HAS_TRANSPORTER]->(transporter)")
.With("load, transporter, shipper, user, count(DISTINCT r) as MyClients")
.Match("p=(shipFrom:City)<-[:SHIP_FROM_CITY]-(load)-[:SHIP_TO_CITY]->(shipTo:City)")
.With("p, load, shipFrom, shipTo, transporter, MyClients")
.Return((load, shipFrom, shipTo) => new
{
TotalShipments = load.CountDistinct(),
FromMyClients = Return.As<long>("MyClients"),
ShipFromCities = Return.As<IEnumerable<string>>("COLLECT( [shipFrom.InternalId, shipFrom.Name])"),
ShipToCities = Return.As<IEnumerable<string>>("COLLECT( [shipTo.InternalId, shipTo.Name])"),
});
Regards
Kiran
You don't need to get so creative. You're only getting into this issue because you're hand crafting such a complex query that flattens out the structure of the data.
Create a class that describes what's in the node:
public class ShippingDestination
{
public long InternalId { get; set; }
public string Name { get; set; }
}
Use this to light up the following syntax in your Return statement:
var cities = graphClient
.Match(...)
.Return(shipTo => new {
Id = shipTo.As<ShippingDestination>().InternalId,
Name = shipTo.As<ShippingDestination>().Name,
})
.Results;

How to create this lambda expression?

To keep things simple, I have this class:
public class Contact
{
public string Name { get; set; }
public string[] Emails { get; set; }
}
I have a collection of contacts = IEnumerable<Contact>
I need to find all contacts in that collection that have, let's say a text "xxx" in their email addresses (they may have multiple emails).
Something like that doesn't work of course:
var found = contacts.Where(c => c.Emails.Where(e => e.Contains("xxx")));
I am wondering how to build such query using lambda expression?
Thanks.
Use Any instead of Where in the inner expression:
var found = contacts.Where(c => c.Emails.Any(e => e.Contains("xxx")));
Try this
var found = contacts.Where(c => c.Emails.Where(e => e.Contains("xxx")).Count() > 0);
This will return all the contacts according to the specified email condition.
Good Luck !!

selecting multiple columns and looping through the selected rows

I want to be able to select multiple columns and loop through the retrieved rows and store the selected fields in a string.
Something like select a.firstname, a.lastname from customer where a.id = '123' and loop through the retireved rows and have them write to a string like
FirstName = John; LastName = Doe
FirstName = Steve; LastName = Smith
I have linq statement as
IList<string> strgradeandbatch = new List<string>();
strgradeandbatch = context.GradeAndBatches
.Where(T => T.RequestGuid == request.ItemGuid)
.Select(T => new{T.GradeName, T.Batch}).ToList();
Obviously this is wrong, and not sure how to do it.Thanks for your help in advance
I think you are almost correct. Just remove IList<string> strgradeandbatch = new List<string>() and use anonymous type var strgradeandbatch.
string GradeName, Batch;
var strgradeandbatch = context.GradeAndBatches
.Where(T => T.RequestGuid == request.ItemGuid)
.Select(T => new{T.GradeName, T.Batch}).ToList();
foreach(var item in strgradeandbatch)
{
GradeName = item.GradeName;
Batch = item.Batch;
}
(Note:If you use anonymous type, you can't return this value from the method)
The Select method projects the query results into a list of an anonymous type objects, so it can be used with a list for strings.
One solution is to create a new class
public class Grade
{
public string GradeName {get; set;}
public string Batch {get; set;}
}
Which is going to be used with the Select method
var strgradeandbatch = context.GradeAndBatches
.Where(T => T.RequestGuid == request.ItemGuid)
.Select(T => new Grade
{
GradeName = T.GradeName,
Batch = T.Batch
}).ToList();

Resources