views:

686

answers:

4

why this line:

var category = _dataContext.Categories.Where<Category>(p => p.Keywords.Split(' ').Contains<string>(context.Request.QueryString["q"])).First();

throws an System.NotSupportedException:

Comparison operators not supported for type 'System.String[]'

And how can I fix it? Thanks.

A: 

It's probably the case that context.Request.QueryString["q"] returns a string array instead of a single string. This is because a url may contain multiple parameters with the same name.

If you are sure there is always just one parameter named q on the request, you can change your code to this: context.Request.QueryString["q"].SingleOrDefault().

Ronald Wildenberg
Just checked - it doesn't. It returns "q1,q2,q3,q4,q5" as a string, not an array.
Arnis L.
HttpRequest.QueryString is a NameValueCollection.
TTT
Indexer of NameValueCollection returns string -_-
Arnis L.
Ah, ok. Then my answer doesn't really help...
Ronald Wildenberg
Actually - i thought the same. But checked before posting. :)
Arnis L.
+2  A: 

So you are looking for a value (from the query-string) in a space-delimited column in the database? And you're using Split to query the individual values inside the database?

(just checking my assumptions...)

string.Split is not supported in this way (at the database on column data) - see here for the supported string operations. (note that string.Split is explicitly not supported).

I'm lazy; when I delimit data in the database (relatively rare), I always add the same delimiter to the start and end of the data; then I can just search for:

string searchFor = DELIMITER + searchValue + DELIMITER;
...
.Where(row => row.Value.Contains(searchFor));

However; in this case, I expect the most practical option might be to write a UDF function that searches a delimited varchar (correctly handling the first/last item), and expose the UDF on the data-context - then use:

.Where(row => ctx.ContainsValue(row.Value, searchValue)); // ContainsValue is our UDF

Or - normalise the data...

.Where(row => row.Values.Any(s=>s.Value == searchValue));
Marc Gravell
+3  A: 

string.split is not supported in LINQ-to-SQL.

There's an easy fix. Select all the data and do the filtering in the client. This may not be very efficient depending on the number of categories.

var category = 
    _dataContext.Categories.ToList()
    .Where<Category>(p => p.Keywords.Split(' ').Contains<string>(context.Request.QueryString["q"])).First();

Calling .ToList() will force enumeration of all the categories from your datasource, and the subsequent operations will be performed in the client code.

Winston Smith
Reading the entire table back to the client is almost always a bad idea. There are some cases where it works, but they are the strict minority.
Marc Gravell
Indeed, it can be a huge performance hit but one must weigh this against the commonly held belief that pre-optimization is also bad. As well as large scale projects where this would be a problem, there are probably countless little web applications out there where the performance hit for this scenario would be trivial. I guess it just comes down to common sense.
Winston Smith
A: 

Firstly, I'd avoid storing delimited data in the database. As you've found out it can make database queries etc awkward.

If you have a limit amount of categories, I'd go with Joe's answer for simplicity, else, adding some detail to Marc's approach.

  1. Create a split-style UDF, for SQL Server I use:

CREATE FUNCTION FN_CHARLIST_TO_TABLE
 (@list      nvarchar(MAX),
  @delimiter nchar(1) = N',')
RETURNS @tbl TABLE (listpos int IDENTITY(1, 1) NOT NULL,
           str     varchar(4000)      NOT NULL,
           nstr    nvarchar(2000)     NOT NULL) AS
/*              
  Comments:
     - Takes a CSV string, and creates a table of data from this
     - Each item takes one row in the following format
      listpos - the index of the item in the string (effectively a row number in the output)
      str - The value, as VARCHAR
      nstr - The value, as NVARCHAR

     - This function is a direct extract from http://www.sommarskog.se/arrays-in-sql-2005.html#iter-list-of-strings
Usage:
    SELECT * 
    FROM  t 
    JOIN FN_CHARLIST_TO_TABLE('a,b,c,1,2,3', ',') list
    ON t.Name = list.str  
*/
BEGIN
   DECLARE @endpos   int,
       @startpos int,
       @textpos  int,
       @chunklen smallint,
       @tmpstr   nvarchar(4000),
       @leftover nvarchar(4000),
       @tmpval   nvarchar(4000)

   SET @textpos = 1
   SET @leftover = ''
   WHILE @textpos  0
      BEGIN
     SET @tmpval = ltrim(rtrim(substring(@tmpstr, @startpos + 1,
             @endpos - @startpos - 1)))
     INSERT @tbl (str, nstr) VALUES(@tmpval, @tmpval)
     SET @startpos = @endpos
     SET @endpos = charindex(@delimiter COLLATE Slovenian_BIN2,
        @tmpstr, @startpos + 1)
      END

      SET @leftover = right(@tmpstr, datalength(@tmpstr) / 2 - @startpos)
   END

   INSERT @tbl(str, nstr)
      VALUES (ltrim(rtrim(@leftover)), ltrim(rtrim(@leftover)))
   RETURN
END

  1. Add this to your DBML file
  2. Query your database, referencing the UDF appropriately

var catergories = from cat in _datacontext.Categories
                  from keyword in _datacontext.FN_CHARLIST_TO_TABLE(cat.Keywords, ' ')
                  where keyword.str == context.Request.QueryString["q"]
                  select cat;

This will then execute a purely database-side query.

MattH