views:

55

answers:

3

What is the problem with this query and how can I fix it?

public JsonResult Find(string q)
{
    var k = new List<string>(q.Split(' '));

    return Json(_dataContext.Jobs
        .OrderBy(p => new List<string>(p.Keywords.Split(' ')).Where(n => k.Contains(n)).Count())
        .Select(p => new { p.Title, p.IsFullTime, p.Location, p.Category, p.Url, p.Id }),
        JsonRequestBehavior.AllowGet);
 }

It throws:

Method 'System.String[] Split(Char[])' has no supported translation to SQL.

It's supposed to order the results by shared words between q and the Keywords for each row so the more you have shared words, you are ordered higher.

Thanks.

BTW: If it's possible to use Lucene.NET to improve this code, I'd happy to see a short example :)

+1  A: 

.OrderBy(p => new List(p.Keywords.Split(' ')).

Well, the message is faily clear. String.Split() cannot be translated into SQL.

There is no really good way to do that in a single Linq-to-Sql statement. I'd suggest pulling the data out using L2S, put it into a List<>, and then sort them there.

    var jobs  = from p in _dataContext.Jobs
    select new 
      {
        p.Title,
        p.IsFullTIme,
        p.Location,
        p.Category,
        p.Url,
        p.Id,
        p.Keywords
      }

      return Json(job.ToList()
            .OrderBy(p=>p.Keywords.Split(' ').Where(n=>k.Contains(n)).Count()),
             JsonRequestBehavior.AllowGet);

However, your real problem is that you have a really bad design. Proper third-normal form would have a JobKeywords table (int JobId, varchar Keyword) with one row for each keyword for a job. Then you could do it in one sql statement:

 return Json(from p in _dataContext.Jobs     
             order by p.Keywords.Intersect(k).Count()
             select new { p.Title, p.IsFullTime, p.Location, 
                          p.Category, p.Url, p.Id },     
        JsonRequestBehavior.AllowGet);            
James Curran
A: 

You can't use p.Keywords.Split(' '). LINQ-to-SQL doesn't support it. And why are you ordering by a list anyway?

Mark Cidade
+1  A: 

You can retrieve all the data from SQL-land and do the string-splitting in C#-land:

public JsonResult Find(string q)
{
    var k = q.Split(' ');

    return Json(_dataContext.Jobs
        // Select all the columns we need, including Keywords
        // (still in SQL-land)
        .Select(p => new { p.Title, p.IsFullTime, p.Location, p.Category,
                           p.Url, p.Id, p.Keywords })
        // Move into C#-land
        .AsEnumerable()
        // Do the sorting here in C#-land
        .OrderBy(p => p.Keywords.Split(' ').Count(n => k.Contains(n)))
        // Finally, remove the Keywords column we no longer need
        .Select(p => new { p.Title, p.IsFullTime, p.Location, p.Category,
                           p.Url, p.Id }),
        JsonRequestBehavior.AllowGet);
 }

However, this is going to be slow because it is going to retrieve the entire Jobs table every time, even if you add a .Take(n) at the end to get only the top n entries.

Timwi
The original was going to return the entire Job table also.
James Curran
`AsEnumerable` just returns the same list. There is no operation defined like that on `IQueryable`. You will need to use `ToList` (or friends) to do the actual retrieval. The `AsEnumerable` does however allow the transistion from SQL to .NET to be made on the subsequent call due to the arguments being delegates.
leppie
@Ieppie: You’re right, `AsEnumerable()` doesn’t actually do the retrieval, but I think that’s not important here. There’s certainly no need for a list.
Timwi