Working with index in Neo4j - c#

I've been going through the Neo4J and Neo4J C# client..
The neo4jclient wiki helped me to with node crud operations.. however the wiki ends there abruptly..
I poked around the test methods in source code and managed to understand about relationships and searched online to understand how indexing works.
So far, here's what I have, roughly:
//create indexing on user and car
client.CreateIndex("User", new IndexConfiguration() { Provider = IndexProvider.lucene, Type = IndexType.fulltext }, IndexFor.Node);
client.CreateIndex("Car", new IndexConfiguration() { Provider = IndexProvider.lucene, Type = IndexType.fulltext }, IndexFor.Node);
//create user
client.Create(new User() { Name = "Dovakiin", Job = "Dragon Slayer" });
client.Create(new User() { Name = "Ulfric stormcloak", Job = "Imperial Slayer" });
//create Car
client.Create(new Car() { Name = "Paarthurnax", Modal = 212 });
//User owns car relationship
client.CreateRelationship(userRef, new Owns_CarRelationship(CarRef));
This is where I am stuck now.. When I try to look for the user by name, my cipher query is returning zero results:
start u=node:User(Name="Dovakiin") return u;
and I don't quite understand why it returns zero nodes when clearly
start n=node(*) return n;
shows all nodes.
Am I missing something else while indexing? Or is this not index related at all? Do I not need to add each node to the index?
All I am trying to do, is select the node with a given property: Name = "Dovakiin" in this case.. How do I select this please?

Just to expand on ulkas' answer, if you want to enable auto indexing and found the documentation a little confusing (like I did the first time I read it), this is how you set it up.
Let's say you want to automatically index some node properties; say, "name" and "job". Open up the /conf/neo4j.properties file and you should see something like this:
# Autoindexing
# Enable auto-indexing for nodes, default is false
#node_auto_indexing=true
# The node property keys to be auto-indexed, if enabled
#node_keys_indexable=name,age
You then have to edit the file to the following:
# Autoindexing
# Enable auto-indexing for nodes, default is false
node_auto_indexing=true
# The node property keys to be auto-indexed, if enabled
node_keys_indexable=name,job
Once this is done, in order for auto indexing to take effect, you'll have to restart neo4j. Also, as a side note, any currently existing nodes won't be auto indexed, which means you'll have to recreate them. If you don't want to start from scratch, here's some documentation on how to update them: http://docs.neo4j.org/chunked/milestone/auto-indexing.html#auto-indexing-update-removal (I've never tried it).
Then you can start finding nodes like this:
start n=node:node_auto_index(name="Dovakiin"), or
start n=node:node_auto_index(job="Dragon Slayer")
Or, like this with the C# client:
Node<User> myNode = client.QueryIndex<User>("node_auto_index", IndexFor.Node, "name:Dovakiin").First();, or
Node<User> myNode = client.QueryIndex<User>("node_auto_index", IndexFor.Node, "job:Dragon Slayer").First();
You can do the same thing with with relationships as well, as soon as you set it up in the /conf/neo4j.properties file. You do it exactly the same way as with nodes.

you must manually add the nodes to the index, something like
client.indexRef1.addToIndex(nodeRef, 'name', 'Dovakiin')
client.indexRef2.addToIndex(nodeRef, 'job', 'Dragon Slayer')
there is also an automatic indexing feature in neo4j in case you want the nodes to be automatically added to the index.

Related

How to stop neo4j creating node if node already exists

I've been fiddeling around with neo4j for the last couple of days, but I keep running
into one issue.
Whenever I run my code it creates an entirely new node.
As seen obove there are two "SELLER" nodes and one "BUYER,SELLER" node.
The node name is "Role" and I'd ideally want it to update if there is already a "Role" node
and only create a new node when there is no "Role" node yet.
Shown below is the query I've written in C# so far, I can't get it to stop creating new nodes.
Hopefully the gods of stackoverflow can help a young lad out. haha
query = query.Merge("(role:Role {roletype: $newRoleValue})")
.Set("role = $newRole")
.WithParam("newRole", new { Name = newTenderVersion.Organization.Role.ToString() })
.WithParam("newRoleValue", newTenderVersion.Organization.Role.ToString())
.Merge($"(organization)-[:HAS_ROLE]->(role)");
=====
EDIT:
query = query.Merge("(role:Role {roleType: $newRoleValue})")
.WithParam("newRoleValue", newTenderVersion.Organization.Role.ToString())
.Merge($"(organization)-[:HAS_ROLE]->(role)");
So I've figured out how to stop the node duplication, now the question remains.
How do I make sure that if an organization is only a SELLER, it will delete its BUYER nodes.

How to implement this tree view in C#

I need to implement a binary tree view attached in the diagram. The process is going like this. First, start in position#1. Then need to print P#2.After p#3.As binary tree method need print left first then right. This is for pyramid system. Please check the example below how this should be. Each node represents the customer(By ID) and his position.
Ex: Let's assume out customerID is number 01 and he might have 5 positions in the tree representing his ID #1, Like in the diagram. And customer number2 have 3 positions in a tree. So we have a lot of customers and some have more than 20 positions in each node.
I need a solution to implement this view. Please note this is not relevant for binary search tree. Thank you!
Please check the image in here
It looks like you are trying to implement a breadth first search but with an unusual order for returning the results whereby you take one item from each child in a row and interleave them.
So you will need to proceed row-by-row: build a list of nodes at that level, and then to get the next level take all of the left children of each and then all of the right children of each of each to form the next level. This will disentangle your curious tree and you will visit the nodes in this order:
Position #1
LEFTS = { P#1 }, RIGHTS = { P#2 }
LEFTS = { P#3 P#4}, RIGHTS = { P#5 P#6 }
LEFTS = { P#7 P#8 P#9 P#10 }, RIGHTS = { P#11 P#12 P#13 P#14 }

Trying to do Unwind and Create New Relationship Between Nodes Neo4J C# Client

I have a list of nodes - In the code as targetNodeList and I have a node called sourceNode(Different Types Of Nodes).
The List and the single node are already existing in the neo4j Db
and I would like to make a relationship between them with additional data that is inside the targetNodeList.
TargetNodeList is a wrapper which contains the node data and the relationship data that I would like to put inside the Relationship
I havn't manage to finish the code because I don't know how to continue it but thats the sample that I tried to do :
public void CreateRelationshipBetweenNodes(NodeType sourceNode,List<TargetNodes> targetNodeList,int solutionId)
{
graphClient.Cypher
.Unwind(targetNodeList, "singleNode")
.Match("(firstNode:FirstType)", "(secondNode:SecondType)")
.Where(" firstNode.Id = otherNode:FirstType{Id:innerNode.Id}")
.AndWhere(" secondNode.Id = node:SecondType {Id:singleNode.Id}")
.WithParams(new { innerNode = sourceNode})
.Create("(firstNode)-[msg:SENT {solution}]->(secondNode)")
.WithParam("solution", solutionId).ExecuteWithoutResults();
}
its not working and there is still more data that I want to add to the relationship from the singleNode for example: singleNode.Score
Would appriciate any help.
Thanks a lot from advanced.
So I'm a little confused with the nodes you're taking in and how they relate, but hopefully the below query will get you on the right route.
First off, match on the sourceNode then UNWIND your other nodes, ready to do the create. Once you have the two nodes MATCHed you then CREATE the relationship (PS. you may want MERGE if you don't want duplicates) - and I set an Id property on the relationship - you need to provide a property name, else it won't work!
graphClient.Cypher
.Match("(sourceNode:SourceNodeType {Id:sourceNode.Id})")
.Unwind(targetNodeList, "singleNode")
.Match("(targetNodeInDb:TargetNode {Id:targetNode.Id})")
.Create("(sourceNode)-[:SENT {Id:{solutionIdParam}}]->(targetNode)")
.WithParam("solutionIdParam", solutionId)
.ExecuteWithoutResults();

Update node property in neo4j database

I am updating node property through C# using following code, but this code will not update node property. If i run only query part in neo4j browser then it work. Is any another approach to solve this issue?
//Code
CypherQuery query = new CypherQuery("
MATCH (n { Name: \"Person B1\" })
SET n.Name = \"Person B2\"
RETURN n",
new Dictionary<string, object>(), CypherResultMode.Projection);
Then probably your match fails.
Does the code actually return the node in question?
You should also use parameters for your data, both for the existing and new ´Name` properties.
And Person would most probably be a label.
This isn't at all a supported way of using Neo4jClient. Please try consulting any of the documentation.
Then, the documentation also includes debugging guidance to help you determine what is different between your C# vs. the Cypher you expect.

Retrieving custom entities in CRM 4 C#

In our CRM environment users have a 1:N relation to an available hours entity which represents their actually available hours for the week. I'm looking for a way in c# to retrieve all the available hours for users that are of a specific team for the current week.
I'm new to developing in CRM and I've looked around but there seems to be a lot of ways to do this and im not sure which is best suited.
The language is C# and the version of CRM is MS CRM 4.0
I'll cover this is 3 bits: general approach, code itself and then some notes on the code (the code is commented to draw attention to certain things, though but some of them will bear further explanation outside the code).
General Approach
As you've seen, there are a couple of ways to do things but for external applications interacting with CRM via the web service it comes down to 3 main options:
Use the strongly typed methods you get from adding a web reference in web service calls to retrieve your custom entities (I think you mentioned in a previous question seeing an explosion of methods...this gets worse when you have a lot of custom entities)
Use DynamicEntity in your web service calls
FetchXML
If your system is super-simple you can normally get away with (1) but I would recommend either of (2) or (3). Using (2) means that you only really have to remember a handful of web service methods and its good if you ever move into plugins or workflow assemblies as the concepts carry across reasonably well. (3) is good if you know FetchXML and can form the appropriate query.
I normally approach these things using (2) as it's commonly found, it's a nice middle of the road approach and, like I said, your code will be reasonably easy to translate to a plugin or workflow assembly. FetchXML carries across quite well but I was never good at forming the queries - I'll cover some techniques for this later but lets go with (2).
Also, if you use DynamicEntity you shouldn't need to refresh your web references because of how you work with it and its array of Property objects (basically you get flexibility at the expense of strong typing) as you'll see in the code. If you go with (1) you get strong typing against your custom entities but you'll have to keep refreshing your WebReference depending on the cadence of changes people make to your entities.
The Code
This is in a little console application where I've added a WebReference to the CRM service and done some calls to simulate your scenario. The code should carry across to other apps like web apps. I have tried to comment it so it is probably worth a read through before moving to the next section.
(NB. I don't claim this is the world's best code, but it does seem work and should get you started)
(NB2. I made the mistake of calling my namespace for the web reference CrmService - please don't make the same mistake as me....)
static void Main(string[] args)
{
CrmService.CrmService svc = new CrmService.CrmService();
svc.CrmAuthenticationTokenValue = GetToken();
svc.UseDefaultCredentials = true;
#region 1 - Retrieve users in team
RetrieveMembersTeamRequest teamMembersReq = new RetrieveMembersTeamRequest()
{
EntityId = new Guid("D56E0E83-2198-E211-9900-080027BBBE99"), //You'll need the team GUID
ReturnDynamicEntities = true
};
ColumnSet teamMembersReqColumnSet = new ColumnSet();
teamMembersReqColumnSet.Attributes = new string[] { "systemuserid", "domainname" };
teamMembersReq.MemberColumnSet = teamMembersReqColumnSet; //Don't use: teamMembersReq.MemberColumnSet = new AllColumns()
List<Guid> userIdList = new List<Guid>();
RetrieveMembersTeamResponse teamMembersResp = svc.Execute(teamMembersReq) as RetrieveMembersTeamResponse;
if (teamMembersResp != null)
{
BusinessEntity[] usersInTeamAsBusinessEntity = teamMembersResp.BusinessEntityCollection.BusinessEntities;
List<DynamicEntity> usersInTeamAsDynEntity = usersInTeamAsBusinessEntity.Select(be => be as DynamicEntity).ToList(); //BusinessEntity not too useful, cast to DynamicEntity
foreach (DynamicEntity de in usersInTeamAsDynEntity)
{
Property userIdProp = de.Properties.Where(p => p.Name == "systemuserid").FirstOrDefault();
Property domainNameProp = de.Properties.Where(p => p.Name == "domainname").FirstOrDefault();
if (userIdProp != null)
{
KeyProperty userIdKeyProp = userIdProp as KeyProperty; //Because it is the unique identifier of the entity
userIdList.Add(userIdKeyProp.Value.Value); //Chuck in a list for use later
Console.Write("Key: " + userIdKeyProp.Value.Value.ToString());
}
if (domainNameProp != null)
{
StringProperty domainNameStringProp = domainNameProp as StringProperty; //Because its data type is varchar
Console.Write("| Domain Name: " + domainNameStringProp.Value);
}
Console.WriteLine();
}
}
#endregion
/*
* For this example I have created a dummy entity called new_availablehours that is in a 1:N relationship with use (i.e. 1 user, many new_available hours).
* The test attributes are :
* - the relationship attribute is called new_userid...this obviously links across to the GUID from systemuser
* - there is an int data type attribute called new_hours
* - there is a datetime attribute called new_availabilityday
*/
#region Retrieve From 1:N
RetrieveMultipleRequest req = new RetrieveMultipleRequest();
req.ReturnDynamicEntities = true; //Because we love DynamicEntity
//QueryExpression says what entity to retrieve from, what columns we want back and what criteria we use for selection
QueryExpression qe = new QueryExpression();
qe.EntityName = "new_availablehours"; //the entity on the many side of the 1:N which we want to get data from
qe.ColumnSet = new AllColumns(); //Don't do this in real life, limit it like we did when retrieving team members
/*
* In this case we have 1 x Filter Expression which combines multiple Condition Operators
* Condition Operators are evaluated together using the FilterExpression object's FilterOperator property (which is either AND or OR)
*
* So if we use AND all conditions need to be true and if we use OR then at least one of the conditions provided needs to be true
*
*/
FilterExpression fe = new FilterExpression();
fe.FilterOperator = LogicalOperator.And;
ConditionExpression userCondition = new ConditionExpression();
userCondition.AttributeName = "new_userid"; //The attribute of qe.EntityName which we want to test against
userCondition.Operator = ConditionOperator.In; //Because we got a list of users previously, the appropriate check is to get records where new_userid is in the list of valid ones we generated earlier
userCondition.Values = userIdList.Select(s => s.ToString()).ToArray(); //Flip the GUID's to strings (seems that CRM likes that) then set them as the values we want to evaulate
//OK - so now we have this userCondition where valid records have their new_userid value in a collection of ID's we specify
ConditionExpression dateWeekBound = new ConditionExpression();
dateWeekBound.AttributeName = "new_availabilityday";
dateWeekBound.Operator = ConditionOperator.ThisWeek; //ConditionOperator has a whole bunch of convenience operators to deal with dates (e.g. this week, last X days etc) - check them out as they are very handy
/*
* As an aside, if we didn't want to use the convenience operator (or if none was available) we would have to create a ConditionExpression like:
*
* ConditionExpression dateLowerBound = new ConditionExpression();
* dateLowerBound.AttributeName = "new_availabilityday";
* dateLowerBound.Operator = ConditionOperator.OnOrAfter;
* dateLowerBound.Values = new object[] { <Your DateTime object here> };
*
* And a corresponding one for the upper bound using ConditionOperator.OnOrBefore
*
* Another alternative is to use ConditionOperator.Between. This is flexible for any sort of data, but the format of the Values array will be something like:
* ce.Values = new object[] { <lower bound>, <upper bound> };
*/
fe.Conditions = new ConditionExpression[] { userCondition, dateWeekBound }; //Add the conditions to the filter
qe.Criteria = fe; //Tell the query what our filters are
req.Query = qe; //Tell the request the query we want to use
RetrieveMultipleResponse resp = svc.Execute(req) as RetrieveMultipleResponse;
if (resp != null)
{
BusinessEntity[] rawResults = resp.BusinessEntityCollection.BusinessEntities;
List<DynamicEntity> castedResults = rawResults.Select(r => r as DynamicEntity).ToList();
foreach (DynamicEntity result in castedResults)
{
Property user = result.Properties.Where(p => p.Name == "new_userid").FirstOrDefault();
Property hours = result.Properties.Where(p => p.Name == "new_hours").FirstOrDefault();
if (user != null)
{
LookupProperty relationshipProperty = user as LookupProperty; //Important - the relationship attribute casts to a LookupProperty
Console.Write(relationshipProperty.Value.Value.ToString() + ", ");
}
if (hours != null)
{
CrmNumberProperty hoursAsCrmNumber = hours as CrmNumberProperty; //We also have CrmFloatProperty, CrmDecimalProperty etc if the attribute was of those data types
Console.Write(hoursAsCrmNumber.Value.Value);
}
Console.WriteLine();
}
}
#endregion
Console.ReadLine();
}
static CrmAuthenticationToken GetToken()
{
CrmAuthenticationToken token = new CrmAuthenticationToken();
token.AuthenticationType = 0; //Active Directory
token.OrganizationName = "DevCRM";
return token;
}
So..What Was That?
I'm not going to do a blow-by-blow, but home in on the key points:
The key method when using the service is the Execute() method where we pass it a request object and get back a response object. The requests will all be objects of class <Operation>Request and responses will be objects of class <Operation>Response.
You typically want to work with DynamicEntity - the <Operation>Request objects will typically expose a property called ReturnDynamicEntities which you should be setting to true
Most <Operation>Request objects have a ColumnSet property where you can specify what attributes you want returned. It is typically bad practice to specify AllColumns() and instead you should be explicit about what data you want returned. Attributes need to match their names in CRM (so of the form <prefix>_<field name>) and all in lower case
Getting users in a team isn't too interesting as it's a predefined operation in CRM and nothing special...in this case the SDK is your friend as it'll show you how these work
Retrieving a bunch custom entities is a more interesting use case and we can normally get these out by using the RetrieveMultipleRequest and RetrieveMultipleResponse methods (if you only want one record then you can just use RetrieveRequest and RetrieveResponse...but you need to know the GUID of what you are looking for to feed into the RetreiveRequest object).
For RetrieveMultipleRequest we feed it a query (QueryExpression) which says what entity(EntityName) we want to get multiple of, the attributes(ColumnSet) of that entity we want to return and the filter(Criteria) which is used to select the actual records we want
Focus on the usage of QueryExpression, FilterExpression and ConditionExpression. An important thing to know is what operators you have available for you in ConditionExpression - I have tried to call some out in the code, but once again the SDK is your best friend to know what is available
Something I haven't covered is more complex filtering like (x OR y) AND z. There is a reasonably good example here. It's just a different way to use FilterExpression and ConditionExpression
Note that RetrieveMultipleResponse contains an array of BusinessEntity. The BusinessEntity by itself is pretty useless so we cast that to a list of DynamicEntity - LINQ is really your friend here and for quite a bit of messing with CRM stuff, LINQ comes in handy
Note how we check properties - de.Properties.Where(p => p.Name == "systemuserid").FirstOrDefault(); and then check if it is NULL. This is because if in CRM an attribute of a record is NULL it won't be returned from the service call - so just because you request an attribute in the ColumnSet don't make the automatic assumption it is there (unless you have it set as mandatory in CRM - then probably OK)...test it and your app will be a whole lot less brittle.
The Property class itself has limited value - to really work with a property you have to cast it to the thing it actually is. I keep harping on about it but the SDK will tell you what the types are but it starts to feel natural after a while, e.g. the GUID for the record is in a KeyProperty, ints are in CrmNumberProperty, floats are in CrmFloatProperty, strings are StringProperty etc. Note the weak typing (which I mentioned previously) where we have to get properties by name, cast to the proper value etc
Other Takeaways
Normally you're going to have to be quite chatty with the service calls - from what I've seen this is fairly normal when developing against CRM (I can only talk about my own experience though)
It is important to be really defensive in how you code - check that properties exist, check you're casting to the right types etc.
If you have to catch an exception it'll be a SoapException and the info you typically want will be in the Detail property - very important to remember this or you'll look at the exception and think it's not telling you a whole bunch
Consult back with the customizations in CRM to figure out the name of relationship attributes, data types etc. I like to have the customizations window for the entities I need open while doing dev for easy reference.
FetchXML is really powerful but really fiddly. If you get good at it though then you'll get a lot of good mileage - a tool like this is useful. Also, a handy trick - if you can construct what you want (or an example of what you want) as an advanced find through the CRM UI then you can use this trick to get the FetchXML it used....you'll probably need to tweak the GUIDs and the like but it gives you a building block if you want to use FetchXML in your code as most of the query is written for you.
Depending on your deployment environment, you might have to mess around with what credentials are used, whether you go through a proxy etc...typical web reference stuff. Just worth noting that CRM isn't immune to that kind of thing - I don't have any real advice here, just a note as it has caused me some "fun" in the past

Categories