25

I have an Expression like so:

var values = Enumerable.Range(1,2);

return message => message.Properties.Any(
    p => p.Key == name 
    && int.Parse(p.Value) >= values[0] 
    && int.Parse(p.Value) <= values[1]);

This compiles fine but when it hits the database it throws the exception 'LINQ to Entities does not recognize the method 'Int32 Parse(System.String)' method, and this method cannot be translated into a store expression '

If I don't do the parse and have values be a string[] I can't then use the >= and <= operators on strings.

p.Value is a string which holds various values but in this case it is int

Is there a way I can query the database to do this sort of between statement?

Jon
  • 38,814
  • 81
  • 233
  • 382

5 Answers5

20

As much as I hate this answer, the actual answer is you can't do it easily. It will be a real pain. I've seen lots of wrong answers and lots of answers with people saying you should just have your database fields be the correct type in the first place, which is not helpful.

Your question is similar to this question on MSDN.

There are several possibilities depending on what sort of EF you are using.

1. You are using EDMX files.

(Not code first or reverse engineered code first).

You can use something like this (from this answer):

[EdmFunction("PlusDomain", "ParseDouble")]
public static double ParseDouble(string stringvalue)
{
    // This method exists for use in LINQ queries,
    // as a stub that will be converted to a SQL CAST statement.
    return System.Double.Parse(stringvalue);
}

and map it in your EDMX like this:

<Function Name="ParseDouble" ReturnType="Edm.Double">
    <Parameter Name="stringvalue" Type="Edm.String" />
    <DefiningExpression>
        cast(stringvalue as Edm.Double)
    </DefiningExpression>
</Function>

2. If you are using code first in EF >= 4.1.

You are screwed. Microsoft didn't see fit to add any such function to SqlFunctions. The best you can hope for is to add a scalar SQL function into your database and (maybe) try to map it into your context. Microsoft didn't see any point in doing anything like this in code first. Fortunately they didn't totally block such things either. The Fluent API is powerful.

You can call the functions or stored procedures like this (reference):

var outParam = new SqlParameter("overHours", SqlDbType.Int);
outParam.Direction = ParameterDirection.Output;

Or like this (reference):

var data = context.Database.ExecuteSqlCommand("dbo.sp_getNumberJobs @overHours OUT", outParam);
int numJobs = (int)outParam.Value;

But to make them actually integrate into LINQ to Entities, you need something like CodeFirstFunctions, by using the EntityFramework.CodeFirstStoreFunctions NuGet package. It maps the SQL functions into the context, but it uses an external library only made for .NET 4.5 (see here).

Instead, you can attempt to do the same thing manually like in this question.

The quicker solution I've settled on for my needs is to just create a view with the converted types. This avoids the whole problem.

CarenRose
  • 1,266
  • 1
  • 12
  • 24
Menace
  • 1,061
  • 13
  • 15
  • 1
    I know this is an old answer, but it was really helpful in opening my eyes on a recent issue. The answer is good, but there is also a way to use model-defined functions with code first! It is a bit more work, though. See https://stackoverflow.com/questions/29503962/are-model-defined-functions-still-supported-in-ef6 Also, the MS code seems to throw NotImplementedException("not for direct calls") in the actual C# code, and I think that is wise. That call is meant to be translated from an expression tree into actual SQL, not called directly! – Alexandru Clonțea Apr 27 '18 at 23:42
8

As pointed out by others in the comments, the fact that you're having to parse this value should be a red flag that you should be using a different data type in your database.

Fortunately, there is a workaround by forcing the query to be executed by LINQ to Objects rather than LINQ to Entities. Unfortunately, it means potentially reading a large amount of data into memory

EDIT

Based on your other comments, the value in the Value column ins't guaranteed to be a number. Therefore, you'll have to try converting the value to a number and then handling things based on the failure/success of that conversion:

return message
       .Properties
       .AsEnumerable()
       .Any(p => 
            {
                var val = 0;
                if(int.TryParse(p.Value, out val))
                {
                    return p.Key == name &&
                           val >= values[0] &&
                           val <= values[1])
                }
                else
                {
                    return false;
                }
           );

EDIT 2

You might actually be able to get away with this in the database. I'm not sure if this will work or not for you but give it a shot:

return message.Properties
              .Where(p => p.Key == name && SqlFunctions.IsNumeric(p.Value) > 0)
              .Any(p => Convert.ToInt32(p.Value) >= values[0] &&
                        Convert.ToInt32(p.Value) <= values[1]);
Justin Niessner
  • 242,243
  • 40
  • 408
  • 536
  • I'm trying not to do that as this is part of a filtering method that is supposed to hit the db and return the filtered data that way – Jon May 22 '13 at 14:49
  • @Jon - Since the data doesn't necessarily have to be a number, there's no good way to handle the conversion in the database. Your options are to use code similar to that above or to use strings for comparison. – Justin Niessner May 22 '13 at 14:52
  • how can i use strings for comparison using the greater/less than operators though – Jon May 22 '13 at 14:56
  • @Jon - Just added another possible option for you. I'm not 100% sure it'll work in your case, but if it does, it'll allow you to use Convert.ToInt32 properly. – Justin Niessner May 22 '13 at 15:03
  • will give it a go, just tried writing TSQL, even with a subquery but it wont filter properly – Jon May 22 '13 at 15:07
  • 4
    bum, I get LINQ to Entities does not recognize the method 'Int32 ToInt32(System.String)' method, and this method cannot be translated into a store expression. – Jon May 22 '13 at 15:09
  • think I will have to dome db schema changes – Jon May 22 '13 at 15:18
  • @Jon - Are you using EDMX or Code-First for EF? – Justin Niessner May 22 '13 at 15:19
  • 1
    Your second edit saved my life 7 years after. Thanks! – Karel Křesťan Mar 20 '21 at 12:07
1

I recently had to convert a string (numeric representation) to an enumeration value in a LINQ query, without materializing the query. My enumeration used int values between 0 and 9, so I ended up doing something like this:

    .Select(str => (MyEnum?)(SqlFunctions.Unicode(str) - 48))

I know this is not providing a full solution for the problem, but it definitely works in a situation like mine. Maybe some will find it useful.

Adi
  • 106
  • 1
  • 6
0

You can use the SqlFunctions.IsNumeric() method and, for example, store it into a list.

var resultsArray = context.TableData_DB.Where(x => SqlFunctions.IsNumeric(x.stringField) >= -1).ToList();
Jaume
  • 3,672
  • 19
  • 60
  • 119
-4

You can try Convert.ToInt32(input); If you are not sure, you can TryParse. It will not throw exception but false is cannot Parse. But Convert.ToInt32(input); should work. You can read about it on http://msdn.microsoft.com/en-us/library/vstudio/bb397679.aspx.

user2399760
  • 27
  • 1
  • 6