- Home
- LLBLGen Pro
- LLBLGen Pro Runtime Framework
Binary expresion CompareTo can't be converted to a predicate expression
Joined: 05-Mar-2008
Hi,
we are using DevExpress grid for WPF, LINQ, InterLinq, WCF, LLBLGen 2.6 version 2.6.10 and SQL server. All database keys are GUIDs. We are able to get all data from any table but there is a problem with filtering. Grid works in LINQServerMode, LINQ queries are serialized through WCF chanel with InterLinq. On the server side we use our query handler, which delegates queries to LLBLGen.
There are two tables: AuditLogDetail and AuditLog. AuditLogDetail has reference to AuditLog. Its fillds are: - AuditLogDetailGUID - AuditLogGuid - FieldName - OldValue - NewValue - CheckSUm
When we want to filter by field “FieldName” grid generates query which fails with an exception. After many hours of searching we are able to reproduce this exception. Query build by grid and serialized by InterLinq:
{Count(Where(ThenBy(OrderBy(Select(InterLinqQuery`1< AuditLogDetailEntity>, i => i), => .FieldName), => .AuditLogDetailGuid), => ((Compare(.FieldName, "LocalisedBinary") < 0) || ((.FieldName = "LocalisedBinary") && (.AuditLogDetailGuid.CompareTo(57f73b00-26b1-11df-8600-001c2311de1b) < 0)))))}
We rewrote it and post it directly to LLBLGen linq metadata and it also fails with the same exception. If we delete the last Count exception is not thrown.
var query = (from i in metaData.AuditLogDetail select i).
OrderBy(i => i.FieldName).
ThenBy(i => i.AuditLogDetailGuid).
Where(i => ((string.Compare(i.FieldName, "LocalisedBinary") < 0) ||
((i.FieldName == "LocalisedBinary") && (i.AuditLogDetailGuid.CompareTo(new Guid("57f73b00-26b1-11df-8600-001c2311de1b")) < 0)))).
Count();
Exception Messager: "The binary expression '([508].CompareTo(57f73b00-26b1-11df-8600-001c2311de1b) < 0)' can't be converted to a predicate expression."
StackTrace:
" at SD.LLBLGen.Pro.LinqSupportClasses.ExpressionHandlers.QueryExpressionBuilder.HandleBinaryExpressionSeparateOperands(BinaryExpression expressionToHandle, Expression leftSide, Expression rightSide)\r\n at SD.LLBLGen.Pro.LinqSupportClasses.ExpressionHandlers.QueryExpressionBuilder.HandleBinaryExpressionBooleanOperator(BinaryExpression expressionToHandle)\r\n at SD.LLBLGen.Pro.LinqSupportClasses.ExpressionHandlers.GenericExpressionHandler.HandleExpression(Expression expressionToHandle)\r\n at SD.LLBLGen.Pro.LinqSupportClasses.ExpressionHandlers.QueryExpressionBuilder.HandleExpression(Expression expressionToHandle)\r\n at SD.LLBLGen.Pro.LinqSupportClasses.ExpressionHandlers.QueryExpressionBuilder.HandleBinaryExpressionBooleanOperator(BinaryExpression expressionToHandle)\r\n at SD.LLBLGen.Pro.LinqSupportClasses.ExpressionHandlers.GenericExpressionHandler.HandleExpression(Expression expressionToHandle)\r\n at SD.LLBLGen.Pro.LinqSupportClasses.ExpressionHandlers.QueryExpressionBuilder.HandleExpression(Expression expressionToHandle)\r\n at SD.LLBLGen.Pro.LinqSupportClasses.ExpressionHandlers.QueryExpressionBuilder.HandleBinaryExpressionBooleanOperator(BinaryExpression expressionToHandle)\r\n at SD.LLBLGen.Pro.LinqSupportClasses.ExpressionHandlers.GenericExpressionHandler.HandleExpression(Expression expressionToHandle)\r\n at SD.LLBLGen.Pro.LinqSupportClasses.ExpressionHandlers.QueryExpressionBuilder.HandleExpression(Expression expressionToHandle)\r\n at SD.LLBLGen.Pro.LinqSupportClasses.ExpressionHandlers.QueryExpressionBuilder.HandleLambdaExpression(LambdaExpression expressionToHandle)\r\n at SD.LLBLGen.Pro.LinqSupportClasses.ExpressionHandlers.GenericExpressionHandler.HandleExpression(Expression expressionToHandle)\r\n at SD.LLBLGen.Pro.LinqSupportClasses.ExpressionHandlers.QueryExpressionBuilder.HandleExpression(Expression expressionToHandle)\r\n at SD.LLBLGen.Pro.LinqSupportClasses.ExpressionHandlers.QueryExpressionBuilder.HandleWhereExpression(WhereExpression expressionToHandle)\r\n at SD.LLBLGen.Pro.LinqSupportClasses.ExpressionHandlers.GenericExpressionHandler.HandleExpression(Expression expressionToHandle)\r\n at SD.LLBLGen.Pro.LinqSupportClasses.ExpressionHandlers.QueryExpressionBuilder.HandleExpression(Expression expressionToHandle)\r\n at SD.LLBLGen.Pro.LinqSupportClasses.ExpressionHandlers.QueryExpressionBuilder.HandleAggregateExpression(AggregateExpression expressionToHandle)\r\n at SD.LLBLGen.Pro.LinqSupportClasses.ExpressionHandlers.GenericExpressionHandler.HandleExpression(Expression expressionToHandle)\r\n at SD.LLBLGen.Pro.LinqSupportClasses.ExpressionHandlers.QueryExpressionBuilder.HandleExpression(Expression expressionToHandle)\r\n at SD.LLBLGen.Pro.LinqSupportClasses.LLBLGenProProviderBase.HandleExpressionTree(Expression expression)\r\n at SD.LLBLGen.Pro.LinqSupportClasses.LLBLGenProProviderBase.Execute(Expression expression)\r\n at SD.LLBLGen.Pro.LinqSupportClasses.LLBLGenProProviderBase.System.Linq.IQueryProvider.Execute[TResult](Expression expression)\r\n at System.Linq.Queryable.Count[TSource](IQueryable`1 source, Expression`1 predicate)\r\n at …
It seems that it is either query is incorrect or there is a problem with LLBLGen and this query.
Thanks, Dawid
You use the latest runtime library build? (2.6.10 is not a build nr of the runtime library). Could you please post the runtime library build (rightmouse button in explorer on ormsupportclasses dll -> properties -> version tab
Joined: 05-Mar-2008
I think it's because there's no mapping of the CompareTo() method to a Db construct. I'll see if I can provide a custom one to the provider so you can use that in your code.
It happens regardless of the .Count() or not:
[Test]
public void CompareToOnGuidTest()
{
using(DataAccessAdapter adapter = new DataAccessAdapter())
{
LinqMetaData metaData = new LinqMetaData(adapter);
var q = (from a in metaData.Address select a)
.Where(a => a.Rowguid.CompareTo(new Guid("57f73b00-26b1-11df-8600-001c2311de1b")) < 0);
foreach(var v in q)
{
}
}
}
also fails. Looking into it.
Ok, this is what you should do: - create a class similar like the one I created below. - when LinqMetaData is instantiated, pass an instance of the functionmappings to the linqmetadata constructor like I did in the example below.
that's it.
Example function mapping class (tested it on adventureworks, of course you should name it differently )
internal class AdventureWorksFunctionMappings : FunctionMappingStore
{
public AdventureWorksFunctionMappings()
: base()
{
this.Add(new FunctionMapping(typeof(Guid), "CompareTo", 1, "CASE WHEN {0} < {1} THEN -1 WHEN {0} = {1} THEN 0 ELSE 1 END"));
}
}
example usage:
[Test]
public void CompareToOnGuidTest()
{
using(DataAccessAdapter adapter = new DataAccessAdapter())
{
LinqMetaData metaData = new LinqMetaData(adapter, new AdventureWorksFunctionMappings());
var q = (from a in metaData.Address select a)
.Where(a => a.Rowguid.CompareTo(new Guid("57f73b00-26b1-11df-8600-001c2311de1b")) < 0);
int count = 0;
foreach(var v in q)
{
count++;
}
Assert.AreEqual(6, count);
}
}
So where you instantiate the LinqMetaData object you've to specify the instance of your custom function mapping class.
We didn't add a CompareTo() mapping for every type to every DQE as in general it's not necessary to do so. With Guid's it's indeed a different matter, as Guid doesn't implement the '<' or '>' operator. I'm not sure if the query above gives the results you want btw, as comparing Guids is not that easy (as endy-ness has to be taken into account)
Joined: 05-Mar-2008
We have tried your solution and everything seems to work properly, thanks! But we have another problem (system configuration is described in first post). We want to fetch entities and their subentities just like in the following query:
var temp = (from i in metaData.AuditLogDetail
select i).WithPath(p => p.Prefetch(i => i.AuditLog));
Everything works on server side. But if we want to use it on client side (query has to be serialize through interlinq) query would look like:
var temp = (from i in clientQueryHandler.Get<AuditLogDetailEntity>()
select i).WithPath(p => p.Prefetch(i => i.AuditLog));
where ClientQueryHandler is InterLinqQueryHandler which is an implementation of IQueryHandler and its Get<T> method returns ClientQueryProvider. And here is a problem:
"Unable to cast object of type 'InterLinq.Communication.ClientQueryProvider' to type 'SD.LLBLGen.Pro.LinqSupportClasses.LLBLGenProProviderBase'."
That is true because WithPath method needs LLBLGenProProviderBase to operate not InterLinq ClientQueryProvider. Is there any way to pass WithPath with interlinq? Is there any other way to fetch subentities?
We can use anonymous types and specific query to fetch all needed data, but it would be much easier to use entities because anonymous types are inconvenient .
Thanks!
Hmm. So interlinq ( I have no knowledge of this library btw) serializes the expression tree, however not entirely? Because that's what's happening it seems: interlinq wraps some part of the expression tree, but not the WithPath method. Did you ask Interlinq why they don't serialize that method call?
Joined: 05-Mar-2008
InterLINQ is an IQueryable Provider implementation and makes it possible to serialize the Expression Tree of the LINQ Query and sending it to a WCF Service. We use DevExpress grid, which has ability to generate LINQ queries for displayed content. This provides us functionality to present thousands of record in grid with filtering, sorting and grouping abilities (everything is done on the server side, and grid fetches only couples of records just to show current page). And that is really great and works really fast! Everything is serialized through WCF and data layer is LLBLGen. But we have problem with fetching subenities using WithPath method.
I quess that WithPath method casts IQueryable<TSource> object to LLBLGenProProviderBase to build some expression?. If we use metadata from LLBLGen everthing works but if we use InterLinq (ClientQueryProvider object) exception is thrown before serialization. I
We ask similar question on InterLinq but no feedback so far: http://interlinq.codeplex.com/Thread/View.aspx?ThreadId=204620
Any idea? Regards David.
Could you give me the stacktrace where the exception occurs? The WithPath is just another method call at the client so the error happens on the server. To make it easier to find where exactly the exception occurs, it's best if you post the stacktrace so we can look at that method
Joined: 05-Mar-2008
Here is some information about exception:
Message: "Unable to cast object of type 'InterLinq.Communication.ClientQueryProvider' to type 'SD.LLBLGen.Pro.LinqSupportClasses.LLBLGenProProviderBase'."
Source: "SD.LLBLGen.Pro.LinqSupportClasses.NET35"
StackTrace: " at SD.LLBLGen.Pro.LinqSupportClasses.LinqUtils.GetElementCreator(IQueryable query)\r\n at SD.LLBLGen.Pro.LinqSupportClasses.QueryableExtensionMethods.WithPath[TSource](IQueryable1 source, Func
2 edgeSpecifierFunc)\r\n at OURPROJECT.UI.Core.UserControls.UcLINQServerModeTest.AuditLogObjects() in OURPROJECT_PATH\UserControls\UcLINQServerModeTest.xaml.cs:line 144"
TargetSite: {SD.LLBLGen.Pro.ORMSupportClasses.IElementCreatorCore GetElementCreator(System.Linq.IQueryable)}
function:
private IQueryable<AuditLogDetailEntity> AuditLogObjects()
{
ClientQueryHandler clientQueryHandler = new ClientQueryHandler(WcfServiceHelper.ClientChannel, BaseData.Sessions.MainSession.Token);
var temp = (from i in clientQueryHandler.Get<AuditLogDetailEntity>()
select i).WithPath(p => p.Prefetch(i => i.AuditLog));
return temp;
//"Unable to cast object of type 'InterLinq.Communication.ClientQueryProvider' to type 'SD.LLBLGen.Pro.LinqSupportClasses.LLBLGenProProviderBase'."
//return temp;
}
Regards, Dawid
yes, I see why it goes wrong there. The IQueryable withpath works on, is our provider (LinqMetaData), and it contains the ElementCreator object, which is necessary to create objects from classes in the generated code without the dependency on those classes in the linq provider.
the only way to solve this IMHO is to replace the elements of interlinq in the expression tree at the service with an LLBLGenProQuery instance, using the LinqMetaData instance of the service. This is somewhere in the object returned by the Get<> method IMHO as it would otherwise also never work without WithPath.
I have no knowledge about how interlinq works, though our linq provider has visitor classes which can easily transform whatever node into another one so it's easy to write a visitor class which processes the expression tree on the service, replaces the interlinq objects with objects as if the expression tree was created locally and then you can execute it. However, it requires interlinq's help, as I can't write that class for you as I don't know how interlinq works.
If you want to know how these visitor classes look like, please look into the LinqSupportClasses sourcecode, the ExpressionHandlers namespace/folder and for example at the ExpressionReplacer handler, which is a visitor which can traverse an entire expression tree and replace expressions it understands with expressions it finds in the set it contains.
But again, I have no idea what this 'Get' method does and where inside the data-structure it returns the original expression tree it represents is located.
I thought about a workaround for you, but they all come down to appending WithPath methods, which of course won't work with this linq tree at the service.