0

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!

ahanusa
  • 969
  • 1
  • 9
  • 11

1 Answers1

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.

Shay Rojansky
  • 15,357
  • 2
  • 40
  • 69
  • 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.g__ReadMessageLong|194_0(NpgsqlConnector connector, Boolean async, DataRowLoadingMode dataRowLoadingMode, Boolean readingNotifications ... – ahanusa May 11 '22 at 17:53
  • 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 https://github.com/npgsql/efcore.pg? – Shay Rojansky May 11 '22 at 18:24
  • 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. – ahanusa May 11 '22 at 18:36
  • 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). – Shay Rojansky May 11 '22 at 18:58
  • 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). – Shay Rojansky May 11 '22 at 18:59
  • Issue on github: https://github.com/npgsql/efcore.pg/issues/2364 – Shay Rojansky May 12 '22 at 08:12
  • @ShayRojansky I'm facing similar problem, could you specify what you mean by wrapping the integer in a JsonElement? [My issue](https://stackoverflow.com/questions/74709811/filter-integer-array-of-jsonb-column-in-postgres-using-ef-core-and-npgsql) – Working Pickle Dec 06 '22 at 23:51