问题
I'm following this little write up: https://github.com/Readify/Neo4jClient/wiki/cypher but I'm doing it from Powershell. so what I have so far is
[System.Reflection.Assembly]::LoadFrom("C:\...\Newtonsoft.Json.6.0.3\lib\net40\NewtonSoft.Json.dll")
[System.Reflection.Assembly]::LoadFrom("C:\...\Neo4jClient.1.0.0.662\lib\net40\Neo4jClient.dll")
$neo = new-object Neo4jClient.GraphClient(new-object Uri("http://localhost:7474/db/data"))
$q=$neo.Cypher.Match("n").Return({param($m) $m});
with which I would mean to retrieve all nodes in the database. the Return()
method is shown in the example to require a lambda expression as a parameter, which in Powershell would be a code-block, however, I get the following error:
Cannot find an overload for "Return" and the argument count: "1". At line:1 char:1 + $q=$neo.Cypher.Match("n").Return({param($m) $m}); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : NotSpecified: (:) [], MethodException + FullyQualifiedErrorId : MethodCountCouldNotFindBest
where am I going wrong?
* Update I *
with the explanation provided by @PetSerAl below, I've managed to get a little further, but I'm still stuck. below I will quote the write up (c#) and then show the equivalent powershell. first we declare a class
public class User
{
public long Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public string Email { get; set; }
}
my class differs a little
Add-Type -TypeDefinition "public class Project { public string Code; public string Name; public string Parent; public string Lifespan; }"
their Cypher
MATCH (user:User)
RETURN user
their c#
graphClient.Cypher
.Match("(user:User)")
.Return(user => user.As<User>())
.Results
now my cypher
MATCH (n:Project)
RETURN n
...and finally, my attempt at the powershell:
$exp = [System.Linq.Expressions.Expression]
$p = $exp::Constant("Project")
$fn = $exp::TypeAs($p, (new-object Project).GetType())
$return = $exp::Lambda([Func[Project]], $fn, $p)
$neo.Cypher.Match("n").Return($return)
but I get an error
Exception calling "Return" with "1" argument(s): "The expression must be constructed as either an object initializer (for example: n => new MyResultType { Foo = n.Bar }), an anonymous type initializer (for example: n => new { Foo = n.Bar }), a method call (for example: n => n.Count()), or a member accessor (for example: n => n.As().Bar). You cannot supply blocks of code (for example: n => { var a = n + 1; return a; }) or use constructors with arguments (for example: n => new Foo(n)). If you're in F#, tuples are also supported. Parameter name: expression" At line:1 char:1 + $neo.Cypher.Match("n").Return($return) + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : NotSpecified: (:) [], MethodInvocationException + FullyQualifiedErrorId : ArgumentException
which, for once, is actually very clear and understandable. so what I want is a method call e.g. n => n.Count()
and clearly did not achieve that.
help?
* Update II *
so, continuing on with the torturous path of neo4j from powershell, I've given @PetSerAl's second approach a stab and got a little further. here's what I managed to write:
$neopath = "C:\[...]\Neo4jClient.dll"
Add-Type -ReferencedAssemblies $neopath -TypeDefinition @"
using System;
using System.Linq.Expressions;
using Neo4jClient.Cypher;
public class Project {
public string Code;
public string Name;
public string Parent;
public string Lifespan;
};
public static class NeoExp {
public static readonly Expression<Func<Neo4jClient.Cypher.ICypherResultItem,Project>> GetProject = (n) => n.As<Project>();
}
"@
which now allows me to do:
$neo.Cypher.Match("n:Project").Return([NeoExp]::GetProject)
and that, miraculously, works! except it brings me back no data:
Results ResultsAsync Query Client
------- ------------ ----- ------
Neo4jClient.Cypher.CypherQ... Neo4jClient.GraphClient
and I know I have projects in the database... so what could the issue be now?
* Update III *
wow, so close but still not done. as per the latest suggestion from @PetSerAl. I tried:
$neo.Cypher.Match("n:Project").Return([NeoExp]::GetProject).get_Results()
which yielded an illuminating error:
Exception calling "get_Results" with "0" argument(s): "The graph client is not connected to the server. Call the Connect method first."
so that made it clear I first needed to do:
$neo.Connect()
also, I needed parentheses around the query's match clause:
$neo.Cypher.Match("(n:Project)").Return([NeoExp]::GetProject)
now I get 27 results back as expected in the .Results
field... however, the results are all blank. so I think maybe it has to do with the n.As<Project>()
where perhaps my class isn't defined properly and that fails. any thoughts?
* Update IV *
ok, got it. the Project
class needs to have properties, not fields:
public class Project {
public string Code { get; set; }
public string Name { get; set; }
public string Parent { get; set; }
public string Lifespan { get; set; }
};
and that's it. I have data. yea!!!
@PetSelAl: I owe you a beer
回答1:
There are two problems with Return
method:
- It accepts argument of expression tree type, rather then compiled delegate type. And PowerShell does not have an easy way to create expression tree from a
ScriptBlock
. So, you have to create expression tree by hands or usestring
overload. string
overload does not allows PowerShell to infer generic parameter for method, and PowerShell syntax does not allows to specifying generic parameter explicitly. So, PowerShell can not callstring
overload ofReturn
method directly. You have to use some workaround to call it, for example, call it thruReflection
.
Sample how can you create a simple expression tree ((a,b) => a*2+b
) in PowerShell:
# First way: messing with [System.Linq.Expressions.Expression]
$a=[System.Linq.Expressions.Expression]::Parameter([int],'a')
$b=[System.Linq.Expressions.Expression]::Parameter([int],'b')
$2=[System.Linq.Expressions.Expression]::Constant(2)
$Body=[System.Linq.Expressions.Expression]::Add([System.Linq.Expressions.Expression]::Multiply($a,$2),$b)
$Sum=[System.Linq.Expressions.Expression]::Lambda([Func[int,int,int]],$Body,$a,$b)
$Sum
# Second way: using help of C#
Add-Type -TypeDefinition @'
using System;
using System.Linq.Expressions;
public static class MyExpression {
public static readonly Expression<Func<int,int,int>> Sum=(a,b) => a*2+b;
}
'@
[MyExpression]::Sum
回答2:
I know this is not exactly what you're asking, and might not even be what you want, but you could create your own C# class right inside PowerShell using Add-Type. It might be easier to implement it that way and provide simple methods you can use within the PowerShell code if what you're writing relies on a lot of C#-specific things.
This example is taken straight from the link above:
$source = @"
public class BasicTest
{
public static int Add(int a, int b)
{
return (a + b);
}
public int Multiply(int a, int b)
{
return (a * b);
}
}
"@
Add-Type -TypeDefinition $source
[BasicTest]::Add(4, 3)
$basicTestObject = New-Object BasicTest
$basicTestObject.Multiply(5, 2)
回答3:
The best I can tell is that you're using it wrong. Your object structure should be your Neo4jClient
object, which should then have a few properties and methods. I am pretty sure that Return
is a property, not a method. So I'm thinking something more like:
$neo = new-object Neo4jClient.GraphClient(new-object Uri("http://localhost:7474/db/data"))
$neo.Cypher.Match = "n"
$neo.Cypher.Return = {param($m) $m}
$q = $neo.Cypher.Results()
There you create your object, you define the Match
filter, define what you want it to return (everything from the looks of it), and then store the results in the $q
variable. I'm pretty sure that should be the same as:
SELECT * FROM Uri("http://localhost:7474/db/data")) WHERE "n"
I also kind of wonder about your Match
criteria, since they seem to be specifying property/value pairs in their examples and you just give one of the two. If that fails I would strongly suggest doing a $neo.Cypher | Get-Member
to see what properties you have, what they're typed as, and what methods you have.
Edit: Ok, so I did look at the link. I also downloaded the libraries, loaded them into PowerShell, and looked at the .Net objects. Return is indeed a method, and the overloads for it are ridiculous. There are 37 overloads for it, the longest of which is almost 750 characters. Most of them are ICypherResultItem
expressions, but the simplest is (string identity)
. Might I suggest simply trying Return("*")
?
回答4:
a million thanks to @PetSerAl who explained the problem well in its essence, and who held my hand through its resolution.
to restate, the problem is that Powershell has no built-in mechanism to call generic methods and the Neo4jClient library's .Return
method overloads are all generic. Specifically, Powershell provides no means of supplying the type of the method.
so there are several ways to solve this:
one solution is to use reflection to make the call, but the task is a bit tricky. thankfully the approach has been tackled by a project available on tech net. see: https://gallery.technet.microsoft.com/scriptcenter/Invoke-Generic-Methods-bf7675af
the second solution suggested in @PetSerAl's reply needed a little help but with @ChrisSkardon's help I got it to work. see here: Constructing a method call
and the third solution (see the Updates in the original post) which relies on creating a c# class with a method where the generic method can be called
I expect to be documenting this solution in the wiki for Neo4jClient and I hope the effort documented here is helpful to others. Thanks again @PetSerAl for all your help
来源:https://stackoverflow.com/questions/30314696/return-overload-fails