nhibernate-core
nhibernate-core copied to clipboard
NH-3565 - StartsWith / EndsWith / Contains don't use correct AnsiString type
Nicolás Sabena created an issue — :
If I have a mapping with a property of type
AnsiString:<class Name="Person"... > <property name="LastName" type="AnsiString">and I query:
sesión.Query<Person>.Where(x => x.Name.StartsWith("Something"));NH translates this into
SELECT ... FROM Person WHERE name like (@p0 + '%')but @p0 is defined as
string(nvarchar)instead ofAnsiString(varchar), which gives terrible performance in the DB Engine as the types don't match.Equality and other operators mantain correct type. Doing this query with QueryOver also allows correct type usage.
I see in code that the
StartsWithGenerator, as well as theEndsWithGeneratorandContainsGenerator, usesConcat, that seems to be the cause of the problem, but I'm a bit lost here...
Alexander Zaytsev added a comment — :
Could you please provide a test case?
Nicolás Sabena added a comment — :
Run the test called 'StartsWithUsesRightParameterType' and check the generated SQL output. You'll see that the first query (equality) defines the parameter as AnsiString, while the second one ("StartsWith") defines the parameter as String.
Thanks a lot, Nicolas
paul added a comment — :
Any news/workaround on that bug? Querying a big table (>3Millons) using linq contains is taking about 40seconds (because nvarchar(4000)). Changing manually to nvarchar(100) and the query takes 3seconds.
Frédéric Delaporte added a comment — :
I do not think any progress has been made on that subject. PR welcome of course.
A workaround could be to use
NHibernate.Linq.SqlMethods.Likeextension method instead. Please drop a note if you test it.sesión.Query<Person>.Where(x => x.Name.Like("Something%"));Maybe using
MappedAsextension method on the string parameter could help too, within the string methods or theLikeextension.sesión.Query<Person>.Where(x => x.Name.StartsWith("Something".MappedAs(NHibernateUtil.AnsiString)));
paul added a comment — :
Hey Frederic,
I tried with the MappedAs extension and instead of a nvarchar(4000), I got a varchar(8000). Its a good start but how can I set length now ? It does not use my mapping definition at all.
Thanks!
Frédéric Delaporte added a comment — :
-
MappedAsdoes not currently allow to specify the type length/precision/scale. Maybe this could be a new feature, which would add someMappedAsoverload for this.-
MappedAsis for adjusting the type of what is converted to a query parameter: it does not try to infer anything from "nearby" entities. So those entities mappings are not taken into account for setting the parameter type characteristics, and this is by design.
paul added a comment — :
Thanks again... Do you known any way to set type/length in a Linq query Contains? I am willing to modify the source to do that, but I dont known where I should look,
Frédéric Delaporte added a comment — :
First, does the parameter length causes any performance issue as the parameter type do? Maybe is it not worth it to adjust it.
Then, parameters in Linq queries are automatically extracted from literals found in the lambda. There are no places to adjust the resulting
DbParameter, excepted with theMappedAsextension. So if you want some way to set a parameter length, the best place is currently to check howMappedAsworks -then add to it an overload taking the length and do required changes for having it working-.MappedAsis processed in an expression tree visitor which detect it by reflection, so to find that in NHibernate sources, search it by name, not just by references.If you want to add this functionality, please add a new Jira issue, since it would not be a fix but a new functionality giving a workaround. And check contributing guidelines.
Frédéric Delaporte added a comment — :
As written by Alexander on this PR upon your request,
MappedAscan already specify the length.x.MappedAs(NHibernate.Type.TypeFactory.Basic("AnsiString(200)"))
paul added a comment — :
Yes, thanks for your help Frederic
Hm.. All of these methods generate HqlLike node. So parameter detection implemented in
https://github.com/nhibernate/nhibernate-core/blob/74b97af3474d42311a46dd6ec832a11ddf7ae689/src/NHibernate/Hql/Ast/ANTLR/Tree/BinaryLogicOperatorNode.cs#L56-L66
should handle and apply proper parameter type (where lhs is mapped property with known type, and rhs - constant parameter)
But this functionality is broken because parameter is guessed and specified for constant as StringType when LINQ query is processed: https://github.com/nhibernate/nhibernate-core/blob/ec634f50ff2a36ce1f5705a361269d47a9ea1de8/src/NHibernate/Linq/Visitors/ParameterTypeLocator.cs#L159-L164
If this guessing is removed - parameter is properly applied as AnsiString.
Oups.. Without guessing parameter detection only properly works for Like. It's still broken for StartsWith and other methods.
For possible fix see https://github.com/nhibernate/nhibernate-core/pull/2793#issuecomment-851673251