1

My team has a table in postgres, let's call it Thing. It looks like this:

Thing Table:

id value type
integer jsonb string

Here are some sample records

id value type
1 "hello" string
2 123 number
3 { "name": "foo" } object
4 [1, 5, 10, 15] array
5 true boolean
6 [2, 3, 7] array

Thing class

public class Thing
{
    public int Id { get; set; }
    public JsonElement Value { get; set; }
    public string Type { get; set; }
}

We were hoping to construct an EF query that must be transpiled into SQL and invoked in Postgres.

Here is the query we were hoping to have work:

var results = context.Things.Where(thing => thing.type == "array" && thing.Value.EnumerateArray().Any(v => v.GetInt32() == 5)));

The result of this query would return record #4.

Unfortunately, the statement can't be transpiled and gives the following error:

(IEnumerable<JsonElement>)p1.Value.EnumerateArray() .Any(v => v.GetInt32() == 5))' could not be translated. Additional information: Translation of method 'System.Text.Json.JsonElement.EnumerateArray' failed.

One would think that EnumerateArray() would be transpiled properly to use the Postgres json_array_elements(json) function, but no such luck.

As previously stated, this query must be invoked on the database (client side filtering is not an option).

We are aware of the EF.Functions.JsonXXX methods, however, none seem to be able to work with json arrays of native elements (it seems that at the very least, the array needs to contain objects with properties).

That said, has anyone had to implement something similar to this without having to write raw sql and be willing to share?

We are really hoping to avoid having to write and maintain raw sql.

Thanks!

3

1 Answer 1

1

It seems like you're trying to query out rows whose JSON array value contains something. Rather than using json_array_elements and then trying to compose over that, you should be able to query directly whether a JSON document contains another JSON document:

SELECT * FROM things WHERE value @> '5';

The @> JSON can be expressed operator in EF LINQ queries via EF.Functions.JsonContains, as per the docs.

Note also that jsonb_typeof (in EF LINQ EF.Functions.JsonTypeof) can be used to query for the type of the json document (array, string...), so you may not need a separate "type" column. If you need that in order to index over it, you can make it a computed column with json_typeof.

Sign up to request clarification or add additional context in comments.

7 Comments

Thanks for the quick response! Unfortunately I receive an exception when using that function. Here is the query: var results = context.Things.Where(thing => thing.type == "array" && EF.Functions.JsonContains(thing.Value, this.ParsedValue))); // parsed value is type INT Here is the result: Npgsql.PostgresException (0x80004005): 42883: operator does not exist: jsonb @> integer at Npgsql.NpgsqlConnector.<ReadMessage>g__ReadMessageLong|194_0(NpgsqlConnector connector, Boolean async, DataRowLoadingMode dataRowLoadingMode, Boolean readingNotifications ...
You may need wrap the integer in a JsonElement to make sure it's sent as a jsonb rather than an int. However, this should work - can you please open an issue with a minimal code sample on github.com/npgsql/efcore.pg?
That was the fix, which makes a lot of sense. So wrapping this.ParsedValue within a JsonElement outside of the EF function worked! Do you think this is an issue? I think it actually makes alot of sense, having to pass in a JsonElement as the second argument, however, it could maybe be documented better? I'll defer to your judgement.
EF.Functions.JsonContains (and the others) accept object-typed parameters since they can work with .NET POCOs - you can pass anything to them. Here there's a type mapping inference issue: EF Core should automatically type the 5 as JSON because of the other side of the operator (a json column).
So yeah, please open an issue for it and I'll look into it (but in the meantime wrapping with JsonElement is totally fine as a workaround).
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.