Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Entity Framework - ridiculous Query, casting smallint to int for comparison [duplicate]

Out of ideas here. I have a simple table that is model first mapped with Entity Framework and I get the following SQL generated:

(@p__linq__0 int,@p__linq__1 int)SELECT 
    [Extent1].[BucketRef] AS [BucketRef], 
    [Extent1].[VariantNo] AS [VariantNo], 
    [Extent1].[SliceNo] AS [SliceNo], 
    [Extent1].[TradeNo] AS [TradeNo], 
    [Extent1].[TradeBegin] AS [TradeBegin], 
    [Extent1].[TradeEnd] AS [TradeEnd], 
    FROM [simstg].[Trade] AS [Extent1]
    WHERE ((( CAST( [Extent1].[BucketRef] AS int) = @p__linq__0) AND ( NOT (( CAST( [Extent1].[BucketRef] AS int) IS NULL) OR (@p__linq__0 IS NULL)))) OR (( CAST( [Extent1].[BucketRef] AS int) IS NULL) AND (@p__linq__0 IS NULL))) AND ((( CAST( [Extent1].[VariantNo] AS int) = @p__linq__1) AND ( NOT (( CAST( [Extent1].[VariantNo] AS int) IS NULL) OR (@p__linq__1 IS NULL)))) OR (( CAST( [Extent1].[VariantNo] AS int) IS NULL) AND (@p__linq__1 IS NULL)))

all those casts kill the perforamnce. I sadly do fail to see where they come from.

The query in question is:

var tradesQuery = repository.SimStgTrade
    .Where(x => x.BucketRef == bucketId && x.VariantNo == set)
    .ToArray();

this is as easy as it gets. The field definitions are: bucketId: short (smallint in the database), set short, smallint in the database. As such, the casts are totally not needed. I have already deleted and recreated the table in the model - and as far as I can see, the mappings match (the fields as smallint). As a result of this, we run into SERIOUS issues with performance - as in: the query times out because it does not use a table scan.

Anyone has any idea how to get rid oc those casts and force the comparison to be based on shorts? It is quite obvious from the SQL that EF decides to move everything to an int first.... which makes no sense.

This is not a "is it nice" thing. The outstanding query paths are totally different and the resulting code is turning this into a self join. In Server Manager the EF variant takes more than 5 minutes while the optimized version with simple SQL takes 0.0 seconds (to return 228 rows out of some billion in that table).

like image 578
TomTom Avatar asked Mar 03 '14 14:03

TomTom


2 Answers

This behavior can be common for different LINQ providers and not only EF-specific, because of how C# compiler generates expression tree for your Where expression.

When you specify condition as:

.Where(x => x.BucketRef == bucketId)

and both BucketRef and bucketId are shorts, compiler generates cast from short to int for both parts of comparison, because == operator isn't defined for Short type. This is explained in answer https://stackoverflow.com/a/18584429/869184

As a workaround you can rewrite condition the following way:

.Where(x => x.BucketRef.Equals(bucketId))

This effectively removes cast from comparison.

like image 106
Ilya Avatar answered Oct 19 '22 17:10

Ilya


You need to create the Where expression yourself using the static functions in the Expression class.

Like this:

Int16 bucketId = 3;

var parameter = Expression.Parameter(typeof(SimStgTrade));
var property = Expression.PropertyOrField(parameter, "BucketRef");
var constant = Expression.Constant(bucketId);
var comparison = Expression.Equal(property, constant);

var lambda = Expression.Lambda<Func<SimStgTrade,bool>>(comparison, parameter);

var tradesQuery = repository.SimStgTrade
  .Where(lambda)
  .Where(x => x.VariantNo == set)
  .ToArray();

Do the same for VariantNo == set in order to remove that cast as well

like image 21
Aducci Avatar answered Oct 19 '22 19:10

Aducci