views:

72

answers:

4

I have a table that has two records (there will be many at runtime). The deviceId of the records are, “DEVICE1” and “DEVICE2”. I want to use a regular expression to extract records. The code below compiles but fails to return a result. When I hover the cursor on the “devices.ToList()” statement I get the following error, “base {System.SystemException} = {"LINQ to Entities does not recognize the method 'System.Text.RegularExpressions.MatchCollection Matches(System.String)' method, and this method cannot be translated into a store expression."}”. Can anyone show me how I can modify my query so that this would return records based on the expression?

filterText = @"DEVICE.";
Regex searchTerm = new Regex(filterText);

using (var ctx = new MyEntities())
{
 var devices = from d in ctx.Devices
                let matches = searchTerm.Matches(d.DeviceId)
               where matches.Count > 0
               select ((Device)d);
return devices.ToList();
}
+1  A: 

You should always remember that your LingToEntities queries must be translated to SQL queries. Since SQL Server has no support for regular expressions, this can not work.

As suggested in the comment by Paul Ruane, StartsWith will work. This can be translated by LinqToEntities into WHERE DeviceId LIKE 'DEVICE%'.

If StartsWith isn't enough because you may need to look for strings in the middle of database columns, Contains will also work:

var devices = from d in ctx.Devices
              where d.DeviceId.Contains("DEVICE")
              select d;

This will result in the following: WHERE DeviceId LIKE '%DEVICE%'.

Ronald Wildenberg
Could I get all the devices first and then apply the regEx stuff using linq to objects ?
Retrocoder
That would be possible but this will only work if you first get all objects in memory. It is generally a better idea to do as much as possible in the database. I would advice that you use Contains.
Ronald Wildenberg
Why do you need to get all of them? Will Like not work ?
Nix
@Retrocoder You can, see my answer. Yes, it's inefficient, but if you must use a regular expression it's either that or use a Stored Proc.
Simon Steele
It's one or the other. You either try to let the database restrict the number of results via a LIKE query (using string.Contains) or you get all records in memory and use a regex there. But suppose you have a million records and only thousand satisfy your constraint, I would give up the flexibility of regex for (far) better performance.
Ronald Wildenberg
Could the person who did this please explain the -1?
Ronald Wildenberg
+1  A: 

I don't believe you can use regular expressions with LINQ to Entities. However, it looks like you're just trying to find devices which start with "DEVICE", so the query would be:

return ctx.Devices.Where(d => d.DeviceId.StartsWith("DEVICE"))
                  .ToList();

EDIT: If you actually need the flexibility of a regular expression, you should probably first fetch the device IDs (and only the device IDs) back to the client, then perform the regular expression on those, and finally fetch the rest of the data which matches those queries:

Regex regex = new Regex(...);

var deviceIds = ctx.Devices.Select(d => DeviceId).AsEnumerable();

var matchingIds = deviceIds.Where(id => regex.IsMatch(id))
                           .ToList();

var devices = ctx.Devices.Where(d => matchingIds.Contains(d.DeviceId));

That's assuming it would actually be expensive to fetch all the data for all devices to start with. If that's not too bad, it would be a simpler option. To force processing to be performed in process, use AsEnumerable():

var devices = ctx.Devices.AsEnumerable()
                         .Where(d => regex.IsMatch(d.DeviceId))
                         .ToList();
Jon Skeet
The example does show that I'm looking for "Device" at the start but the regexpr could be after something else, EG ".*Device.2" etc. I need to be able to give the user something flexable.
Retrocoder
@Retrocoder: If you need more flexibility, you should have said so in the question.
Jon Skeet
Sending a very large collection of matchingIds to the database can result in errors. I think I wouldn't take this course because the correct functioning of your application may depend on the selectivity of the regex, which is user-provided.
Ronald Wildenberg
@Ronald: If that's the case, the OP could always provide them in batches. At the moment we really don't know much about the scenario - how many devices there will be, how much other data there is, whether the regex is really from the user or will be in code from various places, etc.
Jon Skeet
A: 

Remember when using Entity Framework or Linq to Sql that your query ends up being translated to SQL. SQL doesn't understand your regular expression object, and can't use its matches on the server side. To use your RegEx easily you could instead retrieve all the devices from the server first, and then use your existing logic. e.g.

using (var ctx = new MyEntities()) 
{ 
    var devices = from Device d in ctx.Devices select d;

    // Retrieve all the devices:
    devices = devices.ToList();

    devices = from d in devices
                  let matches = searchTerm.Matches(d.DeviceId) 
                  where matches.Count > 0 
                  select ((Device)d); 

    return devices.ToList(); 
}

This does carry the cost of retrieving more data than you need, potentially much more. For complex logic you may want to consider a Stored Procedure, which could potentially use the same RegEx via CLR functions.

Simon Steele
Beat me to it, except I had written it as `return ctx.Devices.ToList().Where(d => searchTerm.Matches(d.DeviceId)).ToList()`.
kbrimington
Yeah, I might have been even quicker were it not for the fact that IE crashed twice on this page!
Simon Steele
A: 

LinqToEntities does not support pushing Regex's down to the database. Just do Contains (which gets converted to sql... where DeviceId Like '%DEVICE%').

    filterText = @"DEVICE.";


    using (var ctx = new MyEntities())
    {
            var devices = from d in ctx.Devices
                          d.DeviceId.Contains(filterText)

                          select d;
            return devices.ToList();
        }
Nix