linq and the dry principle

I found myself today working on a LINQ query that, in my opinion violated the DRY principle.

 

 

Don't Repeat Yourself (DRY, also known as Single Point of Truth) is a process philosophy aimed at reducing duplication, particularly in computing. The philosophy emphasizes that information should not be duplicated, because duplication increases the difficulty of change, may decrease clarity, and leads to opportunities for inconsistency. DRY is a core principle of Andy Hunt and Dave Thomas's book The Pragmatic Programmer . They apply it quite broadly to include "database schemas, test plans, the build system, even documentation."[1] When the DRY principle is applied successfully, a modification of any single element of a system does not change other logically-unrelated elements. Additionally, elements that are logically related all change predictably and uniformly, and are thus kept in sync.

(Source Wikipedia)

 

It’s such a simple principle, and can be really effective if you apply it properly.

In TSQL stored procedures, it was often quite hard to prevent duplication. Consider the following example:

    1: SELECT FIRSTNAME, LASTNAME, ADDRESS
    2: FROM PERSON
    3: WHERE ID = @ID
    4:  
    5: SELECT FIRSTNAME, LASTNAME, ADDRESS
    6: FROM PERSON
    7: WHERE LASTNAME LIKE '@NAMEFILTER' + "%"
    8:  
    9: SELECT FIRSTNAME, LASTNAME, ADDRESS
   10: FROM PERSON
   11: WHERE LASTNAME LIKE '@NAMEFILTER' + "%"
   12: AND AGE BETWEEN 18 AND 24

At first, there doesn’t seem to be too much wrong with these queries.

However, they grossly violate the DRY principle. But in TSQL, it’s hard to reduce this duplication. Sure, you can create helpers like views, SQL functions or table valued functions, but it still is quite hard. Unfortunately, this means that some people feel this is a ‘get-out-of-jail-free-card’ to write long and ugly code. If you have ever had to debug a query that spanned 3 pages in the query analyzer, you feel my pain.

Now with C# 3.0 and LINQ, we get the power of a SQL like query language, right in our C# code. Unfortunately, I’ve seen too often that people just ignore the DRY principle again when creating LINQ queries.

Calling methods from LINQ queries

Yes, you can call methods from LINQ queries. For example, I noticed the following lines of code:

    1: var match = from discountRow in this.pricingDataSet.Discount
    2:             where discountRow.Id.ToString() == id
    3:             select new Discount
    4:             {
    5:                 Id = discountRow.Id.ToString(),
    6:                 PartnerId = discountRow.PartnerId.ToString(),
    7:                 ProductSku = discountRow.ProductSku.ToString(),
    8:                 Name = discountRow.Name
    9:             };
   10:  
   11: var match = from discountRow in this.pricingDataSet.Discount
   12:              where discountRow.Name == name
   13:              select new Discount
   14:              {
   15:                  Id = discountRow.Id.ToString(),
   16:                  PartnerId = discountRow.PartnerId.ToString(),
   17:                  ProductSku = discountRow.ProductSku.ToString(),
   18:                  Name = discountRow.Name
   19:              };

And there were 3 other queries just like it. The filter condition was different, but the select statement identical. So what do you do in a case like this? Right, remove the duplication and call a creation method instead:

    1: public Discount GetDiscountById(string id)
    2: {
    3:     var match = from discountRow in this.pricingDataSet.Discount
    4:                 where discountRow.Id.ToString() == id
    5:                 // Call a method here, instead of duplicating the creation logic everywere
    6:                 select CreateDiscount(discountRow);
    7:  
    8:     return match.FirstOrDefault();
    9: }
   10:  
   11: private Discount CreateDiscount(PricingDataSet.DiscountRow discountRow)
   12: {
   13:     return new Discount
   14:     {
   15:         Id = discountRow.Id.ToString(),
   16:         PartnerId = discountRow.PartnerId.ToString(),
   17:         ProductSku = discountRow.ProductSku.ToString(),
   18:         Name = discountRow.Name,
   19:         Value = discountRow.Value
   20:     };
   21: }

This makes things a lot cleaner to read :)

Composing LINQ queries

Ok, so you can call methods from LINQ. When you create a LINQ query, you are actually creating an expression tree, that will only get resolved the first time you actually execute the query. This allows you to really fun stuff. Look at this example:

    1: var query = from discountRow in this.pricingDataSet.Discount
    2:                   select discountRow;
    3:  
    4: if (id != null)
    5: {
    6:     // Only apply filtercondition when needed
    7:     query = from discountRow in query
    8:             where discountRow.Id.ToString() == id
    9:             select discountRow;
   10: }
   11:  
   12: var match = from discountRow in query
   13:             select CreateDiscount(discountRow);
   14:  
   15: return match.FirstOrDefault();

In this example, I’m only applying the filter condition if I need to have it applied. Try doing that in TSQL! OK, in this example, I haven’t really improved the code, but with more complicated queries, you can do really cool stuff! You can create several filter methods, which you can reuse and between lots of similar queries.

 

Conclusion

LINQ might look like SQL, but in fact, it’s much more flexible and much less dry!

Comments