Repository navigation
Description
I am running into repeated issues whenever I work with enum fields which are backed by anything else but int.
This simple query:
// fetch all current CNP settings for the given cards
var projection =
from cardSec in qe.All<Model.CardSecurity>()
join cnp in qe.All<Model.CardCnpSetting>() on cardSec equals cnp.AsCurrent
where QueryableExtensions.In<int>(cardSec.CardID, parentKeys)
&& cnp.State == HistoryTrackedItemState.Current
select new { cardSec.CardID, cnp };
causes an exception:
Xtensive.Orm.QueryTranslationException: 'Unable to translate 'Query.All().Join(
Query.All(),
cardSec => cardSec,
cnp => cnp.AsCurrent,
(cardSec, cnp) => new @<cardSec, cnp>(
cardSec,
cnp
)
).Where(<>h__TransparentIdentifier0 => (<>h__TransparentIdentifier0.cardSec.CardID.In(@.parentKeys) && (((Int32)<>h__TransparentIdentifier0.cnp.State) == 5))).Select(<>h__TransparentIdentifier0 => new @<CardID, cnp>(
<>h__TransparentIdentifier0.cardSec.CardID,
<>h__TransparentIdentifier0.cnp
))' expression. See inner exception for details.'
The inner exception is an "Ambiguous match found." error. And this is the stack trace:
at System.RuntimeType.GetPropertyImpl(String name, BindingFlags bindingAttr, Binder binder, Type returnType, Type[] types, ParameterModifier[] modifiers)
at System.Type.GetProperty(String name, BindingFlags bindingAttr)
at Xtensive.Orm.Linq.Translator.VisitMemberAccess(MemberExpression ma)
at Xtensive.Linq.ExpressionVisitor`1.Visit(Expression e)
at Xtensive.Linq.ExpressionVisitor.VisitUnary(UnaryExpression u)
at Xtensive.Orm.Linq.Translator.VisitUnary(UnaryExpression u)
at Xtensive.Linq.ExpressionVisitor`1.Visit(Expression e)
at Xtensive.Orm.Linq.Translator.VisitBinary(BinaryExpression binaryExpression)
at Xtensive.Linq.ExpressionVisitor`1.Visit(Expression e)
at Xtensive.Orm.Linq.Translator.VisitBinary(BinaryExpression binaryExpression)
at Xtensive.Linq.ExpressionVisitor`1.Visit(Expression e)
at Xtensive.Orm.Linq.Translator.VisitLambda(LambdaExpression le)
at Xtensive.Orm.Linq.Translator.VisitWhere(Expression expression, LambdaExpression le)
at Xtensive.Orm.Linq.Translator.VisitQueryableMethod(MethodCallExpression mc, QueryableMethodKind methodKind)
at Xtensive.Linq.QueryableVisitor.VisitMethodCall(MethodCallExpression mc)
at Xtensive.Orm.Linq.Translator.VisitMethodCall(MethodCallExpression mc)
at Xtensive.Linq.ExpressionVisitor`1.Visit(Expression e)
at Xtensive.Orm.Linq.Translator.VisitSequence(Expression sequenceExpression, Expression expressionPart)
at Xtensive.Orm.Linq.Translator.VisitSelect(Expression expression, LambdaExpression le)
at Xtensive.Orm.Linq.Translator.VisitQueryableMethod(MethodCallExpression mc, QueryableMethodKind methodKind)
at Xtensive.Linq.QueryableVisitor.VisitMethodCall(MethodCallExpression mc)
at Xtensive.Orm.Linq.Translator.VisitMethodCall(MethodCallExpression mc)
at Xtensive.Linq.ExpressionVisitor`1.Visit(Expression e)
at Xtensive.Orm.Linq.Translator.Translate()
at Xtensive.Orm.Linq.QueryProvider.Translate(Expression expression, CompilerConfiguration compilerConfiguration)
I can try updating to the latest master, in a few days, to confirm this as a pending bug. However, I do believe this has a similar root cause as the mistreatment of non-int fields in general. I have seen DO generate casts in SQL queries, completely unnecessary, such as this: …AND ( CAST([a].[id_arch] AS integer) = 0));, where id_arch is a smallint column (short in C#).
It's a pain and I hope @alex-kulakov and the team will pay attention to this. I already know how to write unit tests in DO codebase, so I will try contributing at least tests to identify these issues.
Activity
ondrejtucny commentedon Mar 8, 2023
Another example of an issue related to non-int fields:
Happens when accessing an referenced entity, which has a
shortprimary key:The data model in question uses mapping on a legacy database. Stripped down version of the entities is as follows:
The code which causes the exception, processes the result of a query, which returns records encapsulating related entities, and is as follows:
private record QueryProjection(…, DProduct Product, …);
ondrejtucny commentedon Mar 9, 2023
Update: The second issue was caused by a mismatched primary key data type (short instead of int) in an entity referenced from
CFeeType. It is strange that during domain build and validation this is not recognized and not reported as a mismatch. Due to the implementation of integer value retrieval in the SQL client such check could save a lot of headaches.See also: dotnet/runtime#19290 and source code here: https://github.com/dotnet/SqlClient/blob/73c6b2ff3ef3b55431cf187e72d5de918caccf76/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBuffer.cs#L409
alex-kulakov commentedon Mar 9, 2023
Hello @ondrejtucny, thank you for the report, I'll look at these issues and be back with some investigation results or comments
alex-kulakov commentedon Mar 9, 2023
By the way, cast to integer may be connected with the fact that literal parameters are by default integer, for example here
… AND ( CAST([a].[id_arch] AS integer) = 0))0 is treated as integer value, I can only say for now is that code is generalized and probably affects values in different parts of statements. It is not important for comparisons to have left and right parts of the same type and asmallintvalue can be compared with anintegervalue but from expressions standpoint it is better be the same type.alex-kulakov commentedon Mar 9, 2023
As far as I know .NET does the same thing, it casts short to int. What is 0 in the example above? Is it constant? What type it was on on the level of IQueryable query?
letarak commentedon Mar 12, 2023
Faced the same issue
Can be reproduced
short enum failed on sql server (ShortEnumType : short)
Unable to cast object of type 'System.Int32' to type 'System.Int16'.
long enum is OK (LongEnumType : long)
ondrejtucny commentedon Mar 13, 2023
The value 0 was provided as a compile time constant:
c.IdArch = 0.It would be better to analyze the expression tree and do own type decisions. C#'s and SQL's type compatibility rules don't match, SQL being IMO more permissive. Hence, for example, in case of comparison operators the types of operands are decided bottom-up, and only at the comparison level a decision is made whether injecting type casts is necessary or not. Similarly, in case of the binary operators (e.g. +, -), first the type of operands is discovered bottom-up, then a decision of the resulting type made at the operator level (e.g. short + int → int), and then a top-down type compatibility check is made, again inserting type casts if and only if necessary. This way I think you can get rid of most unwanted type casts.