Add multiple relationships between 2 nodes with Neo4jClient - neo4j

I am building a social graph application using Neo4jClient to store data and I am trying to come up with the best strategy to model where a user has worked and currently works. So my User is connected with the relationship "USER_WORK" to several Work nodes which has StartDate/EndDate properties. If EndDate is not set I want to add another relationship "CURRENT" between the user/work node to be able to fetch only current work places in an efficient way.
For some reason Neo4jClient does not let me do this? Below query executes without exceptions and the work node and all relationships except "CURRENT" is added (and yes I have checked that there is no problem with the EndDate logic:) I have also tried using Create instead of CreateUnique but that doesn't solve the problem :(
var query = graphClient.Cypher
.Match("(city:City)", "(profession:Profession)", "(company:Company)", "(user:User)")
.Where((CityEntity city) => city.Id == model.CityId)
.AndWhere((ProfessionEntity profession) => profession.Id == model.ProfessionId)
.AndWhere((CompanyEntity company) => company.Id == model.CompanyId)
.AndWhere((UserEntity user) => user.Id == model.UserId)
.Merge("(work:Work { Id: {Id} })")
.OnCreate()
.Set("work = {entity}")
.WithParams(new
{
Id = entity.Id,
entity
})
.CreateUnique("work-[:WORK_AS_PROFESSION]->profession")
.CreateUnique("work-[:WORK_AT_COMPANY]->company")
.CreateUnique("work-[:WORK_IN_CITY]->city")
.CreateUnique("user-[:USER_WORK]->work");
if (model.EndDate == DateTime.MinValue)
{
query.CreateUnique("user-[:CURRENT]->work");
}
query.ExecuteWithoutResults();

When you call CreateUnique to create the user-[:CURRENT]->work relationship, it's not actually being appended to the query. What you need to change that line to is:
query = query.CreateUnique("user-[:CURRENT]->work");
Which is what is happening for all the fluent methods chained in the first query you write out. The easiest way to spot these things is stick a breakpoint on the query.ExecuteWithoutResults(); method and when VS breaks there, hover over query and see if the text matches what you think it should.

Related

Efficient way to get a huge number of records sorted via Linq. Also some help regarding editing an existing DB entry

The fist part of my question is
suppose I have a poco class
public class shop{
public virtual string fruitName {get;set;}
public virtual double numberOfFruitsLeftToConsume {get;set;}
public virtual double numberOfFruitsLeftForStorage {get;set;}
public virtual List<Locations> shopLocations {get;set;}
}
I add new fruits in the db by creating a new object of shop and then add it via my context then save it.
Now to retrieve the data
will it be more efficient for me to first filter by fruit name get a List then in that collection should I run my query to sort by the number of fruits to consume , or should I just put it all into one query. Supposing that the site has more than 1000 hits/sec and a massive DB, which method will be efficient.
List<shop> sh = context.shopDB.Where(p => p.fruitName == "mango" &&
p.fruitName == "apple").ToList();
List<shop> sh = sh.Where(f => f.numberOfFruitsLeftToConsume >= 100 &&
f.numberOfFruitsLeftForStorage <= 100).ToList();
The example has no meaning , I just wanted to show the type of query I am using.
The second part of my question is, when I initialize the class shop I do not initialize the List within it. Later on when I try to add it it does not get saved, the shop is connected to the user.
ApplicationUser user = await usemanager.FindByEmailAsync("email");
if(user.shops.shopLocations == null){
user.shops.shopLocation = new List<Location>();
uset.shops.shopLocation.Add(someLocation);
await context.shopDB.SaveChangesAsync();
}
////already tried
//List<Location> loc = new List<Location>();
//loc.Add(someLocation);
//user.shops.shopLocation = loc;
//await context.shopDB.SaveChangesAsync();
I tried both the methods in a try catch block and no exception is thrown.
If you need any more details or if something is not clear to you please ask.
Thank you.
If I add Location and LocationId properties to shop, and then save, I can only view the LocationId , but the Location property still remains null.
To clear any question , If I save a location Individually it saves. So I don't think I'm providing wrong data.
Now to retrieve the data will it be more efficient for me to first filter by fruit name get a List then in that collection should I run my query to sort by the number of fruits to consume , or should I just put it all into one query. Supposing that the site has more than 1000 hits/sec and a massive DB, which method will be efficient.
You are the only one who can answer that question by measuring the query performance. Only as a general rule I can say that putting all into one query and let the database do the most of the job (eventually tuning it by creating appropriate indexes) is usually preferable.
What about the second part of the question (which basically is a different question), this block
if (user.shops.shopLocations == null)
{
user.shops.shopLocation = new List<Location>();
user.shops.shopLocation.Add(someLocation);
await context.shopDB.SaveChangesAsync();
}
looks suspicious. Your shopLocations member is declared as virtual, which means it's intended to use lazy loading, hence most probably will never be null. And even if it is null, you need to keep only the new part inside the if and do the rest outside, like this
if (user.shops.shopLocations == null)
user.shops.shopLocation = new List<Location>();
user.shops.shopLocation.Add(someLocation);
await context.shopDB.SaveChangesAsync();
1st Question
Because you are calling .ToList() at the end of your queries it will have to fetch all the rows from the db each time, so it will be much faster to do all your filtering in one LINQ .Where() call like this:
List<shop> sh = context.shopDB.Where(p => p.fruitName == "mango" && p.fruitName == "apple" && f.numberOfFruitsLeftToConsume >= 100 && f.numberOfFruitsLeftForStorage <= 100).ToList();
but if you don't call .ToList() at the end of first Linq query, spliting your query into two calls will be tottally fine and will yield the same performance as the previous approach like this:
var sh = context.shopDB.Where(p => p.fruitName == "mango" &&
p.fruitName == "apple");
List<shop> shList = sh.Where(f => f.numberOfFruitsLeftToConsume >= 100 &&
f.numberOfFruitsLeftForStorage <= 100).ToList();
2nd Question
when you initialize the Location for the shop, you must set the shopId property and then it should work, if not the problem might be with your database relationships.

Query - Does Not Contain

I have a search query to lookup Customers.
I would like to use the Sounds Like function to return additional possible results, however this is returning some of the same results in my main search query.
I would like to only show the additional results in a partial view.
I basically need a DoesNotContain.
Here is what I have so far for my main query:
customer = customer.Where(c => SqlFunctions.StringConvert((double)c.CustomerID).Trim().Equals(searchString)
|| c.CustomerName.ToUpper().Contains(searchString.ToUpper()));
And for the additional results:
customeradditional = customeradditional.Where(c => SqlFunctions.SoundCode(c.CustomerName.ToUpper()) == SqlFunctions.SoundCode(searchString.ToUpper()));
The only possible solution I can see at the minute is to do a Contains Query, loop through each item and get the IDs, then do another query for CustomerID != 1 or CustomerID != 2 or CustomerID != 3, etc.
Try using Except:
customeradditional = customeradditional
.Where(c => SqlFunctions.SoundCode(c.CustomerName.ToUpper()) == SqlFunctions.SoundCode(searchString.ToUpper()))
.Except(customer);
I am not sure if I understood you correct:
From what you have now, the customeraddtional query does return some of the customers already returned in the customer query. And you only want the results, which are not already contained in the customer query.
Then the solution would be:
customeradditional = customeradditional.Where(c =>
SqlFunctions.SoundCode(c.CustomerName.ToUpper()) ==
SqlFunctions.SoundCode(searchString.ToUpper()))
.Except(customer);
This way your are explicitly excluding every item, which is present in the customer object.

LINQ to SQL - Filtering the dataset between two nested collections

I have an MVC 3 project in Visual Studio c#. I have a LINQ to SQL query which works fine and by following an example listed elsewhere on stackoverflow:
Comparing two lists using linq to sql
I have been able to successfully reduce my results where my two nested collections match. This is the bit of code that did the trick (example from the link above):
var anyDesiredSkills = canidateSkills.Any( c => desiredSkills.Select( ds => ds.SkillId ).Contains( c.SkillId ) );
I've adapted this successfully, but now I need to be able to filter records using more than one condition. I was wondering if anyone would be able to adapt the above to show how you could include more than one condition?
To give you some background on what my goal is:
A search page where you can select any number of contacts
Each contact added to the search criteria may/may not have a 'role' assigned. If a role is present this should be factored in to the query.
Results returned based on this dynamic criteria.
Thanks in advance for any and all help :O)
It sounds like you're looking for something like:
var desiredSkillIds = desiredSkills.Select(_=>_.SkillId).ToList();
var matchingContacts =
from contact in Contacts
where contact.Role == null || desiredRoles.Contains(contact.Role)
where contact.Skills.Any(cs=> desiredSkillIds.Contains(cs.SkillId))
select contact;
Or in method-based syntax:
var matchingContacts = Contacts
.Where(contact => contact.Role == null || desiredRoles.Contains(contactRole))
.Where(contact => contact.Skills.Any(cs => desiredSkillIds.Contains(cs.SkillId)));
Here's the final code I used:
servicelist = servicelist.Where(
d => d.ContactSelection.Any(
h => model.ContactFilter.Select(ds => ds.StaffNumber).Contains(h.StaffNumber)
&&
model.ContactFilter.Select(ds => ds.ContactRole).Contains(h.ContactRole) || model.ContactFilter.Select(ds => ds.StaffNumber).Contains(h.StaffNumber) && model.ContactFilter.Select(ds => ds.ContactRole).Contains("0"))
);
Note that the last filter .Contains("0) is the value of '-- select role --' which is an option injected in to the drop down. Hope this helps anyone else!

How do I delete a relationship using Neo4jClient

Am trying to get the basics down before moving forward with neo4j. Love the querying aspect but now trying to delete using neo4jclient and stuck.
Simple setup
root-[:has_user]->user
and
user-[:friends_with]->friend`
For a user with the Id of 1 I would like to remove the specified from Id == 2. User 1 is no longer friends with User 2 :(
Anyway, using neo4jclient I first check to make sure that the users are friends in the first place with this:
if (client.Cypher.Start("root", client.RootNode)
.Match("root-[:HAS_USER]->user-[:FRIEND]->friend")
.Where((UserNode user, UserNode friend) => user.Id == 1 && friend.Id == id)
.Return<Node<UserNode>>("user")
.Results
.Count() == 1)
{
now I'm trying to delete:
client.Cypher.Start("root", client.RootNode)
.Match("root-[:HAS_USER]->user-[r]->friend")
.Where("user.Id = 1")
.And()
.Where("friend.Id = " + id)
.And()
.Where(string.Format("type(r) = 'FRIEND'"))
.Delete("r");
}
No errors, but the relationship is still there. Any ideas?
Update 11/12/2012
Got it working. I first updated by Neo4J instance with the stable 1.8. I think something with the latest neo4jclient and neo4j server were not working together. I first got the user's node based on the id, then from that node tested if the node had a relationship, then was able to remove it. Code below:
var currentUserNode = client.Cypher.Start("root", client.RootNode)
.Match("root-[:HAS_USER]->user")
.Where((UserNode user) => user.Id == 1)
.Return<Node<UserNode>>("user")
.Results.Single();
if (currentUserNode.StartCypher("user")
.Match("user-[r]->friend")
.Where("friend.Id = " + id).And()
.Where("type(r) = 'FRIEND'")
.Return<Node<UserNode>>("user")
.Results
.Count() == 1)
{
currentUserNode.StartCypher("user")
.Match("user-[r]->friend")
.Where("friend.Id = " + id).And()
.Where("type(r) = 'FRIEND'")
.Delete("r").ExecuteWithoutResults();
}
One way is to switch to use a CypherFluentQuery instead:
new CypherFluentQuery(client)
.Start("root", client.RootNode)
.Match("root-[:HAS_USER]->user-[r]->friend")
.Where("user.Val = 1").And()
.Where("friend.Val = " + 2).And()
.Where("type(r) = 'FRIEND'")
.Delete("r").ExecuteWithoutResults();
which will do what you want.
I believe this all stems from a bug: https://bitbucket.org/Readify/neo4jclient/issue/40/should-be-able-to-add-cypher-delete-clause
As to why client.Cypher.Start isn't working as it should, I'm not sure, the bug is fixed, and should be working from version 1.0.0.479 (current at time of writing is 1.0.0.496)
Use any Neo4jClient build after 1.0.0.500. See issue 45 if you're interested in why.
Don't forget to call ExecuteWithoutResults. The first code sample in your question is missing this. This is documented on https://bitbucket.org/Readify/neo4jclient/wiki/cypher

Entity Framework 4 Linq help - Pulling data from multiple tables filtered

Not sure how this is done, I have my .edmx set up so that the navigation properties match the foreign key relationships on the tables. Not sure if I still need to perform joins or if EF will give me access to the related table data through the navigational properties automatically.
What I need to do it get all the ContentSections and their associated ContentItems based on the ContentView and filtered by the DiversionProgram.CrimeNumber.
I would like to get back IEnumerable, for each ContentSection it should have access to it's ContentItems via the navigation property ContentItems
Thanks
Something like:
using(Entities context = new Entities())
{
IEnumerable<ContentSection> enumerator = context.ContentSections
.Include("ContentItems")
.Where<ContentSection>(cs => cs.ContentView.ContentViewID == someID && cs.ContentItems.Where<ContentItem>(ci => ci.DiversionProgram.CrimeNumber == someCrimeNumber))
.AsEnumerable<ContentSection>
}
I've interpreted
based on the ContentView
as cs.ContentView.ContentViewID == someID
This will give you all the ContentSections for a given ContentView. And interpreted
filtered by the DiversionProgram.CrimeNumber
as cs.ContentItems.Where<ContentItem>(ci => ci.DiversionProgram.CrimeNumber == someCrimeNumber)
which will give you all those ContentItems that have a specific CrimeNumber.
Or did you mean something else with based on / filtered by. Maybe OrderBy, or all those ContentSections where Any of it's ContentItems would have a certain CrimeNumber?
You can eager load to get all associated records, but when you want to start filtering/ordering, don't bother with Include.
Just do a projection with anonymous types and EF will work out what it needs to do. It's a bit hairy, but it'll work. If it get's too complicated, bite the bullet and use a SPROC.
Now, with that caveat, something like this (off the top of my head):
var query = ctx.ContentView
.Select(x => new
{
ContentSections = x.ContentSections
.Where(y => y.ContentItems
.Any(z => z.DivisionProgram.CrimeNumber = 87))
}).ToList().Select(x => x.ContentSections);
If you use the CTP5 you can do something very unique it looks like this:
var context = new YourEntitiesContext();
var query = context.ContentView.Include(cs => cs.ContentSections
.Select(ci => ci.ContentItems
.Select(dp => dp.DiversionProgram)
.Where(dp.CrimeNumber == crimeNumber)))
.Where(cv => cv.ContentViewID == contentViewID).FirtsOrDefault();
You can learn more about the CTP5 and how it can be used in Database first scenario here
var query = from t1 in studentManagementEntities.StudentRegistrations
join t2 in studentManagementEntities.StudentMarks
on t1.StudentID equals t2.StudentID
select new
{
t1.selected column name,
t2.selected column name
};

Resources