Search code examples
c#entity-frameworklinqlinq-to-entitiesexpression-trees

TSQL query CAST to int + 1 to LINQ


I'm cracking my brains about the following linq to sql query. The idea is to get the smallest next integer number, compared to all REG_CODE items. This field (REG_CODE) is an varchar(10) field. I am trying to convert the following tsql to linq-to-entities (EF 6.0):

SELECT TOP 1 CAST( [Extent1].[REG_CODE] AS int) + 1 
    FROM [dbo].[Notifications] [Extent1]
WHERE NOT ([Extent1].[REG_CODE] LIKE N'%~[a-z]%' ESCAPE N'~') AND (1 = (ISNUMERIC([Extent1].[REG_CODE]))) AND
      NOT EXISTS(SELECT * FROM [dbo].[Notifications] t2 WHERE CAST( t2.REG_CODE AS int) = CAST( [Extent1].[REG_CODE] AS int) + 1 )
ORDER BY [Extent1].[REG_CODE]

(take note of the + 1 's, I want the next part) The design is not really that awsome. The field [REG_CODE] should have been an integer field, bud it's not and will not be anytime soon.

This:

float notificationMaxRegCodeNumeric =
    db.Notifications.Where(not => not.Reg_Code != null && !not.Reg_Code.Contains("[a-z]") && SqlFunctions.IsNumeric(not.Reg_Code) == 1)
        .OrderByDescending(not => not.Reg_Code)
        .Select(not => not.Reg_Code)
        .Cast<int>()
        .Max();

Converts successfully into:

SELECT MAX(CAST( [Extent1].[REG_CODE] AS int)) AS[A1]
FROM[dbo].[Notifications] AS[Extent1]
WHERE ([Extent1].[REG_CODE] IS NOT NULL) AND(NOT([Extent1].[REG_CODE] LIKE N'%~[a-z]%' ESCAPE N'~')) AND(1 = (ISNUMERIC([Extent1].[REG_CODE])))

So far I've got:

int nextNotificationMaxRegCodeNumericInt = db.Notifications.Where(not =>
    not.Reg_Code != null && !not.Reg_Code.Contains("[a-z]") &&
    SqlFunctions.IsNumeric(not.Reg_Code) == 1 &&
    db.Notifications.Any(klainternal => not.Reg_Code.Cast<int>() == klainternal.Reg_Code.Cast<int>())
    )
.OrderByDescending(not => not.Reg_Code)
.Select(not => not.Reg_Code)
.Cast<int>();

but it throws:

DbExpressionBinding requires an input expression with a collection ResultType`.

Also Convert.ToInt32() is throwing:

linq to entities does not recognize the method 'int32 toint32(system.string)' method`

(the .Max() is not really relevant in what I am looking for, bud it's in the working part of the query)

Any suggestions?


Solution

  • First, congratulations for finding the Cast<T>() trick! It seems to be the only out of the box EF6 way of casting string to something else - all other attempts like (int)(object)stringValue or Convert.ToInt32(stringValue) are simply blocked as not supported.

    But note that the Cast<T>() method is defined for IEnumerable and IQueryable and the result is respectively IEnumerable<T> and IQueryable<T>, i.e. works on sequence and produces sequence. It appears on string because string is IEnumerable<char>, thus IEnumerable, but it's not what we need.

    So the trick is to always use projection (Select) + Cast to do the conversions. Applying it to your query leads to something like this:

    int nextNotificationMaxRegCodeNumericInt = db.Notifications
        .Where(n => n.Reg_Code != null &&
            !n.Reg_Code.Contains("[a-z]") &&
            SqlFunctions.IsNumeric(n.Reg_Code) == 1)
        .Select(n => n.Reg_Code).Cast<int>() // <--
        .Select(reg_Code => reg_Code + 1)
        .Where(reg_Code => !db.Notifications.Select(n => n.Reg_Code).Cast<int>() // <--
            .Contains(reg_Code))
        .OrderByDescending(reg_Code => reg_Code)
        .FirstOrDefault();
    

    which converts to

    SELECT TOP (1)
        [Project1].[C1] AS [C1]
        FROM ( SELECT
             CAST( [Extent1].[Reg_Code] AS int) + 1 AS [C1]
            FROM [dbo].[Notifications] AS [Extent1]
            WHERE ([Extent1].[Reg_Code] IS NOT NULL) AND ( NOT ([Extent1].[Reg_Code] LIKE N'%~[a-z]%' ESCAPE N'~')) AND (1 = (ISNUMERIC([Extent1].[Reg_Code])))
        )  AS [Project1]
        WHERE  NOT EXISTS (SELECT
            1 AS [C1]
            FROM [dbo].[Notifications] AS [Extent2]
            WHERE  CAST( [Extent2].[Reg_Code] AS int) = [Project1].[C1]
        )
        ORDER BY [Project1].[C1] DESC