Search code examples
c#linqimmutability

How to create nested object graphs using Linq when the objects are immutable and reference their parent


In my attempt to write more side effect free code (using immutable classes) I've hit a bit of a brick wall when using linq to query a data source and select over parent and child relationships to create a new graph consisting of immutable objects.

The problem comes into play when I have a child object e.g. invoiceLineItem object that needs to have a reference to the parent object passed to it in its constructor, so that the .Parent property will reference the parent, e.g. lineItem[2].Parent references the Invoice.

I can't see how this can be done using linq when the classes are immutable. Linq and immutable classes are such big concepts in C# I believe i must be missing something obvious.

I will show some code that demonstrates the problem, first I will show using immutable classes that there appears to be no solution using Linq, then below that I will show how you can do it using mutable classes, which of course I do not want to do.

update : 17.05.18 I find it hard to believe that this is impossible, because if it is, then in my opinion thats a design flaw in the language, and the language is under quite intense scrutiny, ... much more likely I'm simply missing something.

Sample with Immutable classes ( the ??? ) is what I need to fix, how to pass a reference at that point to the containing class instance?

    void Main()
    {
        var nums = new[]{
                new { inv = 1, lineitems =new [] {new { qty = 1, sku = "a" }, new { qty = 2, sku = "b" }}},
                new { inv = 2, lineitems =new [] { new { qty = 3, sku = "c" }, new { qty = 4, sku = "d" }}},
                new { inv = 3, lineitems =new [] { new { qty = 5, sku = "e" }, new { qty = 5, sku = "f" }}}
        };

        // How do I pass in the reference to the newly being created Invoice
        // below to the Item constructor?

        var invoices = nums.Select(i => 
            new Invoice(i.inv, i.lineitems.Select(l => 
                new Item(l.qty, l.sku, ??? )
        )));

        invoices.Dump();
    }

    public class Invoice 
    {
        public int Number { get; }
        public IEnumerable<Item> Items { get; }
        public Invoice(int number, IEnumerable<Item> items) {
            Number = number;
            Items = items;
        }
    }

    public class Item 
    {
        public Invoice Parent { get; }
        public int Qty { get; }
        public string SKU { get; }
        public Item(int qty, string sku, Invoice parent) {
            Parent = parent;
            Qty = qty;
            SKU = sku;
        }
    }

The same classes but this time the DTOs are mutable, and we are able to solve passing in the reference by first creating the parent, then the children, then mutating the parent state by attaching the children that now have a reference to the parent set. I need to be able to do this using Immutable classes, but how?

void Main()
{
    var nums = new[]{
            new { inv = 1, lineitems =new [] {new { qty = 1, sku = "a" }, new { qty = 2, sku = "b" }}},
            new { inv = 2, lineitems =new [] { new { qty = 3, sku = "c" }, new { qty = 4, sku = "d" }}},
            new { inv = 3, lineitems =new [] { new { qty = 5, sku = "e" }, new { qty = 5, sku = "f" }}}
    };

    var invoices = nums.Select(i => 
    {
        var invoice = new Invoice() 
        { 
            Number = i.inv
        };
        var items = from item in i.lineitems select new Item() 
        { 
            Parent = invoice, Qty = item.qty, SKU = item.sku 
        };
        invoice.Items = items;
        return invoice;
    });

    invoices.Dump();
}

public class Invoice
{
    public int Number { get; set; }
    public IEnumerable<Item> Items { get; set; }
}

public class Item
{
    public Invoice Parent { get; set; }
    public int Qty { get; set; }
    public string SKU { get; set; }
}

I'm hoping I've missed something obvious, any help will be most appreciated. thank you Alan


Solution

  • I'm quite suprised that Linq doesnt support this, as bidrectional navigation in objects is essential for many graph type and in-memory queries. If you look at Table object from Linq2Sql or even Entity Framework, you'll see a lot of this type of bi directional relationships being setup. I think my mistake was referring to the classes as DTO's that was a massively overloaded word.

    So, assuming that birectionality is a requirement, and there are lots of reasons you would want that in a particular design, the problem still remains.

    I was hoping I was missing something and some smart person will come along and say, hey just do this, Linq has this func thing that points to (this) of the object being created. That would not surprise me.

    The best I could come up with in the end is very similar to what @gnud has described above, but with a slight twist to make the intent (and lack of language support) more obvious by adding in an AttachParent method, and only allowing that to be called once, see below:

    I've made the following changes

    • added public Invoice Parent { get; private set; } to the line item, but with a private setter.
    • added public void AttachParent(Invoice parent) { if(parent!=null) throw new InvalidOperationException("Class is immutable after parent has already been set, cannot change parent."); Parent = parent; } to LineItem.cs
    • lastly, changed the parent to be attached by the LineItem inside the constructor, but without having to clone the passed in items.

    I think that last point is really a matter of style for the team to decide how far they want to go with immutability. That's not something that's cast in concrete in my project.In my use case I'm dealing literally with billions of graph nodes, the Invoice and Item example is purely the smallest bit of code I could think of to demonstrate the Linq problem. needing to clone all the objects to set their parents in my use case would be expensive, and not necessarily lead to safer code.

    Instead of .AsReadOnly I'm simply using arrays, because the code is only going to be used by me, and the convention I'm using on my project is for arrays to represent a readonly collection, and arrays make the code super clean and easier to scan and instantly recognise what properties are readonly in signatures etc.

    .

        void Main()
        {
            var nums = new[]{
                    new { inv = 1, lineitems =new [] {new { qty = 1, sku = "a" }, new { qty = 2, sku = "b" }}},
                    new { inv = 2, lineitems =new [] { new { qty = 3, sku = "c" }, new { qty = 4, sku = "d" }}},
                    new { inv = 3, lineitems =new [] { new { qty = 5, sku = "e" }, new { qty = 5, sku = "f" }}}
            };
    
            // How do I pass in the reference to the newly being created Invoice below to the Item constructor?
    
            var invoices = nums.Select(i => 
                new Invoice(i.inv, i.lineitems.Select(l => 
                    new Item(l.qty, l.sku )
                ).ToArray()
            ));
    
            invoices.Dump();
        }
    
        public class Invoice 
        {
            public int Number { get; }
            public Item[] Items { get; }
            public Invoice(int number, Item[] items) {
                Number = number;
                Items = items;
                foreach(var item in Items) item.AttachParent(this);
            }
        }
    
        public class Item 
        {
            public Invoice Parent { get; private set; }
            public int Qty { get; }
            public string SKU { get; }
            public Item(int qty, string sku) {
                Qty = qty;
                SKU = sku;
            }
    
            public void AttachParent(Invoice parent) {
                if(Parent!=null) throw new InvalidOperationException("Class is immutable after parent has already been set, cannot change parent.");
                Parent = parent;
            }
        }
    

    Update : 10am 16May If you're wanting to go fully down the immutable class rabbit hole then using arrays is a big no-no. Please see my last comment at the bottom. The real focus of this question wasn't about writing immutable classes, but how to create a nested object that points to it's parent when using linq and the classes just happen to be immutable, i.e. where you want to do it with a single LINQ projection, nice and clean all in one go. :) Thanks all for the feedback and quick answers, you guys rock!