Refactoring LINQ with Tuples

10/12/10

Permalink 09:56:39 pm, by truewill Email , 503 words, 1088 views   English (US)
Categories: Tips, C#, .NET

Refactoring LINQ with Tuples

C#/.NET 4.0 has some nice features, including the Tuple classes. While generic tuples do not override the == operator, they do override Equals. This allows tuples composed of primitive types to be compared.

An interesting use of this is in refactoring LINQ queries, some of which can get quite long. The problem is that multiple-key joins are generally performed with anonymous types (because they override Equals/GetHashCode to compare properties), but anonymous types cannot be returned from methods (unless the return type is dynamic). Queries may also select anonymous types.

Tuples provide a neat solution to this. Here is a contrived example, which assumes legacy data with multi-column keys. (Note that this is LINQ to Objects, not LINQ to SQL.) I probably would not refactor to this degree in production code, as the Item1/Item2 portions are unclear. Note the elegant join in GetOrdersWithCustomers, though.

internal class Program
{
    private static void Main()
    {
        IEnumerable<Customer> customers = GetCustomers();
        IEnumerable<Order> orders = GetOrders();
        IEnumerable<Item> items = GetItems();

        /* Original:

        var query =
            from order in orders
            join customer in customers
                on
                new
                    {
                        order.CustomerType,
                        order.CustomerId
                    }
                equals
                new
                    {
                        CustomerType = customer.Type,
                        CustomerId = customer.Id
                    }
            join item in items
                on order.Id equals item.OrderId
            select
                new
                    {
                        OrderId = order.Id,
                        CustomerName = customer.Name,
                        item.Product
                    };
        */

        // Refactored:

        var ordersWithCustomers =
            GetOrdersWithCustomers(orders, customers);

        var query =
            from x in ordersWithCustomers
            join item in items
                on x.Item1.Id equals item.OrderId
            select
                new
                    {
                        OrderId = x.Item1.Id,
                        CustomerName = x.Item2.Name,
                        item.Product
                    };

        foreach (var record in query)
        {
            Console.WriteLine(record);
        }
    }

    private static IEnumerable<Tuple<Order, Customer>>
        GetOrdersWithCustomers(
        IEnumerable<Order> orders,
        IEnumerable<Customer> customers)
    {
        return
            from order in orders
            join customer in customers
                on order.GetCustomerKey()
                equals customer.GetKey()
            select
                new Tuple<Order, Customer>
                (order, customer);
    }

    private static IEnumerable<Item> GetItems()
    {
        return
            new List<Item>
                {
                    new Item
                        {
                            OrderId = 10,
                            Product = "TenOne"
                        },
                    new Item
                        {
                            OrderId = 10,
                            Product = "TenTwo"
                        },
                    new Item
                        {
                            OrderId = 20,
                            Product = "TwentyOne"
                        }
                };
    }

    private static IEnumerable<Order> GetOrders()
    {
        return
            new List<Order>
                {
                    new Order
                        {
                            Id = 10,
                            CustomerType = "A",
                            CustomerId = 1
                        },
                    new Order
                        {
                            Id = 20,
                            CustomerType = "A",
                            CustomerId = 2
                        }
                };
    }

    private static IEnumerable<Customer> GetCustomers()
    {
        return
            new List<Customer>
                {
                    new Customer
                        {
                            Type = "A",
                            Id = 1,
                            Name = "Bob"
                        },
                    new Customer
                        {
                            Type = "B",
                            Id = 1,
                            Name = "Hobo"
                        },
                    new Customer
                        {
                            Type = "A",
                            Id = 2,
                            Name = "Bill"
                        },
                    new Customer
                        {
                            Type = "B",
                            Id = 2,
                            Name = "Shotgun"
                        }
                };
    }
}

internal class Customer
{
    public string Type { get; set; }
    public int Id { get; set; }
    public string Name { get; set; }

    public Tuple<string, int> GetKey()
    {
        return new Tuple<string, int>(Type, Id);
    }
}

internal class Order
{
    public int Id { get; set; }
    public string CustomerType { get; set; }
    public int CustomerId { get; set; }

    public Tuple<string, int> GetCustomerKey()
    {
        return new Tuple<string, int>
            (CustomerType, CustomerId);
    }
}

internal class Item
{
    public int OrderId { get; set; }
    public string Product { get; set; }
}

Comments, Pingbacks:

No Comments/Pingbacks for this post yet...

Leave a comment:

Your email address will not be displayed on this site.
Your URL will be displayed.

Allowed XHTML tags: <p, ul, ol, li, dl, dt, dd, address, blockquote, ins, del, span, bdo, br, em, strong, dfn, code, samp, kdb, var, cite, abbr, acronym, q, sub, sup, tt, i, b, big, small>
(Line breaks become <br />)
(Set cookies for name, email and url)
(Allow users to contact you through a message form (your email will NOT be displayed.))
The name truewill is composed of two other words. What is the SECOND word?

Development Central

Development Central is the blog of Bill Sorensen, a professional software developer. Much of this will relate to C#, .NET, and OOP in general.

Disclaimer
These postings are provided "AS IS" with no warranties and confer no rights.

Search

Categories

Linkblog

b2evolution

contributors

XML Feeds

What is RSS?

Who's Online?

  • Guest Users: 3

powered by b2evolution free blog software