Implementing Search Without Convoluted If Statements In Asp.Net MVC

Implementing Search Without Convoluted If Statements In Asp.Net MVC

Introduction

In the beginning, when I start to learn C# and Asp.Net MVC, building search form was a complicated thing to do for me, the more criteria I’ve had for a search form, the harder it would get, but it was hard because I didn’t use the proper tools to do my job, back then, I didn’t know about IQueryable interface and what Linq deferred execution was. In this post, I’m going to show you how you can build any kind of search form without complicating things, using Linq deferred execution.

Search Form, The Wrong Way

First, I’m going to show you the wrong way to do it, and I’m telling you, if your search becomes more than a mere two field, the logic can become a real mess, Imagine we have a search form like this:

And here our ugly, naive, mess of code for this form:

public ActionResult ManageOrders(DateTime fromDate, DateTime toDate, string tracingCode, string customerName)
        {
            if (!string.IsNullOrEmpty(fromDate) && !string.IsNullOrEmpty(toDate) && string.IsNullOrEmpty(tracingCode) && string.IsNullOrEmpty(customerName))
            {
                var model = db.Orders.Where(s => s.OrderDate >= fromDate && s.OrderDate <= toDate);
                return View(model);
            }
            else if (!string.IsNullOrEmpty(fromDate) && !string.IsNullOrEmpty(toDate) && !string.IsNullOrEmpty(tracingCode) && !string.IsNullOrEmpty(customerName))
            {
                var model = db.Orders.Where(s => s.OrderDate >= fromDate && s.OrderDate <= toDate && s.TracingCode == tracingCode && s.CustomerName == customerName);
                return View(model);
            }
            else if (!string.IsNullOrEmpty(fromDate) && !string.IsNullOrEmpty(toDate) && string.IsNullOrEmpty(tracingCode) && !string.IsNullOrEmpty(customerName))
            {
                var model = db.Orders.Where(s => s.OrderDate >= fromDate && s.OrderDate <= toDate && s.CustomerName == customerName);
                return View(model);
            }
            else if (!string.IsNullOrEmpty(fromDate) && !string.IsNullOrEmpty(toDate) && !string.IsNullOrEmpty(tracingCode) && string.IsNullOrEmpty(customerName))
            {
                var model = db.Orders.Where(s => s.OrderDate >= fromDate && s.OrderDate <= toDate && s.TracingCode == tracingCode);
                return View(model);
            }
            else if (string.IsNullOrEmpty(fromDate) && string.IsNullOrEmpty(toDate) && !string.IsNullOrEmpty(tracingCode) && !string.IsNullOrEmpty(customerName))
            {
                var model = db.Orders.Where(s => s.TracingCode == tracingCode && s.CustomerName == customerName);
                return View(model);
            }
            else if (string.IsNullOrEmpty(fromDate) && string.IsNullOrEmpty(toDate) && string.IsNullOrEmpty(tracingCode) && !string.IsNullOrEmpty(customerName))
            {
                var model = db.Orders.Where(s => s.CustomerName == customerName);
                return View(model);
            }
            else if (string.IsNullOrEmpty(fromDate) && string.IsNullOrEmpty(toDate) && !string.IsNullOrEmpty(tracingCode) && string.IsNullOrEmpty(customerName))
            {
                var model = db.Orders.Where(s => s.TracingCode == tracingCode);
                return View(model);
            }

            return View(db.Orders);
            
     }

As you can see, we have to check for each and every one of the conditions, for when one field is filled and others are empty and vise versa, and return the correct filtered model, this approach is just plain wrong and shouldn’t be used, at least when we have Linq and deferred execution.

Search From, The Right Way

With the powers that Linq and its deferred execution give us, we turn the above code to this:

public ActionResult ManageOrders(DateTime fromDate, DateTime toDate, string tracingCode, string customerName)
        {
            IQueryable orders = db.Orders;

            if (!string.IsNullOrEmpty(fromDate) && !string.IsNullOrEmpty(toDate))
            {
                orders = orders.Where(s => s.OrderDate >= startDate && s.OrderDate <= endDate);
            }

            if (!string.IsNullOrEmpty(tracingCode))
            {
                orders = orders.Where(s => s.TracingCode.Contains(tracingCode));
            }

            if (!string.IsNullOrEmpty(customerName))
            {
                orders = orders.Where(s => s.CustomerName.Contains(customerName));
            }
   
            return View(orders);
     }

You can make it even shorter if you use ternary if, but the point is, now all we had to do was to check if the action parameter is not empty, and add another where to our search query. Here, we use Linq to SQL which is different form Linq to object, but we can use Linq to object too, which means we can use IEnumerable instead of IQueryable, but using IQueryable here is more efficient, here is a good explanation of their difference, to put it briefly, Linq to SQL runs our query in the SQL Server, so we get only what we need, but Linq to object retrieves the whole entity and then filters it in memory.

With deferred execution that Linq gives us, we can build an IQueryable or IEnumerable and accumulate our wheres on it based on our conditions, this is far better than what we had before.

Share...
 

Hamid Mosalla

Hi, I'm Hamid Mosalla, I'm a software developer, indie cinema fan and a classical music aficionado. Here I write about my experiences mostly related to web development and .Net.