How to use PostgreSQL xmlexists() function in Hibernate HQL query? - postgresql

I want to create HQL query with xmlexists() function but I get an error.
My code:
Query query = session.createQuery("From XMLTable AS tb WHERE xmlexists('//food[#id = \"1\"]' PASSING BY REF tb.xmlData)");
List list = query.list();
list.forEach(obj -> {
printSth((XMLTable) obj);
});
ERROR:
ERROR: line 1:77: unexpected token: PASSING
I also tried in pgAdmin 4 and there everything works fine. I guess that this is a syntax problem in HQL.

The problem seems to be related to the unorthodox syntax of the function you use.
An obvious solution for the problem would be to use session.createNativeQuery() instead of a JPQL query. You can then (and should) use plain SQL as the method argument.
Another solution might be to extend the dialect class Hibernate provides for PostgreSQL and register a new function implementation for xmlexists(). I am not sure if that would work though.

Related

Working with Entity Framework client vs server evaluation changes

I have the following exceprt of my query. I am using ASP.NET Core 3.1 project with EF Core.
I read that the server vs client has changed, so how I used to perform the WHERE part in Core 2.1 (using variables from elsewhere in my code) doesn't seem to work anymore.
So as below I have changed (as per something I read) to use ToList() in each part, but now is it not hitting the database more (in my Core 2.1 I would of only had the ToList on the final part as per the code comment below).
So now for Core 3.1 I need to have a dynamic where in the initial "// Load data" part - how do I do a dynamic Where in the initial part, or is there a way, now the server vs client changes are in EF Core to work around that (note it is the final "// Search" part that fails in EF under Core 3.1 (prior to adding the ToList 's)
public List<KBEntryListVM> lstKBEntry;
// Load data
var q = await (from _k in _context.KBEntry
join _kc in _context.KBCategory on _k.CategoryId equals _kc.Id
into _kc2
from _kc3 in _kc2.DefaultIfEmpty()
select new KBEntryListVM()
{
Id = _k.Id,
DateCreated = DateTime.Parse(_k.DateCreated.ToString()),
CategoryId = _k.CategoryId,
CategoryTitle = _kc3.Title.ToString().Trim(),
Text = _k.Text.ToString().Trim(),
Title = _k.Title.ToString().Trim()
}).ToListAsync();
// KBCategory
if (!string.IsNullOrEmpty(c) && Guid.TryParse(c.ToString().Trim(), out var newGuid))
{
q = q.Where(w => w.CategoryId == Guid.Parse($"{c.ToString()}")).ToList();
}
// Search
if (!string.IsNullOrEmpty(s))
{
q = q.Where(w => w.Title.ToLower().Contains($"{s.ToLower()}") || w.CategoryTitle.ToLower().Contains($"{s.ToLower()}") || w.Text.ToLower().Contains($"{s.ToLower()}")).ToList();
}
lstKBEntry = q; //.ToList(); this would of been the only place in Core 2.1 I would of had ToList()
Arthur
So as below I have changed (as per something I read) to use ToList() in each part
EF Core 3.x+ client evaluation exception message suggests to either (1)
rewrite the query in a form that can be translated
or (2)
switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync()
So you are taking the option (2) which is easier, but you should try utilizing the option (1) which is harder, but better from performance perspective and the main reason implicit client evaluation have been removed by EFC 3.0. Option (2) should be your last resort only in case there is no way to apply option (1).
The exception message also contains the failed expression. Unfortunately it is not the exact part, but the whole expression (the whole Where predicate for instance), so you need to analyze it, find the failing part(s) and try to replace them with translatable constructs.
One of the general rules for simple data expressions is to avoid explicit conversions (ToString(), Parse). Store dates and numbers in database as such rather than strings, or utilize value conversions when using old existing database and aren't allowed to change it.
In this particular query, the unsupported (non translatable) construct most likely are ToString() calls of string type properties (e.g. Title, Text). EF Core still supports implicit client evaluation of the final Select, so you won't notice it if there is no Where (or other) clause after that referencing such expressions. But as being said at the beginning, you should avoid them regardless - query the raw data and let the usages (UI) do the desired formatting.
Anyway, I can't tell exactly because you haven't show your model, but removing ToString() should make the query translatable, hence no need of intermediate ToList() or similar client materialization:
CategoryTitle = _kc3.Title.Trim(),
Text = _k.Text.Trim(),
Title = _k.Title.Trim()
You should probably also replace
DateCreated = DateTime.Parse(_k.DateCreated.ToString())
with just
DateCreated = _k.DateCreated
because it seems that DateCreated is already DateTime, so the double conversion through string doesn't make sense and would cause similar troubles. And even if the database type is string, still remove Parse / ToString and setup value converter which does that.

Problem calling npgsql stored function of a postgresql 10.10 database with postgis from EF Core 3.0

I have a function with a input json parameter in text format, I do the casting to json inside itself. This function returns another Json in text format. When I call this function from PGAdmin 4 using another function test, it works fine, but if I call that function from EF Core 3.0 I get this error:
Error Name: invalid input syntax for type json
Error State: 22P02
Error Context: JSON data, line 1: {"ErrorMsg":"Error Name: type "geometry...
PL/pgSQL function miguel.searchjson(text) line 87 at assignment
SQL statement "SELECT * from miguel.searchJSON(param)"
PL/pgSQL function miguel.test_searchjson() line 19 at SQL statement
With other functions without Json that returns a text, I don't have problems in the execution.
I am calling to my function that way:
var res = _context.JSResultSearch.FromSqlRaw("SELECT miguel.test_searchjson() as jsresult");
Frankly, I'm already desperate with this topic. I will be grateful with any help.
It seems like you're trying to call a function which returns a simple text, and expect EF Core to read it out for you (although this isn't clear, since you're calling FromSqlRaw on JSResultSearch, and we don't know what that is). EF Core doesn't support loading simple, non-entity values for you - see https://github.com/aspnet/EntityFrameworkCore/issues/11624.
However, EF Core wouldn't have much value here beyond simply dropping down to ADO.NET or using Dapper.

Replacement for deprecated PostgresDataType.JSON?

I'm using JOOQ with PostgreSQL, and trying to implement a query like this:
INSERT INTO dest_table (id,name,custom_data)
SELECT key as id,
nameproperty as name,
CONCAT('{"propertyA": "',property_a,'", "propertyB": "',property_b,'","propertyC": "',property_c,'"}')::json as custom_data
FROM source_table
The concatenation/JSON bit is what I'm here to ask about. I actually have managed to get it working, but only by using this (Kotlin):
val concatBits = mutableListOf<Field<Any>>()
... build up various bits of the concatenation ...
val concatField = concat(*(concatBits.toTypedArray())).cast(PostgresDataType.JSON)
It concerns me that PostgresDataType is deprecated. The documentation says I should use SQLDataType instead, but it has no JSON value.
What's the recommended way to do this?
EDIT: a bit more information ...
I'm building the query like this:
val innerSelectFields = listOf(
field("key").`as`(DEST_TABLE.ID),
field("nameproperty").`as`(DEST_TABLE.NAME),
concatField.`as`(DEST_TABLE.CUSTOM_DATA)
)
val innerSelect = dslContext
.select(innerSelectFields)
.from(table("source_table"))
val insertInto = dslContext
.insertInto(DEST_TABLE)
.select(innerSelect)
The initial query I posted is slightly misleading, as the resulting SQL from this code doesn't have the
(id,name,custom_data) part.
Also, in case it matters, "source_table" is a temporary table, created during runtime, so there are no autogenerated classes for it.
jOOQ currently doesn't support the JSON data type out of the box. The main reason is that it is unclear what Java type to bind a JSON data structure to, as the JDK doesn't have such a standard type, and jOOQ will not prefer one third party library over the other.
The currently recommended approach is to create your own custom data type binding for your preferred third party JSON library:
https://www.jooq.org/doc/latest/manual/code-generation/custom-data-type-bindings
In that case, you will no longer need to explicitly cast your bind variable to some JSON type, because your binding will take care of that transparently.

Dynamic LINQ and Kendo MVC wrappers

I'm using System.Linq.Dynamic.Core and EntityFramework library to generate a query like this:
var q = context.Items.Select("new(SomeProperty)")
The q is of type IQueryable at the moment. I can successfully apply OrderBy, Take, Skip and then execute the query.
But if I try to use the extension method from Kendo, things go south:
var results = q.ToDataSourceResult(new DataSourceRequest())
I get an Exception:
NotSupportedException
Unknown LINQ expression of type 'Dynamic'.
I know that the ToDataSourceResult method adds the OrderBy, Take and Skip to the IQueryable to do the paging so that's the most likely source of the problem (it does create a dynamic expression if the object is object or IDynamicMetaObjectProvider)
Any idea how to workaround this?
You could also try this package https://github.com/StefH/KendoGridBinderEx which is created for Kendo Grid queries and uses System.Linq.Dynamic.Core

Imported function in Entity Framework Where clause?

Is it possible to use a function import in a where clause in entity framework? I have tried the following, but I get a rather cryptic exception to which I cannot find any information about:
var q = MyContext.MyEntities.Where("MyContext.MyFunction(it.ID)")
(The function is set to return a Boolean)
System.Data.EntitySqlException: 'MyContext.MyFunction' cannot be resolved into a valid type constructor or function., near WHERE predicate, line 6, column 21..
Regards
Lee
The query you are trying to write is composing a call to a FunctionImport with a query over an EntitySet.
But because FunctionImports are wrappers around StoredProcedures, which are non-composable, this just won't work.
In order for something like this to work, theoretically the function would need to be a wrapper around something composable like a TVF (Table Value Function). But unfortunately TVFs aren't supported in the Entity Framework today.
Alex
I don't think you want to pass this expression as a string. You want a proper lambda expression like:
MyContext.MyEntities.Where( entity => MyContext.MyFunction(entity.ID ) );