Reputation: 1119
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
Upvotes: 0
Views: 484
Reputation: 191
Had a similar challenge with immutable child and parent records. Solved it like this:
// Dummy static instance of parent class somewhere in static class
// to reduce dumb memory allocations count.
public static class DummyFactory
{
public static readonly ParentClass DummyParentClass = new([]);
}
public record ParentClass
{
public ChildClass[] Children { get; init; }
public ParentClass(ChildClass[] children)
{
Children = children.Select(c => c with { Parent = this }).ToArray();
}
}
public record ChildClass
{
public ParentClass Parent { get; init; }
public string SomeProp { get; init; }
public ChildClass(string someProp)
{
Parent = DummyFactory.DummyParentClass;
SomeProp = someProp;
}
}
Not the most efficient way in terms of memory allocations and performance, but when code simplicity matters more then could be used. Should work within LINQ also.
Key element is the "with" keyword that appeared in C# 10, but the whole child record can also be re-created with alternative constructor. Collection expressions ([ and ]) are from C# 12.
Upvotes: 0
Reputation: 1119
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
public Invoice Parent { get; private set; }
to the line item, but with a private setter.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.csI 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!
Upvotes: 0
Reputation: 78608
The 'Invoice' object doesn't exist until after the constructor is run. There's no way to refer to it when creating the arguments for the constructor. This has nothing to do with LINQ.
Personally, I think I would drop the link to the Parent completely. When do you actually need it?
If a Line can exist without a parent, you can create them with null
parents, and then create a new Item with the parent, inside the Invoice constructor.
public class Invoice
{
public int Number { get; }
public IReadOnlyList<Item> Items { get; }
public Invoice(int number, IEnumerable<Item> itemsWithoutParent)
{
Number = number;
Items = itemsWithoutParent
.Select(x => new Item(x.Qty, x.SKU, this))
.ToList().AsReadOnly();
}
}
If you don't want the possibility of Line's without parents, then the Invoice constructor must take a sequence of Line factory functions, instead of Line objects.
public class Invoice
{
public int Number { get; }
public IReadOnlyList<Item> Items { get; }
public Invoice(int number, IEnumerable<Func<Invoice,Item>> items)
{
Number = number;
Items = items
.Select(x => x(this))
.ToList().AsReadOnly();
}
}
/* usage */
var invoices = nums.Select(i =>
new Invoice(i.inv, i.lineitems.Select(l =>
(Func<Invoice,Item>)(parent => new Item(l.qty, l.sku, parent))))
).ToList();
Just to repeat, I would prefer to remove the Parent property completely from your DTOs.
Upvotes: 1