Saturday, January 30, 2010

Linq 2 SQL - where statement querying multiple columns with the same value

I have a search function that I need to be able to search for a piece of text against two columns in the database. In this case, I'm searching for the title of a book, but I also want to be able to search against the subtitle column with the same search value.

For example, the book title may be "The Smith's Christmas", with a subtitle of "First in the Xmas dinners series". I know that 'dinner' is somewhere in the name of the book. I input the term 'dinner', and need it to search against both the Title and the Subtitle (which are two separate fields).

This is some of my code.

// Because I have multiple search fields, I create 'matches' as the search criteria that changes depending on what I input. Allows it to be a bit dynamic
IQueryable<Book> matches = LibraryModelHelpers.dc.Books;

// This is the lambda statement. Basically translates to get me a match where the title contains my input text OR the subtitle contains my input text
matches = matches.Where(c => (c.Title.Contains(myInputText) || c.Subtitle.Contains(myInputText)));

 // Using the search statement, create an anonymous type which has a title, the author, the bookID and the cover information - I don't care about the rest for this operation
 var coverDetails = from c in matches
                               select new { c.Title, c.Author, c.BookID, c.Cover };

Tuesday, January 26, 2010

XML node doesn't exist?

This is from a small personal project using Amazon webservices. It's a pretty simple windows form desktop app that uses Linq to SQL. 

Dealing with some evil XML from Amazon that seems to have several variations. In some responses the node is there, other responses the node doesn't exist and is called something else (for pretty much the same data). I'm being lazy and pulling out the data I need one node at a time which can be a problem when the nodes are not present.

To get around this, for the nodes that I know to be problematic, I needed to check that the node was actually going to be available before doing the convert. I do this by looking at the exact node and check if it's null.

doc.GetElementsByTagName("TotalReviews").Item(0) != null

If you just look at the  doc.GetElementsByTagName("TotalReviews") != null it won't return null, but will fall over because there are no items to pull data from. I think when I looked at it it returned '0'. Anyway, you need the Item(0) as this is the value you are doing .InnerText on.

I'm using the GetElementsByTagName as it's generally easier than a longwinded xpath, but in some cases this is just not appropriate. A few cases I've selected children nodes by their position. I know this code isn't pretty, it probably isn't the best way to do thing, but it works.

using System.Xml;

WebRequest request = HttpWebRequest.Create(url);
WebResponse response = request.GetResponse();
doc.Load(response.GetResponseStream());
               
//If in doubt, spit the xml out somewhere and look at the payload
// doc.Save(Console.Out);
...

 if (doc.GetElementsByTagName("TotalReviews").Item(0) != null)
{
      book.NumOfReviews = Convert.ToInt32(doc.GetElementsByTagName("TotalReviews").Item(0).InnerText);
}
 else if (doc.GetElementsByTagName("TotalFeedback").Item(0) != null)
 {
      book.NumOfReviews = Convert.ToInt32(doc.GetElementsByTagName("TotalFeedback").Item(0).InnerText);
 }
     else
 {
        book.NumOfReviews = 0;
 }

...