Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BUG: Predicate query fails when searching for a text word that is the same as an Entity name. #73

Open
EchoFoxxtrot opened this issue Feb 13, 2023 · 10 comments

Comments

@EchoFoxxtrot
Copy link

Consider the following scenario:

I have an entity type called Area that corresponds to an Area table in my database and I have another entity type in the same entity metadata set that contains a Title field, defined as a string, that I want to query against by creating a Predicate something like the following:

let searchFilter = 'area' // simulating typed input from a search criteria entered by a user through the UI
let myNewPredicate = new Predicate('Title', FilterQueryOp.startsWith, searchFilter);

I then use my predicate with the EntityQuery.where() method to query my other table which DOES NOT have a field or navigation property called "Area".

The result is that I will receive an error from the EntityManager that states:

Error: unable to locate property: Area on entityType:
<MyOtherEntityClass>#<My.DataAccessModel.Namespace>

This is true when you write any Predicate using any valid FilterQueryOp operator to query for any string that is an exact caseless match of the name of any Entity Type that is registered from a server Metadata call on an Entity Framework Core dbContext.

My current workaround to this is to remove the last character of a "startsWith" predicate or the first character of an "endsWith" predicate, but it gets dicey when I need to use an "equals" predicate, so it would be nice if this bug could be fixed.

@steveschmitt
Copy link
Member

Not a bug.

In this case, you need to use some additional syntax to indicate whether you are querying the literal value or the property.

// query where the Title property starts with the Area property
let propertyPredicate = new Predicate('Title', FilterQueryOp.startsWith, { value: 'area', isProperty: true });

// query where the Title property starts with the literal 'area'
let valuePredicate = new Predicate('Title', FilterQueryOp.startsWith, { value: 'area', isProperty: false });

This is in the Breeze documentation, but it's buried pretty deep.

@Will-at-FreedomDev
Copy link

I attempted to fix this issue in my own application by using isProperty: false, but now my queries that are not string based queries are all failing. I also tried isLiteral: true but that had the same effect.

@steveschmitt
Copy link
Member

steveschmitt commented Mar 22, 2023

Can you give me an example of a query that is failing, and what the error is?

@Will-at-FreedomDev
Copy link

Sorry, I've already made a workaround: to test the value I wanted to query by and only apply isProperty: false when the value is a string type and not a date formatted string. This fixed the data type errors we were seeing (could not filter Int32 by Double, etc.) For background, all the data tables uses a single architecture to send these queries to our back-end. A user happened to filter a string column by a value that happened to also be a property name on the same entity. This causes issues when both are strings and the behaviour is not expected. The other and worse thing that happened was if you searched by a property name that was a different data type completely (string could not be filtered by Int32 is the sort of error we saw).

I was hoping isProperty: false simply disabled making the query search by another property in the filter, but it had other unwanted side-effects aforementioned.

@Will-at-FreedomDev
Copy link

Sorry I don't have more expertise. I've just taken over this project and never heard of Breeze :) In code, my workaround was to do this:

// don't accidentally attempt to query by another property name because it's ambiguous behavior
            // if the value is a string and not a date formatted string, then set isProperty: false
            // we can't always set isProperty: false because the query sends the wrong data type to the server for some reason.
            let queryValue = typeof value !== "string" || /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d*)?Z?$/.test(value)  ? value : { value, isProperty: false };
...
let result = new Predicate(_this.fixFieldName(criteria[0]), translateBinaryOperator(operator), queryValue);

@steveschmitt
Copy link
Member

Please help me, because I'm trying to reproduce the scenario in the Breeze tests.

Let's say I have an entity with three properties, stringProp, intProp, and dateProp, which are type string, Int32, and DateTime respectively. What do I do to get the "could not filter Int32 by Double" and "string could not be filtered by Int32" errors?

Also, what version of Breeze are you using on the client and the server?

@Will-at-FreedomDev
Copy link

The server is using Breeze.AspNetCore.NetCore (6.0.4) and the client is using breeze-client: ^2.1.2

In my scenario this Predicate caused the error I mentioned:

new Predicate("intProp", "eq", { value: 1, isProperty: false }); // it was passing Data Type: Double to the server instead of `Int32` or whatever it should have been passign

This worked fine

new Predicate("intProp", "eq", 1);

Something about setting that flag caused the query to be built incorrectly. As I mentioned, I have little to no experience with Breeze, just enough to make a workaround for the filtering by property name issue the OP mentioned (I understand there are circumstances where it would be appropriate to have that behavior).

If you can't recreate it in your tests, I'm sure something in the architecture of this project is to blame. Thank you for being a good steward of this project.

@Will-at-FreedomDev
Copy link

The "string couldn't be filtered by Int32" error was caused by this predicate:

new Predicate("stringProp", "eq", "intProp");

This fixed that issue (because I didn't want to filter by the actual property value, I wanted to filter by the literal string....

new Predicate("stringProp", "eq", { value: "intProp", isProperty: false });

I was hoping I could just always set isProperty: false but as mentioned, that had side-effects in my project

@steveschmitt
Copy link
Member

Thanks for the help. I'll see if I can fix it. I'm glad you found the workaround for you.

@steveschmitt
Copy link
Member

I could not reproduce the problem.

The data type of a property -- String, Int32, Double, etc. -- comes from the Breeze metadata, which is consumed by the Breeze EntityManager and usually generated based on the server classes. Your metadata may need updating or correcting.

You can also specify the data type explicitly in the predicate:

new Predicate("intProp", "eq", { value: 1, isProperty: false, dataType: "Int32" });

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants