-
Notifications
You must be signed in to change notification settings - Fork 58
2009 10 16 vb net specific text comparison in linq queries
Published on October 16th, 2009 at 13:26
Some time ago, I had a discussion with Michael Ketting, where he pointed me at this blog post by Jason Pettys. Jason notices that currently, NHibernate’s LINQ provider does not support string comparisons:
If you’re in VB.NET using LINQ to NHibernate and you get this error on a simple query:
System.ArgumentException : Expression of type 'System.Int32' cannot be used for return type 'System.Boolean'
Then I’ve got an explanation and a fix for you.
Jason notices that this is caused by VB.NET representing string comparisons by calls to the Microsoft.VisualBasic.CompilerServices.Operators.CompareString
method, which is not recognized by that LINQ provider. Jason also suggests a fix from the Visual Basic Team blog, which would simply replace those method calls with ordinary comparison operators.
Now, how does this relate to re-linq? Currently, re-linq does not handle those VB.NET-specific operators in any special way, so a LINQ provider based on re-linq would encounter those MethodCallExpressions
to Microsoft.VisualBasic.CompilerServices.Operators
whenever they are confronted with a LINQ query using VB.NET’s special operators.
We’d like to simplify handling of those somehow, but I’m not sure about the exact approach yet.
Options we’re considering:
- Just leave it as it is – let the actual LINQ provider sort this out.
- Substitute those operators with the standard ones.
- Define generic text comparison expression node types and replace the
MethodCallExpressions
with instances of those types. - Define VB-specific expression node types and replace the
MethodCallExpressions
with instances of those types. - Implement support for language packages, i.e. plug-ins for re-linq that encapsulate language-subtleties and their processing.
Option 1 is the simplest one – for us. But of course, it’s not acceptable, since it moves the burden on to our users (i.e. the concrete LINQ providers). So I’d rule that out - although it’s the current situation, I’ll definitely want to change that in the future.
Approach number 2 is definitely the simplest one for re-linq’s users – from their point of view everything would apparently “just work”. However, it also has a great disadvantage: it probably wouldn’t do what a VB user would expect.
The reason why VB.NET emits those MethodCallExpressions
is that string comparisons have very specific semantics in VB.NET. For example, null
(Nothing
in VB.NET) compares equal to empty strings. And these semantics should probably be retained in the LINQ query; otherwise there would be a discrepancy between the language and the actual query.
Note that LINQ to SQL does differentiate between ordinary compares and VB.NET compares, as Michael has found out:
C#:
var result = from p in context.Products where p.Size == null select p;
SELECT COUNT(*) AS [value] FROM [Production].[Product] AS [t0]
WHERE [t0].[Size] IS NULLVB.NET 1:
Dim result = From p In context.Products Where p.Size = Nothing
SELECT COUNT(*) AS [value] FROM [Production].[Product] AS [t0]
WHERE [t0].[Size] = ''VB.NET 2:
Dim result = From p In context.Products Where p.Size Is Nothing
SELECT COUNT(*) AS [value] FROM [Production].[Product] AS [t0]
WHERE [t0].[Size] IS NULL
As you can see, when you compare a string using ‘=
’ in VB.NET (which results in the specific MethodCallExpression
to Operators.CompareString
), LINQ to SQL substitutes Nothing
with the empty string. If we unified that scenario with C'#’s “== null
” scenario, we’d have IS NULL
semantics instead.
That loss of semantics probably won’t be an option for LINQ providers trying to offer “real” VB.NET support, so we’ll definitely make that transformation optional if we implement it.
Which leaves us with options 3-5 as the default option – and I’m not completely sure what would be the most elegant solution here.
Should we assume this is simply an issue of text comparisons and implement option number 3? This would mean that those expression types would go into re-linq’s standard expression vocabulary, and even a provider without VB support would be encouraged to handle them. We could reuse the expressions to represent String.Compare
method calls, storing metadata such as case (in)sensitivity or culture as well as null semantics.
Or should we follow option 4 and say this is part of VB.NET support, providing a set of VB-specific expression types plus visitors? In that case, (only) providers with VB support would have to implement a set of additional VB-specific visitors.
But maybe this is an indicator of a bigger problem – different languages might embed different semantics in LINQ expression trees, so we should probably follow option 5 and implement support for language packages consisting of special visitors and expression types and visitors. VB.NET would only be one incarnation of that concept; others might follow. For the re-linq user, this means that the provider has to configure and handle packages for all languages that should be supported. It’s definitely the most flexible, but probably also the most complicated approach. And is it really required or am I just being paranoid about that language problem?
I’m really not sure how far to go. If you have any opinions or ideas – please share them.
Edit (2009-10-16 15:33): Please discuss your ideas in our discussion group rather than on this blog, here’s the link to the topic: http://groups.google.com/group/re-motion-dev/browse_thread/thread/819583bd86a1cf5c.
- Fabian
Hey man, good afternoon.
I was wondering: has this been fixed? Or is there any workaround I can apply to make this work? I’m currently tied to vb.net and this has bitten me. I’m using NHibernate trunk which uses Remotion.Data.Linq 1.13.41.2.
Do you happen to know a place where I can fix this? I’m just aiming for a quick n’ dirty fix so I can keep working. I just built Remotion.Data.Linq from trunk which gave version 1.13.60.2, and after that I built NHibernate from trunk (http://github.com/leemhenson/nhibernate) with the new re-linq dll, and the same problem appeared 🙁
Well, I finally made it by hacking on NHibernate. The signature of the method being called was the following: bool Equals(string, System.StringComparison). What I had to do was to add a new SupportedMethod in the constructor of EqualsGenerator class found in NHibernate.Linq.Functions.StringGenerator class. The implementation is trivial.
By doing only this, I don’t take into account the System.StringComparison parameter, but I only needed this to work, I don’t mind having to compare lowered strings always.
If you know of a better way to do this, I’d really appreciate it if you told me.
Thank you very much.
Hi Jorge,
The post above is about the Microsoft.VisualBasic.CompilerServices.Operators.CompareString
method, which the VB compiler uses for string comparisons. Current versions of re-linq (1.13.65 and above) now automatically handle that method, allowing the LINQ provider to process it like an ordinary comparison, as it would be emitted by other languages. (With previous versions of re-linq, the LINQ provider’s backend had to detect and handle the method call by itself.)
However, the problem you’re describing, with the string.Equals (string, System.StringComparison)::bool
method being called, is a different one: it definitely needs to be handled by NHibernate’s LINQ provider, not by re-linq. So it also needs to be fixed there, probably in a similar way as you’ve done. I’d encourage you to report the problem and your fix to one of the NHibernate mailing lists (http://groups.google.com/group/nhusers and groups.google.com/group/nhibernate-development).
However, I’d be very interested in the exact case where the VB.NET compiler would automatically emit a call to string.Equals (string, StringComparison)::bool
. This is a method that’s difficult to translate to any database language because it involves culture-specific behavior. Even LINQ to SQL (which has a very good LINQ provider) doesn’t handle this method. Would you care to post your VB.NET code snippet here?
Fabian
Hi!
After thinking a little bit more about this I realized that this was probably solved in re-linq, since NHibernate wasn’t receiving the VisualBasic stuff. Have you explained anywhere way and how did you solve this?
And in my attempt to make this work I changed the way I compared strings and included a direct comparison to Equals(string, StringComparison) and didn’t remember. I changed it back to = and now I’m getting a NotSupportedException with the message VBStringComparisonExpression from NHibernate. I guess I’ll have to dig deeper there to find out the problem, but in the meantime I’ll just leave it as it is.
Thank you very much for your help, Fabian.
Also, I will do as you mention to let the NHibernate guys know about this issue, since they’re still using an old version of re-linq which has the problem explained in this post.
Later I’ll keep reading your code and NHibernate code to see if I can come up with a good solution to this problem. I’m guessing it goes through the lines of VBStringComparisonExpression.
Thanks.
Hi Jorge,
If you already have a VBStringComparisonExpression
, you must already have re-linq 1.13.65 or later (because we didn’t include that expression before that). In that case, your (ie., NHibernate’s) LINQ provider will need to either handle VBStringComparisonExpressions
(by implementing IVBSpecificExpressionVisitor
) or be able to ignore unknown, reducible expression types.
I’ll try to prepare a blog post about this later today or tomorrow.
Fabian