How to create and related multiple node to another node - neo4j

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?

Related

What's the return value of DBSet.Add(object o)

Consider the situation.
I have a userlogin table. the userlogin has the following fields.
userid(identity(1,1)), username(unique), password(string)
I have another table, userRole with following fields.
userid(fk referencing userlogin), role(string)
Now suppose I want to add an admin user to my empty application database.
What I am currently doing is:
// Check Userlogin if it contains adminuser1 as username, if not, add adminuser1 with password xyz.
UserLogin login = new UserLogin();
login.username = "adminuser1";
login.password = "xyz";
context.UserLogins.Add(login);
context.SaveChanges();
// query again from database to get the userid
Userlogin user = context.UserLogins.Single(l => (l.username == "adminuser1") && (l.password == "xyz"));
int userid = user.userid;
UserRole admin = new UserRole();
admin.userid = userid;
admin.role = "admin";
context.UserRoles.Add(admin);
context.SaveChanges();
I want to make it a less troublesome, if we can get the userid of userRecently Added, without making another request.
I mean I want to do this if it is possible.
UserLogin login = new UserLogin();
login.username = "adminuser1";
login.password = "xyz";
UserLogin user = context.UserLogins.Add(login);
UserRole admin = new UserRole();
admin.userid = user.userid;
admin.role = "admin";
context.UserRoles.Add(admin);
context.SaveChanges();
Update
I also wanted to know if there is some way to do
context.UserLogins.Single(l => l == login);
instead of
context.UserLogins.Single(l => (l.username == "adminuser1") && (l.password=="xyz"));
because I use the same method in large classes in many fields.
It can be different based on your needs but you can have something like:
public class UserRole
{
public int Id { get; set; }
public string role { get; set; }
}
public class UserLogin
{
public int Id { get; set; }
public string username { get; set; }
public string password { get; set; }
public UserRole Role { get; set; }
}
and then use them like:
var login = new UserLogin
{
username = "adminuser1",
password = "xyz"
};
var admin = context.UserRoles.Single(_=> _.role == "admin");
if (admin == null)
{
admin = new UserRole
{
role = "admin"
};
}
login.Role = admin;
context.UserLogins.Add(login);
context.SaveChanges();
Your models' relationship seems wrong but based on your information you can have this:
var login = context.UserLogins.Single(_ => _.username == "adminuser1");
if (login == null)
{
login = new UserLogin();
login.username = "adminuser1";
login.password = "xyz";
context.UserLogins.Add(login);
context.SaveChanges();
}
else
{
// this user already exists.
}
var admin = context.UserRoles.Single(_ => _.role == "admin");
if (admin == null)
{
admin.userid = login.userid;
admin.role = "admin";
context.UserRoles.Add(admin);
context.SaveChanges();
}
else
{
// the role already exists.
}
context.UserLogins.Single(l => l == login); would not work for you! you have to query DB based on your model key, not whole model data!
For the question
What's the return value of DBSet.Add(object o)
The answer is: it will return the same object o(i.e. without the userid). Simply because userid is an identity column and relies on the database, its value is only available after context.SaveChanges() is called. Since Add() method only registers that a change will take place after SaveChanges() is called.
For the answer to update,
Instead of using
context.UserLogins.Single(l => (l.username == "adminuser1") && (l.password=="xyz"));
For classes that have many fields, I can check if there are any unique columns. For example. I could use, simply
context.UserLogins.Single(l => l.username == "adminuser1");
Just because, username(unique) is specified in the question.
I would rather recommend people use a single Stored Procedure. The calling of context.SaveChanges() and the context.xyz.Single() require opening database connection multiple times. For optimising performance you can use Stored Procedures, as they require only one connection per task. For more information.
Understang Performance Considerations
As I am using database first approach, I found this link also helpful.
Use Stored Procedure in Entity Framework
Thanks :)

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

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.

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