Eclipselink JPA MappingSelectionCriteria customization - jpa

According to EclipseLink/Examples/JPA/MappingSelectionCriteria I can make some filtering on OneToOne or OneToMany relationships. To do that I have to implement DescriptorCustomizer.
My question is: Can I do some conditional filtering with this technique and how? I mean, in the example of mentioned link we can write something like this
public class ConfigureBsFilter implements DescriptorCustomizer {
public void customize(ClassDescriptor descriptor) throws Exception {
OneToManyMapping mapping = (OneToManyMapping) descriptor
.getMappingForAttributeName("bs");
ExpressionBuilder eb = new ExpressionBuilder(mapping
.getReferenceClass());
Expression fkExp = eb.getField("A_ID").equal(eb.getParameter("A_ID"));
Expression activeExp = eb.get("active").equal(true);
mapping.setSelectionCriteria(fkExp.and(activeExp));
}
}
But what if in the expression
Expression activeExp = eb.get("active").equal(true);
the "active" is not always true but have to be set at runtime by some parameter. Can I do that and how?

Looking at wiki.eclipse.org/Using_Advanced_Query_API_(ELUG) you could use a query redirector on the ForeignReferenceMapping#getSelectionQuery() so that your query redirector can dynamically clone the query and add filters as required. Passing parameters to the redirector will need to be creative though, such as storing them on the thread context or in the session's properties map.

Related

Spring #QuerydslPredicate and QuerydslBinderCustomizer: is it possible to infuse default criteria into predicate generated from request params?

I am using Spring Data JPA and QueryDsl (v.4.2.2), Java 8. I can explicitly construct search predicates and pass them to the repository methods. However, I like the idea of using the #QuerydslPredicate annotation on a web/REST controller's method argument when the queried entities have more than a few properties, and I want the flexibility of filtering the search by any of them. So, something like this, generally, works very well:
#GetMapping("/accounts/summaries")
public PageDto<AccountSummaryDto> getAccountSummaries(#QuerydslPredicate(root = AccountSummary.class) Predicate accountSearchPredicate,
#RequestParam(name = "pageIndex", defaultValue = "0") int pageIndex,
#RequestParam(name = "pageSize", defaultValue = "25") int pageSize,
#RequestParam(name = "sortBy", defaultValue = "id") String sortBy,
#RequestParam(name = "sortOrder", defaultValue = "desc") String sortOrder) {
// delegating to web-agnostic service that:
// - creates Pageable pageRequest,
// - calls accountSummaryRepository.findAll(predicate, pageRequest),
// - constructs custom PageDto wrapper, etc.
return accountService.retrieveAccountSummaries(accountSearchPredicate, pageIndex, pageSize, sortBy, sortOrder);
}
My Spring Data JPA repository interface looks similar to this:
public interface AccountSummarySearchRepository
extends JpaRepository<AccountSummary, Integer>, QuerydslPredicateExecutor<AccountSummary>, QuerydslBinderCustomizer<QAccountSummary > {
#Override
default void customize(QuerydslBindings bindings, QAccountSummary acctSummary) {
bindings.bind(acctSummary.customer.firstName).first((path, value) -> path.isNull().or(path.startsWithIgnoreCase(value))) ;
bindings.bind(acctSummary.customer.lastName).first((path, value) -> path.isNull().or(path.startsWithIgnoreCase(value))) ;
// etc.
// default binding for String properties to be case insensitive "contains" match
bindings.bind(String.class).first(
(StringPath path, String value) -> path.isNull().or(path.containsIgnoreCase(value)));
}
My question:
The bindings in the customize method are set using the entity field
paths and the values of the request parameters that match those
paths. If the parameter is not specified, is there a way to bind the
path to some constant value or a value obtained dynamically?
For example, I want to always ONLY retrieve the entities where property deleted is set to false - without forcing the client to pass that as a query parameter? Similarly, I may want to set other default lookup values dynamically for each query. For example, I may want to "retrieve only those accounts where assignedTo == [current user ID available on a ThreadLocal]...
The following will not work
bindings.bind(acctSummary.deleted).first((path, value) -> path.eq(false));
because it, obviously, expects the first occurrence of the path/value pair for deleted=... in the Predicate (mapped from the incoming request params via the #QuerydslPredicate annotation. I don't want to pass that as a parameter because the requester does not even need to know about the existence of such field.
Is there a simple way to infuse the Predicate instance that is auto-populated via the #QuerydslPredicate annotation with any additional implicit/default criteria that are not explicitly passed in the web request? Could this be done in the customize method? I suppose, one (very ugly) way would be to intercept the HTTP request in a filter - before it is processed by the Spring-QueryDsl framework - and replace it with a new request with added parameters? That would be a horrible solution, and I feel there has to be a better way to do it via some hook/capability provided by the framework itself.
Unfortunately, there seem to be no comprehensive documentation for Spring QueryDsl support - other than some very simplistic examples.
Thanks for your help!
Answering my own question... I was hoping to find a hook in the framework where I could add the code to enhance the auto-generated predicate with criteria common for all my queries - before it arrives in the controller method, but wasn’t able to figure that out. Overriding QuerydslPredicateArgumentResolver doesn't seem a good or necessary option. And, quite frankly, I've come to the conclusion that this wasn't such a great idea to begin with. It seems that any modifications to the search criteria should be done in a more obvious way - in the business tier. So I decided to simply update the predicate in the service method:
public PageDto<AccountSummaryDto> retrieveByPredicate(Predicate predicate, int pageIndex, int pageSize, String sortBy, String sortOrder) {
Pageable pageRequest = PageRequest.of(pageIndex, pageSize, Sort.Direction.fromString(sortOrder), sortBy);
QAccountSummary accountSummary = QAccountSummary.accountSummary; //QueryDsl auto-generated query type for AccountSummary (path root)
// construct new enhanced search predicate w/added criteria common for all queries
// using original predicate generated by framework from request params as base
BooleanBuilder updatedPredicate = new BooleanBuilder(predicate)
.and(accountSummary.somethingNested.id.eq(SomeThreadContext.getSomethingId()))
.and(accountSummary.deleted.eq(false))
.and(accountSummary.someProperty.eq("xyz"));
Page<accountSummary> page = summarySearchRepository.findAll(updatedPredicate, pageRequest);
return toAccountSummaryPageDto(page); // custom method that converts results to page DTO w/entity dots and page stats
}
The construction of the updated predicate may be extracted into a separate private method on the service should it be desirable to use it in multiple search methods and/or if more logic is required to dynamically generate additional search criteria.

Trying to add Support For Index Usage on EFCore Spanner Provider

I'm writing a driver for EF Core for Spanner - In basic level it works and I can write Read and Write Queries that get's translated to Spanner SQL , executed and return results etc..
Now I'm trying to add Support For Read Query with Secondary Index.
Ultimately I'm trying to generate this SQL Query:
SELECT * FROM PostTags#{ FORCE_INDEX = PostTagsByTagId } WHERE TagId = 1
From This Linq:
var postTag = ctx.PostTags.WithIndex("PostTagsByTagId").Where(x => x.TagId == 1).FirstOrDefault();
I've added extension method as follow:
public static class SpannerIndexSupport
{
public static IQueryable<TSource> WithIndex<TSource>(this IQueryable<TSource> query, string indexName)
{
var methodDefinition = typeof(SpannerIndexSupport).GetTypeInfo().GetMethods().Single(m => m.Name == "WithIndex");
var method = methodDefinition.MakeGenericMethod(typeof(TSource));
var args = new[] { query.Expression, Expression.Constant(indexName) };
var expression = Expression.Call(null, method, args);
return query.Provider.CreateQuery<TSource>(expression);
}
}
And tried to write IAsyncQueryProvider to support it but couldn't find a way to make it work.
Any ideas Anyone?
In the official Spanner EFCore library (https://github.com/GoogleCloudPlatform/google-cloud-dotnet/tree/master/apis/Google.Cloud.EntityFrameworkCore.Spanner/Google.Cloud.EntityFrameworkCore.Spanner), I would start by overriding VisitTable(TableExpression tableExpression) in SpannerQuerySqlGenerator:
https://github.com/GoogleCloudPlatform/google-cloud-dotnet/tree/master/apis/Google.Cloud.EntityFrameworkCore.Spanner/Google.Cloud.EntityFrameworkCore.Spanner/Query/Sql/Internal/SpannerQuerySqlGenerator.cs
This will allow you to get a proof of concept going because you can directly influence the generated SQL text there.
Once that works, then you will want to make it proper.
I suppose there might be a few ways to make this work. The simplest might be to have some custom no-op method marker in the Linq expression tree and then register an IMethodCallTranslator to convert it either to a custom spanner specific Expression (whose Accept calls into SqlGenerator to generate the proper Sql) or possibly creating a SqlTranslatingExpressionVisitor to switch out the table expression to a custom one that allows the FORCE_INDEX.
Sorry I couldn't help more.
This is now supported in the official Entity Framework provider for Google Cloud Spanner. You can add this by adding a tag to the query like this:
var singersOrderedByFullName = context.Singers
// This will add the following comment to the generated query:
// `-- Use hint: force_index FullName`
// This comment will be picked up by the interceptor and an index
// hint will be added to the query that is executed.
.TagWith("Use hint: force_index FullName")
.OrderBy(s => s.FullName)
.AsAsyncEnumerable();
A full example can be found here: https://github.com/googleapis/dotnet-spanner-entity-framework/blob/main/Google.Cloud.EntityFrameworkCore.Spanner.Samples/Snippets/QueryHintSample.cs

JPA Criteria api using IN expression with join

i have entity Request that have #ManyToMany Set<Region> regions, and Region entity have field region of type RegionEnum of enum type with constants of regions.
I need to create criteria to get requests, where its regions are in collection of RegionEnum;
In my choice:
List<RegionEnum> regs=...; // from method parameter
CriteriaBuilder cb=em.getCriteriaBuilder();
CriteriaQuery<Request> cq=cb.createQuery(Request.class);
Root<Request> from=cq.from(Request.class);
cq.where(cb.isTrue(from.join("regions").get("region").in(regs)));
return em.createQuery(cq).getResultList();
I have an java.lang.IllegalArgumentException: PREDICATE_PASSED_TO_EVALUATION (There is no English translation for this message.)
enum:
public enum RegionEnum {
CENTRAL("Центральный"),
SOUTH("Южный"),
NWEST("Северо-Западный"),
FEAST("Дальневосточный"),
SIB("Сибирский"),
URFO("Уральский"),
VOLGA("Волжский"),
NCAU("Северо-Кавказский");
private final String value;
private Region(String value) {
this.value=value;
}
public String value() {
return this.value;
}
}
is my criteria right and problem with enum? or criteria is bad?
I have faced with the same exception today so I tried to solve the problem.
Let's assume we have the following instances already initialized (as in your example):
CriteriaBuilder cb;
List<String> values;
Root<Entity> r; // where Entity has String name attribute
Now we can create a Predicate instance this way which is working using EclipseLink 2.6.2 library:
Predicate good = r.get("name").in(values);
But if we try to wrap the predicate into the cb.isTrue() method (as you did it) which require an "Expression<Boolean>" parameter like this:
Predicate wrong = cb.isTrue(r.get("name").in(values));
the mentioned exception PREDICATE_PASSED_TO_EVALUATION will be raised as you have received it even the expression has right syntax.
I think because Expression is an Ancestor of Predicate class but this just an idea. At least the exception message says something like this.
So just remove the cb.isTrue() wrapping and probably it will work for you as it works for me.

Use GuidRepresentation.Standard with MongoDB

I am implementing a custom IBsonSerializer with the official MongoDB driver (C#). I am in the situation where I must serialize and deserialize a Guid.
If I implement the Serialize method as follow, it works:
public void Serialize(BsonWriter bsonWriter, Type nominalType, object value, IBsonSerializationOptions options)
{
BsonBinaryData data = new BsonBinaryData(value, GuidRepresentation.CSharpLegacy);
bsonWriter.WriteBinaryData(data);
}
However I don't want the Guid representation to be CSharpLegacy, I want to use the standard representation. But if I change the Guid representation in that code, I get the following error:
MongoDB.Bson.BsonSerializationException: The GuidRepresentation for the writer is CSharpLegacy, which requires the subType argument to be UuidLegacy, not UuidStandard.
How do I serialize a Guid value using the standard representation?
Old question but in case someone finds it on google like I did...
Do this once:
BsonDefaults.GuidRepresentation = GuidRepresentation.Standard;
For example, in a Web Application/Web API, your Global.asax.cs file is best place to add it once
public class WebApiApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
BsonDefaults.GuidRepresentation = GuidRepresentation.Standard;
//Other code...below
}
}
If you don't want to modify the global setting BsonDefaults.GuidRepresentation (and you shouldn't, because modifying globals is a bad pattern), you can specify the setting when you create your collection:
IMongoDatabase db = ???;
string collectionName = ???;
var collectionSettings = new MongoCollectionSettings {
GuidRepresentation = GuidRepresentation.Standard
};
var collection = db.GetCollection<BsonDocument>(collectionName, collectionSettings);
Then any GUIDs written to the collection will be in the standard format.
Note that when you read records from the database, you will get a System.FormatException if the GUID format in the database is different from the format in your collection settings.
It looks like what's happening is when you are not explicitly passing the GuidRepresentation to BsonBinaryData constructor, it defaults to passing GuidRepresentation.Unspecified and that ultimately maps to GuidRepresentation.Legacy (see this line in the source)
So you need to explicitly pass the guidRepresentation as a third argument to BsonBinaryData set to GuidRepresentation.Standard.
edit: As was later pointed out, you can set BsonDefaults.GuidRepresentation = GuidRepresentation.Standard if that's what you always want to use.

Limiting EF result set permanently by overriding ObjectQuery ESQL

Does anyone have any idea how to limit result set of EntityFramework permanently? I'm speaking about something like this Conditional Mapping. This is exactly what I want to achieve with one exception: I want to do this programmatically. That's because condition value will be passed to EF only on context creation. Beside I don't want this column to disappear from mapping.
I know how to achieve this with EF2.0 and reflection. I was using CreateQuery() method to generate my own ObjectQuery. CreateQuery() allows to inject my own ESQL query with additional condition e.g. WHERE TABLE.ClientID == value.
Problem with EF40 is that there is no more ObjectQuery but only ObjectSet and CreateQuery() is not used. I have no idea how to inject my own ESQL query.
The reason why I want to limit result sets is that I want to separate clients data from each other. This separation should be done automatically inside context so that programmers will not have to add condition .Where(x => x.ClientID == 5) to each individual query.
Maybe my approach is completely bad — but I don't know any alternative.
You don't need reflection for this. You can simply use class inherited from ObjectContext or create custom implementation of UnitOfWork and Repositories which will wrap this functionality in better way (upper layer has access only to UnitOfWork and Repositories which do not expose EF context).
Simple example of object context:
public class CustomContext : ObjectContext
{
private ObjectSet<MyObject> _myObjectsSet;
private int _clientId;
public CustomContext(string connectionString, int clientId)
: base(connectionString)
{
_myObjectSet = CreateObjectSet<MyObject>();
_clientId = clientId;
}
public IQueryable<MyObject> MyObjectQuery
{
get
{
return _myObjectsSet.Where(o => o.ClientId == _clientId);
}
}
}