To WAQS or not to WAQS, that is the question – ASP.NET MVC Patterns comparison

I recently asked to some of my friends who didn’t use WAQS yet why.

I got different replies.

One of them was replied by many of them: I just develop basic ASP.NET MVC applications so I think WAQS is useless / overkill.

 

So I defined with them the following scenario.

Using Northwind, we have five entities: Customer, Order, OrderDetail, Product and Employees.

We want to show the ten best orders per employee with a link to the list of their details.

In this list, we add a column Total and a column CustomerName which is equal to Customer.CompanyName + " – " + Customer.ContactName.

In the order details list, we add a column Amount, a column CustomerName, a column OrderDate and a column ProductName.

 

So how to do it with ASP.NET MVC?

 

Frequent patterns

We have many ways to do it. In my case (with my poor knowledge of ASP.NET MVC), I think about 3 ways:

1. Querying on controllers

For this option, we use some view models that encapsulates our entities:

public class OrderViewModel
{
    public Order Order { get; set; }
    public string CustomerName { get; set; }
    public double Total { get; set; }
} public class OrderDetailViewModel
{
    public OrderDetail OrderDetail { get; set; }
    public string CustomerName { get; set; }
    public DateTime OrderDate { get; set; }
    public string ProductName { get; set; }
    public double Amount { get; set; }
}


Then, we use the following methods in our controller:



public class EmployeeController : Controller
{
    private NorthwindWAQSEntities db = new NorthwindWAQSEntities();

    public async Task<ActionResult> Get10BestOrders(int employeeId)
    {
        return View("Orders",
            await (from o in db.Orders
                   where o.EmployeeId == employeeId
                   let total = o.OrderDetails.Sum(od => od.Quantity * od.UnitPrice * (1 - od.Discount))
                   orderby total descending
                   select new OrderViewModel
                   {
                       Order = o,
                       CustomerName = o.Customer.CompanyName + " " + o.Customer.ContactName,
                       Total = (double?)total ?? 0
                   }).Take(10).ToListAsync());
    }
    public async Task<ActionResult> GetOrderOrderDetails(int orderId)
    {
        return View("OrderDetails",
            await db.OrderDetails.Where(od => od.OrderId == orderId).Select(od =>
                new OrderDetailViewModel
                {
                    OrderDetail = od,
                    CustomerName = od.Order.Customer.CompanyName + " " + od.Order.Customer.ContactName,
                    OrderDate = od.Order.OrderDate,
                    ProductName = od.Product.Name,
                    Amount = od.Quantity * od.UnitPrice * (1 - od.Discount)
                }).ToListAsync());
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
            db.Dispose();
        base.Dispose(disposing);
    }
}


It is rather easy even if we have some traps like the fact to think to cast our sum into nullable of double because of SQL logic if an order don’t have any detail.



The worst point IMO is the fact that we have code duplication in our queries which means that if we want to change the way to calculate order detail amount, for example, we have to change all controllers methods using it which is really not great for maintainability.



 



2. In memory calculation



To factorize our code, we can get calculated properties values in memory after loading their entity dependent graph.



To do it, we can use Eager loading with Entity Framework Include method (ASP.NET MVC Scaffolding does it for example), lazy loading or we can use many queries (one per entity type). As I already explained in my blog the two first ones could have a catastrophic impact on performances. But even with the last one, it is not great to load useless data. For example, to get order detail product name, we need to load order details product. But we don’t use product properties except ProductName. So it’s a shame to load them.



 



3. Working with expressions



Another way to factorize our code in our queries is to work with expressions.



So for example, we can use this code for GetOrdersFromEmployeeId:



public async Task<IEnumerable<OrderViewModel>> Get10BestOrders(int employeeId)
{
    return await _context.Orders
.Select(GetOrderViewModelFromOrderExpression ())
.OrderByDescending(o => o.Total)
.Where(o => o.Order.EmployeeId == employeeId)
.Take(10)
.ToListAsync();
}
private static Expression<Func<Order, OrderViewModel>> _orderViewModelFromOrderExpression;
internal static Expression<Func<Order, OrderViewModel>> GetOrderViewModelFromOrderExpression ()
{
    if (_orderViewModelFromOrderExpression != null)
        return _orderViewModelFromOrderExpression;
    var oParameter = Expression.Parameter(typeof(Order));
    _orderViewModelFromOrderExpression =
        Expression.Lambda<Func<Order, OrderViewModel>>(
            Expression.MemberInit(
                Expression.New(typeof(OrderViewModel)),
                Expression.Bind(typeof(OrderViewModel).GetProperty("Order"), oParameter),
                Expression.Bind(typeof(OrderViewModel).GetProperty("CustomerName"),
                    GetCustomerNameExpression (GetCustomerName(oParameter))),
                Expression.Bind(typeof(OrderViewModel).GetProperty("Total"),
                    Expression.Coalesce(
                        Expression.Convert(
                            GetTotalExpressionBody(oParameter),
                            typeof(double?)),
                        Expression.Constant(0D, typeof(double))))),
            oParameter);
    return _orderViewModelFromOrderExpression;
}
internal static Expression<Func<Order, double>> GetTotalExpression ()
{
    var oParameter = Expression.Parameter(typeof(Order));
    return Expression.Lambda<Func<Order, double>>(
        GetTotalExpressionBody(oParameter),
        oParameter);
}
internal static Expression GetTotalExpressionBody(Expression oParameter)
{
    return Expression.Call(
        typeof(Enumerable).GetMethods().First(m => m.Name == "Sum" && m.ReturnType == typeof(double) && m.IsGenericMethod).MakeGenericMethod(typeof(OrderDetail)),
            Expression.MakeMemberAccess(
                oParameter,
                typeof(Order).GetProperty("OrderDetails")),
            GetAmountExpression ());
}
internal static Expression GetCustomerName(Expression oParameter)
{
    var customer = Expression.MakeMemberAccess(oParameter, typeof(Order).GetProperty("Customer"));
    return GetCustomerNameExpression (customer);
}
internal static Expression GetCustomerNameExpression (Expression cParameter)
{
    return Expression.Call(
        typeof(string).GetMethod("Concat", new Type[] { typeof(string), typeof(string), typeof(string) }),
        Expression.MakeMemberAccess(cParameter, typeof(Customer).GetProperty("CompanyName")),
        Expression.Constant(" - ", typeof(string)),
        Expression.MakeMemberAccess(cParameter, typeof(Customer).GetProperty("ContactName")));
}
internal static Expression<Func<OrderDetail, double>> GetAmountExpression ()
{
    var odParameter = Expression.Parameter(typeof(OrderDetail));
    return Expression.Lambda<Func<OrderDetail, double>>(
        GetAmountExpressionBody(odParameter),
        odParameter);
}
internal static Expression GetAmountExpressionBody(Expression odParameter)
{
    return Expression.Multiply(
        Expression.Convert(
            Expression.MakeMemberAccess(odParameter, typeof(OrderDetail).GetProperty("Quantity")),
            typeof(double)),
        Expression.Multiply(
            Expression.MakeMemberAccess(odParameter, typeof(OrderDetail).GetProperty("UnitPrice")),
            Expression.Subtract(
                Expression.Constant(1D, typeof(double)),
                Expression.MakeMemberAccess(odParameter, typeof(OrderDetail).GetProperty("Discount")))));
}


So the great thing here is the fact that we don’t duplicate our business code and we don’t load useless data but the issue is the fact that most of developers are not familiar with LINQ expressions and so this code will be hard to write and maintain for them.



 



WAQS



So now what about WAQS?



After installing WAQS NuGet server package in our ASP.NET MVC Application and executing the generation command, we define our business code (named “specifications” in WAQS vocabulary):



public static class NorthwindSpecifications
{
    public static string GetCustomerName(this Order o)
    {
        return o.Customer.CompanyName + " " + o.Customer.ContactName;
    }

    public static string GetCustomerName(this OrderDetail od)
    {
        return od.Order.GetCustomerName();
    }

    public static DateTime GetOrderDate(this OrderDetail o)
    {
        return o.Order.OrderDate;
    }

    public static string GetProductName(this OrderDetail od)
    {
        return od.Product.Name;
    }

    public static double GetAmount(this OrderDetail od)
    {
        return od.Quantity * od.UnitPrice * (1 - od.Discount);
    }

    public static double GetTotal(this Order o)
    {
        return o.OrderDetails.Sum(od => od.GetAmount());
    }
}


After building the project, we can regenerate WAQS T4 files. //We can execute UpdateWCFAsyncQueryableServicesServerT4Templates powershell command in Package Manager Console to do it.



Then, we just need to use this code in our controller:



public class EmployeeController : Controller
{
    private NorthwindWAQSService db = NorthwindWAQSServiceL2E.Create<NorthwindWAQSEntities>();

    public async Task<ActionResult> Get10BestOrders(int employeeId)
    {
        return View("Orders",
            await (from o in db.Orders
                   where o.EmployeeId == employeeId
                   orderby o.Total descending
                   select o).Take(10).WithTotal().WithCustomerName().ToListAsync());
    }

    public async Task<ActionResult> GetOrderOrderDetails(int orderId)
    {
        return View("OrderDetails",
            await db.OrderDetails.Where(od => od.OrderId == orderId).WithCustomerName().WithOrderDate().WithProductName().ToListAsync());
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
            ((IDisposable)db).Dispose();
        base.Dispose(disposing);
    }
}


So, using WAQS, we only load what we really need with a very basic way, writing our code only once.



 



 



Conclusion



IMHO WAQS is the best option to develop our basic ASP.NET MVC Application.



In addition, note that the more complex our data model and our business code are, the more interesting WAQS is.



 



Hope that helps. You have no more excuse now guys :)

This entry was posted in 16868, 18472. Bookmark the permalink.

2 Responses to To WAQS or not to WAQS, that is the question – ASP.NET MVC Patterns comparison

  1. Jalal Hezeb says:

    Why use this when there is already Entity Framework do the same thing?

  2. Matthieu MEZIL says:

    @Jalal : Did you read my post?
    Entity Framework do NOT do the same thing.

    In my first sample, I use Entity Framework only and I have code duplication on my queries.

    Using WAQS as a super layer on top on EF, I have a better way to query.

Leave a Reply

Your email address will not be published. Required fields are marked *


*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>