cancel
Showing results for 
Search instead for 
Did you mean: 

Head's Up! These forums are read-only. All users and content have migrated. Please join us at community.neo4j.com.

C# strange behavior with .First() on Neo4j's IStatementResult on different machines

MPasadu
Node Link

We are using Neo4j.Driver and the code looks like this:

using (var session = driver.Session(AccessMode.Write))
{
var result = [...].WriteTransaction(tx => tx.Run[...]
var value1 = result.First()["value1"].As<string>();
var value2 = result.First()["value2"].As<string>();
[...]
}

On my machine this works fine. On my coworker's machine, result is suddenly empty after the first "First()" call.
Any ideas? Maybe it's a problem with C# in general?

EDIT: We have determined that this problem only occurs when using Visual Studio 2019 16.4.2, the working machine has 16.4.1! After upgrading to 16.4.2 the same error happens!

EDIT: This was with neo4jDriver v.1.7.2

1 ACCEPTED SOLUTION

OK, so this is how the IStatementResult is intended to work.

What's important to note is that whilst var result is an IEnumerable the underlying structure isn't an Array or List<T> but a stream of data.

When you call First() you iterate the IEnumerable and pull the first item from the stream, the next time you call First() you get the first item from the stream - but in this case, because the stream has already yielded it's first item, you're getting the second.

Woah! Hard to read much?!?!

Imagine you have an array [1,2,3,4,5] on a server somewhere, and this is streamed to you.

You call arr.First() - this goes to the server and pulls down 1. At this point, the server now has:

[2,3,4,5]

You then call arr.First() - and the server now sends you 2 as - from it's point of view that is the first item.

I'm not sure if that's clearer or not - let me know if not.

Anyhews - if you want First() to always work - you would need to do ToList(), so for example:

void Main()
{
	var driver = GraphDatabase.Driver("bolt://localhost:7687", AuthTokens.Basic("neo4j", "neo"));
	using (var session = driver.Session(AccessMode.Write))
	{
		Console.WriteLine("Just IEnumerable");
		var result = session.ReadTransaction(s => s.Run("MATCH (m:Movie) RETURN m.title AS title, m.tagline AS tagline"));
		WriteToScreen(result);

		Console.WriteLine();
		Console.WriteLine("Using ToList()");
		var result2 = session.ReadTransaction(s => s.Run("MATCH (m:Movie) RETURN m.title AS title, m.tagline AS tagline"));
		WriteToScreen(result2.ToList());
	}
}

public static void WriteToScreen(IEnumerable<IRecord> result)
{
	var value1 = result.First()["title"].As<string>();
	var value2 = result.First()["tagline"].As<string>();
	value1.Dump();
	value2.Dump();
}

outputs:

Just IEnumerable
The Matrix
Free your mind

Using ToList()
The Matrix
Welcome to the Real World

The ToList version doing what you're hoping for.

Now. The way you probably want to approach this - to avoid pulling everything from the DB in one go, is to do:

using (var session = driver.Session(AccessMode.Write))
{
	var result = [...].WriteTransaction(tx => tx.Run[...]
	var first = result.First();
	var value1 = first["value1"].As<string>();
	var value2 = first["value2"].As<string>();
	[...]
}

Lastly - as to why changing VS made a difference, I can't explain - this should always work like this - are you 100% sure the code wasn't different?

All the best

Chris

View solution in original post

1 REPLY 1

OK, so this is how the IStatementResult is intended to work.

What's important to note is that whilst var result is an IEnumerable the underlying structure isn't an Array or List<T> but a stream of data.

When you call First() you iterate the IEnumerable and pull the first item from the stream, the next time you call First() you get the first item from the stream - but in this case, because the stream has already yielded it's first item, you're getting the second.

Woah! Hard to read much?!?!

Imagine you have an array [1,2,3,4,5] on a server somewhere, and this is streamed to you.

You call arr.First() - this goes to the server and pulls down 1. At this point, the server now has:

[2,3,4,5]

You then call arr.First() - and the server now sends you 2 as - from it's point of view that is the first item.

I'm not sure if that's clearer or not - let me know if not.

Anyhews - if you want First() to always work - you would need to do ToList(), so for example:

void Main()
{
	var driver = GraphDatabase.Driver("bolt://localhost:7687", AuthTokens.Basic("neo4j", "neo"));
	using (var session = driver.Session(AccessMode.Write))
	{
		Console.WriteLine("Just IEnumerable");
		var result = session.ReadTransaction(s => s.Run("MATCH (m:Movie) RETURN m.title AS title, m.tagline AS tagline"));
		WriteToScreen(result);

		Console.WriteLine();
		Console.WriteLine("Using ToList()");
		var result2 = session.ReadTransaction(s => s.Run("MATCH (m:Movie) RETURN m.title AS title, m.tagline AS tagline"));
		WriteToScreen(result2.ToList());
	}
}

public static void WriteToScreen(IEnumerable<IRecord> result)
{
	var value1 = result.First()["title"].As<string>();
	var value2 = result.First()["tagline"].As<string>();
	value1.Dump();
	value2.Dump();
}

outputs:

Just IEnumerable
The Matrix
Free your mind

Using ToList()
The Matrix
Welcome to the Real World

The ToList version doing what you're hoping for.

Now. The way you probably want to approach this - to avoid pulling everything from the DB in one go, is to do:

using (var session = driver.Session(AccessMode.Write))
{
	var result = [...].WriteTransaction(tx => tx.Run[...]
	var first = result.First();
	var value1 = first["value1"].As<string>();
	var value2 = first["value2"].As<string>();
	[...]
}

Lastly - as to why changing VS made a difference, I can't explain - this should always work like this - are you 100% sure the code wasn't different?

All the best

Chris