Linq-to-LLBLGen Pro: ORMQueryConstructionException in .NET Core Project when using VB.NET and "=" operator

Posts   
 
    
acl
User
Posts: 91
Joined: 28-Mar-2012
# Posted on: 20-Sep-2021 14:51:16   

Hi,

I've started using the .NET Standard variant of the LLBLGen Runtime (v5.7.1) on .NET Core projects.

All of my projects so far were C#, and everything has worked fine. But recently I ran into problems with a project that uses VB.NET. I'm using the adapter templates.

Consider the following query:

(From Table In linqMetaData.Table Where Table.Table_Cod = "XXX" Select r = Table.Table_Value).FirstOrDefault

This throws "ORMQueryConstructionException: The binary expression '(CompareString(EntityField(LPLA_1.Table_Cod AS Table_Cod), "XXX", False) == 0)' can't be converted to a predicate expression."

The problem comes from the Table.Table_Cod = "XXX" expression. When I write Table.Table_Cod.Equals("XXX")it works fine. I haven't seen this mentioned in the changelogs, and I haven't seen any other forum threads on this. So I assume v5.8 will behave the same.

I believe this is a bug.

Could you please look into that.

Thanks,

andreas

Full call stack:

SD.LLBLGen.Pro.ORMSupportClasses.ORMQueryConstructionException: The binary expression '(CompareString(EntityField(LPLA_1.Table_Cod AS Table_Cod), "XXX", False) == 0)' can't be converted to a predicate expression.
   at SD.LLBLGen.Pro.LinqSupportClasses.ExpressionHandlers.QueryExpressionBuilder.HandleBinaryExpressionSeparateOperands(BinaryExpression expressionToHandle, Expression leftSide, Expression rightSide)
   at SD.LLBLGen.Pro.LinqSupportClasses.ExpressionHandlers.QueryExpressionBuilder.HandleBinaryExpressionBooleanOperator(BinaryExpression expressionToHandle)
   at SD.LLBLGen.Pro.LinqSupportClasses.ExpressionHandlers.GenericExpressionHandler.HandleExpression(Expression expressionToHandle)
   at SD.LLBLGen.Pro.LinqSupportClasses.ExpressionHandlers.QueryExpressionBuilder.HandleExpression(Expression expressionToHandle)
   at SD.LLBLGen.Pro.LinqSupportClasses.ExpressionHandlers.QueryExpressionBuilder.HandleLambdaExpression(LambdaExpression expressionToHandle)
   at SD.LLBLGen.Pro.LinqSupportClasses.ExpressionHandlers.GenericExpressionHandler.HandleExpression(Expression expressionToHandle)
   at SD.LLBLGen.Pro.LinqSupportClasses.ExpressionHandlers.QueryExpressionBuilder.HandleExpression(Expression expressionToHandle)
   at SD.LLBLGen.Pro.LinqSupportClasses.ExpressionHandlers.QueryExpressionBuilder.HandleWhereExpression(WhereExpression expressionToHandle)
   at SD.LLBLGen.Pro.LinqSupportClasses.ExpressionHandlers.GenericExpressionHandler.HandleExpression(Expression expressionToHandle)
   at SD.LLBLGen.Pro.LinqSupportClasses.ExpressionHandlers.QueryExpressionBuilder.HandleExpression(Expression expressionToHandle)
   at SD.LLBLGen.Pro.LinqSupportClasses.ExpressionHandlers.GenericExpressionHandler.HandleSelectExpression(SelectExpression expressionToHandle, SelectExpression newInstance)
   at SD.LLBLGen.Pro.LinqSupportClasses.ExpressionHandlers.GenericExpressionHandler.HandleSelectExpression(SelectExpression expressionToHandle)
   at SD.LLBLGen.Pro.LinqSupportClasses.ExpressionHandlers.QueryExpressionBuilder.HandleSelectExpression(SelectExpression expressionToHandle)
   at SD.LLBLGen.Pro.LinqSupportClasses.ExpressionHandlers.GenericExpressionHandler.HandleExpression(Expression expressionToHandle)
   at SD.LLBLGen.Pro.LinqSupportClasses.ExpressionHandlers.QueryExpressionBuilder.HandleExpression(Expression expressionToHandle)
   at SD.LLBLGen.Pro.LinqSupportClasses.ExpressionHandlers.GenericExpressionHandler.HandleSelectExpression(SelectExpression expressionToHandle, SelectExpression newInstance)
   at SD.LLBLGen.Pro.LinqSupportClasses.ExpressionHandlers.GenericExpressionHandler.HandleSelectExpression(SelectExpression expressionToHandle)
   at SD.LLBLGen.Pro.LinqSupportClasses.ExpressionHandlers.QueryExpressionBuilder.HandleSelectExpression(SelectExpression expressionToHandle)
   at SD.LLBLGen.Pro.LinqSupportClasses.ExpressionHandlers.GenericExpressionHandler.HandleExpression(Expression expressionToHandle)
   at SD.LLBLGen.Pro.LinqSupportClasses.ExpressionHandlers.QueryExpressionBuilder.HandleExpression(Expression expressionToHandle)
   at SD.LLBLGen.Pro.LinqSupportClasses.LLBLGenProProviderBase.HandleExpressionTree(Expression expression)
   at SD.LLBLGen.Pro.LinqSupportClasses.LLBLGenProProviderBase.PerformExecute(Expression expression, Type resultType)
   at SD.LLBLGen.Pro.LinqSupportClasses.LLBLGenProProviderBase.System.Linq.IQueryProvider.Execute[TResult](Expression expression)
   at System.Linq.Queryable.FirstOrDefault[TSource](IQueryable'1 source)
   at our code
Walaa avatar
Walaa
Support Team
Posts: 14950
Joined: 21-Aug-2005
# Posted on: 20-Sep-2021 21:11:10   

Which .NET Standard version have you generated the code to? And which .NET core version is this running against?

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 21-Sep-2021 08:37:28   

I think this is a VB.NET quirk where the '=' comparison is compiled into a method call (the C# compiler compiles to an op_Equality call) in a vb.net specific library that is overlooked. We have several of these covered but strangely enough we missed this one.

We'll look into it. As a workaround you can keep using the .Equals() call, the sql is the same.

(Edit) It works fine on .net framework. Will now try on .net core 3.1

Frans Bouma | Lead developer LLBLGen Pro
acl
User
Posts: 91
Joined: 28-Mar-2012
# Posted on: 21-Sep-2021 09:11:12   

Thanks, Otis!

FYI the main project (c#) is built against .NET Core 3.1 and the VB.NET library is built against .NET Standard 2.0. Both reference the .NET Standard 2.0 LLBLGen runtime.

We are indeed using the .Equals workaround. It's just that the VB.NET library is an old piece of code with a few hundred queries that would have to be changed. So I prefer to wait for a fix.

Best,

andreas

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 21-Sep-2021 09:16:14   

I can't reproduce it (5.7 )

Works on .NET Core 3.1. (Generated code for .NET Standard 2.0, C#, run on .NET Core in a unit test: )

<Test()>
Public Sub StringEqualMethodCallTest()
    Using adapter As New DataAccessAdapter
        Dim metaData As New LinqMetaData(adapter)
        Dim q = (From c In metaData.Customer
                Where c.Country = "USA" 
                Select r = c.City).FirstOrDefault()
        Assert.AreEqual("Eugene", q)
    End Using
End Sub

Produces

Generated Sql query: 
    Query: SELECT TOP(@p2) [LPLA_1].[City] FROM [Northwind].[dbo].[Customers] [LPLA_1] WHERE ( ( ( ( ( ( [LPLA_1].[Country] = @p3))))))
    Parameter: @p2 : Int64. Length: 0. Precision: 0. Scale: 0. Direction: Input. Value: 1.
    Parameter: @p3 : String. Length: 15. Precision: 0. Scale: 0. Direction: Input. Value: "USA".
Frans Bouma | Lead developer LLBLGen Pro
acl
User
Posts: 91
Joined: 28-Mar-2012
# Posted on: 21-Sep-2021 10:06:50   

That was quick. Thanks.

That's strange tough. The situation in my case is a little different. The generated code of the LLBLGen Library is also VB.NET, it is used in a VB.NET DLL (which contains the bad queries) and this VB.NET DLL is then referenced in a .NET Core 3.1 project.

I will do some testing and I'll try to create a minimal example.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 21-Sep-2021 10:52:41   

acl wrote:

That was quick. Thanks.

That's strange tough. The situation in my case is a little different. The generated code of the LLBLGen Library is also VB.NET, it is used in a VB.NET DLL (which contains the bad queries) and this VB.NET DLL is then referenced in a .NET Core 3.1 project.

I will do some testing and I'll try to create a minimal example.

Alright, tho the generated code being in vb or c# doesn't really matter. The Linq query being in VBNET is what matters as the VB.NET compiler creates the calls to the methods building the expression tree and it's there where the method calls to the vbnet library methods are formulated.

Frans Bouma | Lead developer LLBLGen Pro
acl
User
Posts: 91
Joined: 28-Mar-2012
# Posted on: 22-Sep-2021 09:04:29   

Ok so I managed to narrow down the problem. It appears that the "bad" query has to be part of a library that the main project links against.

Attached you find a sample solution consisting of :

  • One executable targeting .net Core 3.1

  • One library targeting .net Standard 2.0

I did not include the generated LLBLGen library, you should be able to just reference yours. The generated LLBLGen library is VB.NET targeting .net Standard 2.0.

The solution contains two identical queries. One is located in the main project (.net Core) the other one is located in the .net Standard library, and called from the main project. The first query executes just fine. This is consistent with what you reported from your test. The second query throws the aforementioned exception.

It appears that the compiler produces a different expression for the equality check when targeting .net Standard.

Could you please have a look at that.

Thanks!

Attachments
Filename File size Added on Approval
EqualityTest.zip 2,742 22-Sep-2021 09:04.35 Approved
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 22-Sep-2021 10:44:56   

Expression tree of Program.vb:

.Call System.Linq.Queryable.Select(
    .Call System.Linq.Queryable.Where(
        .Constant<SD.LLBLGen.Pro.LinqSupportClasses.DataSource2`1[NW26.Adapter.EntityClasses.CustomerEntity]>(SD.LLBLGen.Pro.LinqSupportClasses.DataSource2`1[NW26.Adapter.EntityClasses.CustomerEntity]),
        '(.Lambda #Lambda1<System.Func`2[NW26.Adapter.EntityClasses.CustomerEntity,System.Boolean]>)),
    '(.Lambda #Lambda2<System.Func`2[NW26.Adapter.EntityClasses.CustomerEntity,System.String]>))

.Lambda #Lambda1<System.Func`2[NW26.Adapter.EntityClasses.CustomerEntity,System.Boolean]>(NW26.Adapter.EntityClasses.CustomerEntity $c)
{
    .Call Microsoft.VisualBasic.CompilerServices.EmbeddedOperators.CompareString(
        $c.Country,
        "USA",
        False) == 0
}

.Lambda #Lambda2<System.Func`2[NW26.Adapter.EntityClasses.CustomerEntity,System.String]>(NW26.Adapter.EntityClasses.CustomerEntity $c)
{
    $c.City
}

Expression tree of Functions.vb

.Call System.Linq.Queryable.Select(
    .Call System.Linq.Queryable.Where(
        .Constant<SD.LLBLGen.Pro.LinqSupportClasses.DataSource2`1[NW26.Adapter.EntityClasses.CustomerEntity]>(SD.LLBLGen.Pro.LinqSupportClasses.DataSource2`1[NW26.Adapter.EntityClasses.CustomerEntity]),
        '(.Lambda #Lambda1<System.Func`2[NW26.Adapter.EntityClasses.CustomerEntity,System.Boolean]>)),
    '(.Lambda #Lambda2<System.Func`2[NW26.Adapter.EntityClasses.CustomerEntity,System.String]>))

.Lambda #Lambda1<System.Func`2[NW26.Adapter.EntityClasses.CustomerEntity,System.Boolean]>(NW26.Adapter.EntityClasses.CustomerEntity $c)
{
    .Call Microsoft.VisualBasic.CompilerServices.Operators.CompareString(
        $c.Country,
        "USA",
        False) == 0
}

.Lambda #Lambda2<System.Func`2[NW26.Adapter.EntityClasses.CustomerEntity,System.String]>(NW26.Adapter.EntityClasses.CustomerEntity $c)
{
    $c.City
}

They switch to a different namespace.... disappointed

Frans Bouma | Lead developer LLBLGen Pro
acl
User
Posts: 91
Joined: 28-Mar-2012
# Posted on: 22-Sep-2021 10:50:27   

Too bad simple_smile

Please let me know when this will be fixed. It's not super urgent.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 22-Sep-2021 11:43:34   

It's a simple fix, it's just nasty how they do these things. No documentation too of this namespace. It's a 'well known type' in the roslyn code, that's it. Not a peep elsewhere. How is anyone able to find this out other than through a bug report by a user? Which is absolutely not how one wants to find this out.

Some checks for the type are now also testing for this type and we had to adjust the type mappings. Fix should be up in a couple of minutes.

Frans Bouma | Lead developer LLBLGen Pro
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 22-Sep-2021 12:23:18   

Fix is now available (hotfix 5.7.6 and 5.8.3)

Frans Bouma | Lead developer LLBLGen Pro
acl
User
Posts: 91
Joined: 28-Mar-2012
# Posted on: 22-Sep-2021 14:03:35   

Thanks! I can confirm that it works. It works on the sample project but also on the original project.