Reputation: 42229
I am writing a cypher query where I want to find all users who were in a particular role between the given date ranges. It seems that null
queries are a bit tricky.
Consider the following code:
public async Task<Result<IEnumerable<UserNode>>> FindUsersByRoleNameAsync(
string roleName,
DateOnly? from = default,
DateOnly? until = default,
CancellationToken token = default)
{
from ??= DateOnly.MinValue;
until ??= DateOnly.MaxValue;
return await client.Cypher
.Match("(user:User)-[edge:has_role]-(role:Role)")
.Where((RoleNode role) => role.Name == roleName)
.AndWhere((HasRole edge) => edge.From >= from)
.AndWhere((HasRole edge) => edge.Until <= until) // this line is problematic!
.ToNodesAsync<UserNode>("user", UnitOfWork, token) // Custom deserialization...ignore.
.ConfigureAwait(false);
}
A breakdown explanation...
DateOnly?
parameters from
and until
which can be omitted from the query.from
is omitted, it's set to DateOnly.MinValue
(realistically, nobody will have a role before 01/01/0001)until
is omitted, it's set to DateOnly.MaxValue
(realistically, nobody will have a role after 31/12/9999)Some context might help here as to why null
seems to be the issue;
HasRole.From
is DateOnly
.HasRole.Until
is DateOnly?
where null
denotes that the role still applies.If I omit this line from the query...
.AndWhere((HasRole edge) => edge.Until <= until) // this line is problematic!
...then it works for queries where from
and until
are omitted, and queries where from
is not omitted.
The bit that doesn't seem to work is when trying to compare edge.Until
(which might be null
in the database) with until
(which will either be a specified value, or DateOnly.MaxValue
if it's omitted).
Removing the line above, 2/3 query tests pass. Adding that line back in causes 0/3 query tests to pass.
What am I doing wrong?
Upvotes: 0
Views: 37
Reputation: 6270
It's worth looking at the actual query that is generated from your code, which will be something like:
MATCH (user: User)-[edge:has_role]-(role:Role)
WHERE (role.Name = "Blah")
AND (edge.From >= "")
AND (edge.Until <= "9999-12-31")
So it is always expecting Until
to exist. You would need to change the query to something like:
MATCH (user:User)-[edge:has_role]-(role:Role)
WHERE (role.Name = "Blah")
AND (edge. From >= "")
AND (edge. Until IS NULL)
OR (edge. Until <= "9999-12-31")
The client is doing exactly what you ask of it, to find any HasRole
edge with an Until
property less than DateOnly.MaxValue
, and writes the corresponding Cypher.
Probably something like this is what you want:
public async Task<Result<IEnumerable<UserNode>>> FindUsersByRoleNameAsync(
string roleName,
DateOnly? from = default,
DateOnly? until = default,
CancellationToken token = default)
{
from ??= DateOnly.MinValue;
until ??= DateOnly.MaxValue;
return await client.Cypher
.Match("(user:User)-[edge:has_role]-(role:Role)")
.Where((RoleNode role) => role.Name == roleName)
.AndWhere((HasRole edge) => edge.From >= from)
.AndWhere((HasRole edge) => edge.Until <= until) // this line is problematic!
.OrWhere($"edge.{nameof(HasRole.Until)} IS NULL") //Add here
.ToNodesAsync<UserNode>("user", UnitOfWork, token) // Custom deserialization...ignore.
.ConfigureAwait(false);
}
Upvotes: 1