We are using Neo4j Community 3.2.2 with Neo4jClient 2.0.0.9 and try to create a node if it does not exist. This is covered in the cypher examples and questions like this here on SO, so we thought that should be pretty straight forward:
public class KlientNode
{
[JsonProperty(PropertyName = "id")]
public Guid Id { get; set; }
}
and:
var neuerKlient = new KlientNode { Id = ev.KlientId };
var kq = graphClient.Cypher
.Merge("(klient:Klient { id: {klientId} })")
.OnCreate()
.Set("klient = {neuerKlient}").WithParams(new
{
klientId = neuerKlient.Id,
neuerKlient
});
Console.WriteLine(kq.Query.DebugQueryText);
kq.ExecuteWithoutResults();
So we basically copied the example 1:1.
Unfortunately, this leads to the following output and exception:
MERGE (klient:Klient { id: "80248429-ea80-4a5d-8d4e-88dc1499ea8a" })
ON CREATE
SET klient = {
"id": "80248429-ea80-4a5d-8d4e-88dc1499ea8a"
}
Neo4jClient.NeoException: SyntaxError: Invalid input 'N': expected 'p/P' (line 5, column 2 (offset: 250))
"ON CREATE"
^
The cause seem to be the quotes around the "id" in the SET klient = ... query. If I paste the generated query to the neo4j web console, it shows a syntax error, and if I remove the quotation marks, the query runs just fine.
Anyone has an idea what might be causing the broken query when we just seem to copy the examples almost verbatim?
FWIW, I'm not sure why that happened, but we were able to solve it as follows:
var kq = graphClient.Cypher
.Merge($"(klient:{NodeName} {{ id: {{klientId}} }})")
.OnCreate()
.Set("klient = {neuerKlient}").WithParams(new
{
klientId = ev.KlientId,
neuerKlient = new KlientNode
{
Id = ev.KlientId,
},
});
Console.WriteLine(kq.Query.DebugQueryText);
kq.ExecuteWithoutResults();
The debug output is still the same (ignore the different random Guid):
MERGE (klient:Klient { id: "87798b47-ab1b-49b7-9c5e-018cd244465e" })
ON CREATE
SET klient = {
"id": "87798b47-ab1b-49b7-9c5e-018cd244465e"
}
and the query is still broken if I try to paste it into the neo4j web frontend:
However, the query now executes without an exception.
Since the only change is the shorthand definition of the neuerKlient property in the anonymous object, I assume there is some behaviour internally that was causing the error (even though the debug query output was the same).
Related
I have a little project in C# which consists of an API that receives data about (made up) infection cases. Each case object have the geographic coordinates of such case.
The program is supposed to query the nearest city to that case and return it as a response. I try to do this with the following method:
private void ObterCidadeMaisProxima(Infectado infectado){
var collection = GetCidadeCollection();
string coordinates = $"[ {infectado.Localizacao.Latitude}, {infectado.Localizacao.Longitude} ]";
string query = "{ location: { $near : { $geometry: { type: \"Point\",";
query += $"coordinates: {coordinates}";
query += "}, $maxDistance: 1000000 } } }";
var e = BsonDocument.Parse(query);
var doc = new QueryDocument(e);
var result = collection.Find<Cidade>(doc).ToList<Cidade>();
//So far I just want to test the function, that's why I don't return the result
foreach(var r in result) Console.WriteLine(r);
}
Both my classes (Infectado and Cidade) have the following attribute:
public GeoJson2DGeographicCoordinates Localizacao { get; set; }
And in MongoDB Atlas I have created the following indexes for both collections (infectado and cidade):
Geospatial indexes
So, I have two collections using geospatial indexes, and I'm trying to execute a $near query, but MongoDB can't find the indexes.
Here's the output:
fail: Microsoft.AspNetCore.Server.Kestrel[13]
Connection id "0HM7HA1MKQ9RL", Request id "0HM7HA1MKQ9RL:00000001": An unhandled exception was thrown by the
application.
MongoDB.Driver.MongoCommandException: Command find failed: error processing query: ns=covidDB.cidadeTree: GEONEAR field=location
maxdist=1e+06 isNearSphere=0
Sort: {}
Proj: {}
planner returned error :: caused by :: unable to find index for $geoNear query.
What's wrong?
I have the follwoing query in cypher
Match(n1: Red)
Where n1.Id = "someId"
Call apoc.path.subgraphAll(n1,{ minLevel: 0,maxLevel: 100,relationshipFilter: "link",labelFilter: "+Red|Blue"})
Yield nodes, relationships
Return nodes, relationships
The graph I query has roughly a structure of "Red -> Blue -> Red" where all the edges are of the type "link".
The query yield exactly the expected result in the browser client.
My C# looks like this:
string subgraphAll = "apoc.path.subgraphAll";
object optionsObj = new {
minLevel = 0,
maxLevel = 100,
relationshipFilter = $"{link}",
labelFilter = $"+{Red}|{Blue}",
beginSequenceAtStart = "true",
bfs = true,
filterStartNode = false,
limit = -1,
//endNodes = null,
//terminatorNodes = null,
//whitelistNodes = null,
//blacklistNodes = null,
};
string options = JObject.FromObject(optionsObj).ToString();
var query = client.Cypher
.Match($"(n1:{"Red"})")
.Where((Red n1) => n1.Id == "someId")
.Call($"{subgraphAll}(n1, {options})")
.Yield($"nodes,relationships")
//FigureOut what to do
.Return<Object>("");
var result = query.ResultsAsync.Result;
My question is: How would I write that in C# with the Neo4J client and how do I get typesafe lists at the end (something like List<Red>, List<Blue>, List<Relationship>).
As Red and Blue are different types in C#, I don't see how I can deserialize the mixed "nodes" list from the query.
Note that my examples are a bit simplified. The Nodetypes are not strings but come from Enums in my application to have a safe way to know what node types exist and there are real models behind those types.
I tried to break out the whole parametrization of the stored proc, but the code is untested and I don't know if there is a better solution to do this yet. If there is a better way, please advise on that too.
I am new to cypher, so I need a little help here.
My idea was to split the nodes list into two lists (Red and Blue List) and then output the three Lists as properties of an anonymous object (as in the examples). Unfortunately My cypher isn't good enough to figure it out yet, and translating to the c# syntax at the same time doesn't help either.
My main concern is that once I deserialize into a list of untyped objects, It will be hell to parse them back into my models. So I want the query to do that sorting out for me.
In my view, if you want to go down the route of parsing the outputs into Red/Blue classes, it's going to be easier to do it in C# than in Cypher.
Unfortunately, also in this case - I think it'll be easier to execute the query using the Neo4j.Driver driver instead of Neo4jClient - and that's because at the moment, Neo4jClient seems to remove the id (etc) properties you'd need to be able to rebuild the graph properly.
With 4.0.3 of the Client you can access the Driver by doing this:
((BoltGraphClient)client).Driver
I have used a 'Movie/Person' example, as it's a dataset I had to hand, but the principals are the same, something like:
var queryStr = #"
Match(n1: Movie)
Where n1.title = 'The Matrix'
Call apoc.path.subgraphAll(n1,{ minLevel: 0,maxLevel: 2,relationshipFilter: 'ACTED_IN',labelFilter: '+Movie|Person'})
Yield nodes, relationships
Return nodes, relationships
";
var movies = new List<Movie>();
var people = new List<People>();
var session = client.Driver.AsyncSession();
var res = await session.RunAsync(queryStr);
await res.FetchAsync();
foreach (var node in res.Current.Values["nodes"].As<List<INode>>())
{
//Assumption of one label per node.
switch(node.Labels.Single().ToLowerInvariant()){
case "movie":
movies.Add(new Movie(node));
break;
case "person":
/* similar to above */
break;
default:
throw new ArgumentOutOfRangeException("node", node.Labels.Single(), "Unknown node type");
}
}
With Movie etc defined as:
public class Movie {
public long Id {get;set;}
public string Title {get;set;}
public Movie(){}
public Movie(INode node){
Id = node.Id;
Title = node.Properties["title"].As<string>();
}
}
The not pulling back ids etc problem for the client is something I need to look at how to fix, but this is the quickest way short of that to get where you want to be.
Taking an example from a previous post; I am unable to query using IN and a list of Guids; I get different errors depending on what I have tried...
public class DataAccess
{
string _connectionString = "{your connection string}";
public async Task<IEnumerable<CustomerDto>> GetListAsync(List<Guid> customers)
{
const string query = #"
SELECT Id,
Name
FROM Customers
WHERE Id IN #CustomerIdList
";
using (var c = new SqlConnection(_connectionString))
{
return await c.QueryAsync<CustomerDto>(query, new { CustomerIdList = customers.ToArray() });
}
}
}
The above fails with 42601: syntax error at or near "$1"
I have tried various things like the below which also fails with 42601: syntax error at or near "$1":
return await c.QueryAsync<CustomerDto>(query, new { CustomerIdList = new[] { customers[ 0 ], customers[ 1 ], customers[ 2 ], customers[ 3 ] } } );
Can anyone help, what am I doing wrong?
EDIT: Fixed query due to copying example code example from another question
Found out that you have to use a different clause as IN does not work with an array of parameters in Postgresql which sounds like a bit of a flaw to me:
WHERE Id = ANY(#CustomerIdList)
Using the example code from the Unity Developer Guide | Parse
# https://www.parse.com/docs/unity_guide#objects-updating
// Create the object.
var gameScore = new ParseObject("GameScore")
{
{ "score", 1337 },
{ "playerName", "Sean Plott" },
{ "cheatMode", false },
{ "skills", new List<string> { "pwnage", "flying" } },
};
gameScore.SaveAsync().ContinueWith(t =>
{
// Now let's update it with some new data. In this case, only cheatMode
// and score will get sent to the cloud. playerName hasn't changed.
gameScore["cheatMode"] = true;
It just adds a new row and leaves the original row unchanged.
I guess i'm thinking Parse would do something "SQL like" such as UPDATE where primaryKey = 123.
Searching for an answer i found this code #
https://parse.com/questions/updating-a-field-without-retrieving-the-object-first, but there was no example in C#. All attempts to port this to C# result in multiple syntax errors.
UnityScript:
// Create a pointer to an object of class Point with id dlkj83d
var Point = Parse.Object.extend("Point");
var point = new Point();
point.id = "dlkj83d";
// Set a new value on quantity
point.set("quantity", 6);
// Save
point.save(null, {
success: function(point) {
// Saved successfully.
},
error: function(point, error) {
// The save failed.
// error is a Parse.Error with an error code and description.
}
});
Does Parse have some way to update a row that already exists using C#? And where is it in the docs? And how can their own example be so useless?
One of the posts related to my question stated "retrieve the object, then write it back with the changes" and i had not the faintest idea how to execute the stated objective (especially after the epic fail of Parse Documentation's example code)
Here is what i have been able to figure out and make work:
var query = new ParseQuery<ParseObject>("Tokens")
.WhereEqualTo ("objectId", "XC18riofu9");
query.FindAsync().ContinueWith(t =>
{
var tokens = t.Result;
IEnumerator<ParseObject> enumerator = tokens.GetEnumerator();
enumerator.MoveNext();
var token = enumerator.Current;
token["power"] = 20;
return token.SaveAsync();
}).Unwrap().ContinueWith(t =>
{
// Everything is done!
//Debug.Log("Token has been updated!");
});
the first part retrieves the object with the stated objectId, the second part sets the fields in the object. The third part reports all is well with the operation.
it's a monkey see, monkey do understanding at this point being that i do not understand the finer points in the code.
the code can be tested by creating a class named "Tokens". in that class create a tokenName field and a power field. make a few rows with Fire, water, mud as the tokenNames. Replace the objectId in the .WhereEqualTo clause with a valid objectId or any other search parameters you like. Execute the code and observe the changes in the Parse Data Browser.
For extra credit create the class required to implement the example code from the Chaining Tasks Together section of Parse's Documentation.
https://www.parse.com/docs/unity_guide#tasks-chaining
Updated to show a working sample
I am trying to do a partial search on a collection of usernames in ElasticSearch.
Searching around has pointed me in the nGram Tokenizer direction but I am stumped at proper implementation and fail to get any results.
This is the the relevant code stripped from the project I'm working on.
I have tried different combinations and search types to no avail.
setup.cs
var client = new ElasticClient(settings.ConnectionSettings);
// (Try and) Setup the nGram tokenizer.
var indexSettings = new IndexSettings();
var custonAnalyzer = new CustomAnalyzer();
customAnalyzer.Tokenizer = "mynGram";
customAnalyzer.Filter = new List<string> { "lowercase" };
indexSettings.Analysis.Analyzers.Add("mynGram", customAnalyzer);
indexSettings.Analysis.Tokenizers.Add("mynGram", new NGramTokenizer
{
MaxGram = 10,
MinGram = 2
});
client.CreateIndex(settings.ConnectionSettings.DefaultIndex, indexSettings);
client.MapFromAttributes<Profile>();
// Create and add a new profile object.
var profile = new Profile
{
Id = "1",
Username = "Russell"
};
client.IndexAsync(profile);
// Do search for object
var s = new SearchDescriptor<Profile>().Query(t => t.Term(c => c.Username, "russ"));
var results = client.Search<Profile>(s);
Profile.cs
public class Profile
{
public string Id { get; set; }
[ElasticProperty(IndexAnalyzer = "mynGram")]
public string Username { get; set; }
}
Any tips would be much appreciated.
Take a look at this from the es docs on nGram token filters:
"settings" : {
"analysis" : {
"analyzer" : {
"my_ngram_analyzer" : {
"tokenizer" : "my_ngram_tokenizer"
}
},
"tokenizer" : {
"my_ngram_tokenizer" : {
"type" : "nGram",
"min_gram" : "2",
"max_gram" : "3",
"token_chars": [ "letter", "digit" ]
}
}
}
}
A few things to note
You need to add mynGram to your analyzer or it won't be used. They way it works is like this. Each indexed field has an analyzer applied to it, an analyzer is one tokenizer followed by zero or more token filters. You have defined a nice nGram tokenizer (mynGram) to use, but you did not use it in customAnalyzer, it is using the standard tokenizer. (Basically you are just defining but never using mynGram.)
You need to tell elasticsearch to use your customAnalyzer in your mapping:
"properties": {"string_field": {"type": "string", "index_analyzer": customAnalyzer" }}
You should change the maxGram to a bigger number (maybe 10), otherwise 4 letter searches will not behave exactly as autocomplete (or could return nothing, depends on the search-time analyzer).
Use the _analyze api endpoint to test your analyzer. Something line this should work.
curl -XGET 'http://yourserver.com:9200?index_name/_analyze?analyzer=customAnalyzer' -d 'rlewis'
Good luck!