Joining two datatables as parent-child in linq - c#

I need to build a typed list of parent-child objects that are read from two different Excel sources: One describes parent object, another describes child objects. The hierarchy is only 2 layers ever.
Reading into excel is not the issue, as it is read into 2 untyped datatables, but joining the information is.
The structure is very plain:
Parent has an ID and some text fields
Children have a parentID (so its 1-m) and some text fields
The objects that these are to be populated into looks like this:
public class ParkingSites
{
public List<ParkingLot> Lots { get; set; }
public ParkingSites(List<ParkingLot> arg)
{
Lots = arg;
}
}
public class ParkingLot
{
public List<Bay> Bays{ get; set; }
public int Id { get; set; }
public List<string> ParkingLotDetails { get; set; }
public ParkingLot()
{
}
}
public class Bay
{
public List<string> BayDetails { get; set; }
public int ParentId { get; set; }
public Bay()
{
}
}
The excel sources have a fixed column order with the parent sheet's first column being the parentId, and the first column on the child sheet also being the parentId.
EDIT: After playing around a bit, I just made both parent and child classes typed, as the initial reason for leaving them mostly untyped lead to more problems than it prevented. This is part of a larger project where the untypedness is a better solution for our problem on the other classes with data that is not hierarchial.

You can simply group the list of children by the parent id, and then iterate over the parents and add each child that belongs to it.
For example, you could use ToLookup:
// assuming you have all Bay instances in a collection called bays
var baymap = bays.ToLookup(b => b.ParentId);
// and all ParkingLot instances in a collection called lots
foreach(var lot in lots)
lot.Bays.AddRange(baymap[lot.Id]);
or, using the first element in the details lists:
var baymap = bays.ToLookup(b => b.BayDetails[0]);
foreach(var lot in lots)
lot.Bays.AddRange(baymap[lot.ParkingLotDetails[0]]);
or, using Where without a lookup (probably slower, depends on your data):
foreach(var lot in lots)
lot.Bays.AddRange(bays.Where(b => b.ParentId == lot.Id));

Related

EF Core, include derived type property

I have a small problem with a specific include statement.
My datastructure is as follows:
[Table("item")]
public class Item
{
public int Id { get; set; }
public string ItemCode { get; set; }
...
}
public abstract class DerivedItemAbstractBase : Item
{
[ForeignKey("ItemId")]
public List<Assignment> Assignments { get; set; }
...
}
[Table("item")]
public class DerivedItemA : DerivedItemAbstractBase
{
...
}
[Table("item")]
public class DerivedItemB : DerivedItemAbstractBase
{
...
}
public class ItemContext : DbContext
{
public DbSet<Item> Items { get; set; }
...
}
Now I want to get a list of all DerivedItemA and include properties of it.
I have the following method:
public List<DerivedItemA> GetDerivedItemsA()
{
var list = _context.Items
.Include(x => (x as DerivedItemA).Assignments)
.ToList();
}
This code compiles just fine and is something I have found on stackoverflow.
However executing this results in an exception with the short message Invalid include.
I dont know how to solve this problem.
The project is a database-first approach so I have no control over the database.
All items are stored in the same table item.
I cannot create multiple DbSets because there is no discriminator column in the table and I
cannot configure a custom discriminator in code because it would need to discriminate based on multiple properties and not a single property.
Is there any other way of doing this?
Currently I am solving it by iterating through all ItemContext.Items then doing a .Select() on each and creating a new DerivedItemA. After that I manually set every Assignment by iterating from the Assignment table.
However this approach takes far too long and it would be a lot quicker if I could just include it in the initial query.

Avoiding transposed parameters without creating coupling

I'm currently building a test application that manages parts for engineers, and I've hit a snag. I have a few different classes, including PartsModel and EngineerModel, and I want to update a list of parts that an engineer has, but I'm mindful of issues from either transposed parameters or from structuring the code in a way that unnecessarily couples to a particular class.
The two classes, with some relevant properties:
public class PartModel
{
public int PartId { get; private set; }
public string PartTitle { get; set; }
public string PartDescription { get; set; }
public int Quantity { get; set; }
public int MinimumStock { get; set; }
public void AddToStock (int quantityToAdd) {
Quantity += quantityToAdd;
}
public void RemoveFromStock (int quantityToRemove) {
Quantity -= quantityToRemove;
CheckMinimumStock();
}
}
public class EngineerModel
{
public int EngineerId { get; private set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public List<PartModel> PartsInStock { get; set; } = Factory.CreatePartsList();
}
As you can see, each engineer has a list of parts they have in stock via a List<PartModel>. I want to pass another list to this one so that I can update it respectively (incrementing or decrementing quantities, and then adding or removing parts to the list as necessary).
The first warning bell is that it takes two inputs of the same type, and is going to fill one from the other one (which isn't needed afterwards), so you're essentially modifying one input and destroying the other. To me, this presents a danger of the inputs getting transposed and the wrong list being either returned or updated (depending on whether it returns or just acts on the list). Because it removes items that have no quantity, it can't check the list length and just update the longer one, because there are possible cases where the engineer's list is shorter (maybe they're a new engineer, or maybe they just had a large shipment of parts sent when they were running low on stock). If it did just keep parts with quantity zero, then you're threatening scalability of both engineers and parts (not to mention any other objects that use the same operation).
So, put it as a method in the EngineerModel class and operate on PartsInStock, right? But what about when I want to use the same operation on other classes (e.g. if I have a list of parts associated to a work task)? Then I extract the method out to another class and... I'm passing the two lists as parameters in the method, so I'm back to where I was.
Am I being reasonable in not wanting to have two parameters of the same type, and how do I structure the code to deal with this, but without creating unnecessary coupling? If I'm not being reasonable, what am I overlooking?
Use an extension method
Thanks to #DavidBrowne-Microsoft for clarifying this. By defining an extension method for List<PartModel>, it only needs the one parameter - the list containing the updates (foreach below based on #Valentin's answer to this question).
public static class PartsHandler
{
public static List<PartModel> UpdateStockQuantitiesWith(this List<PartModel> stockToBeUpdated, List<PartModel> stockUpdates) {
foreach ( var part in stockUpdates )
{
var partToBeUpdated = stockToBeUpdated.FirstOrDefault(x => x.PartId == part.PartId);
if ( partToBeUpdated != null )
{ partToBeUpdated.Quantity += part.Quantity; }
else
{ stockToBeUpdated.Add(part); }
}
stockToBeUpdated.RemoveAll(x => x.Quantity <= 0);
return stockToBeUpdated;
}
}
Now any class that needs to implement this can simply call it in a method on the respective property. For example, in the EngineerModel class, it can operate on the PartsInStock property:
public void AddPartsToStock(List<PartModel> partsSent) {
PartsInStock.UpdateStockQuantitiesWith(partsSent);
}

C# - Traversing Nodes of a Tree and Identify level

I have an application that allows a user to select items from a tree structure that are then passed to a method that builds a report. The item is the smallest level of granularity and contains an id value. I need to produce an enumerable object that can be used to produce both summery level reports and individual item reports. For example, the picture bellow shows the tree and the desire report structure.
What I need is to be able to identify or flag items that need to be printed individually. In other words if item is not found in any child groups then mark it as needs to print individual. This is important because it is possible for the item to exist in more than one grouping. How can I parse through the tree and check if the item exists in a child node?
The Item and ItemSet(groups) classes are as follows:
public class Item
{
public int Property1 { get; set; }
public int Property2 { get; set; }
public int Property3 { get; set; }
public int Property4 { get; set; }
}
public class ItemSet
{
public List<Item> Items { get; set; }
//.
//.
//.
//.
public List<ItemSet> ItemSets { get; set; }
}
Notice that an itemset can contain other item sets. Also, the list of Items includes all items including item in children. I need a mechanism to check for if the item exists at that level alone.
UPDATE
To further clarify, I have added summarized class diagrams to illustrate the relationships/composition of Items, ItemSets, and the application as a whole.This is a legacy code base so and so i do not have much flexibility and must make do with most of what is in existence. When getAllItems() is called on a partiular set, it returns all of its decedents, even if it is an item the belongs to a nested set. I need to be able to determine if it is a direct decedent or a child of a sub set.
foreach (var item in itemSet.getAllItems())
{
if (item.Parent == itemSet)
{
// Is child of itemSet
}
else
{
// Is descendant of nested itemSet
}
}

How to solve "Expected element name to be '_t', not 'number'."

I have a mongo model like this:
class ObjectA {
[BsonId(IdGenerator = typeof(BsonObjectIdGenerator))]
public BsonObjectId Id;
[BsonElement("number")]
public int Number { get; set; }
[BsonElement("b")]
public List<ObjectB> objectB { get; set; }
}
class ObjectB {
[BsonElement("someProperty")]
public string SomeProperty { get; set; }
}
My problem is when I aggregate the collection with {$unwind:objectB}. The result documencts have a unique object on the property objectB (not a list).
So the cast failes with the exception:
An error occurred while deserializing the ObjectB property of class
ObjectA: Expected element name to be '_t', not
'number'.
Do I have to create a new model for this or is there a easier way to solve it?
You could also choose to work with BsonDocument directly (but that is not strongly typed and more cumbersome to work with), e.g. (I'm using the simple Posts/Tags example here)
var aggregationResults = db.GetCollection("Posts").Aggregate().ResultDocuments;
foreach (var document in aggregationResults)
{
var tag = document.GetValue("Tags").AsString;
}
Unlike the normal query and projection operators, the aggregation framework may change the structure of your document. As you already pointed out, $unwind transforms a document that contains an array into a number of documents that each have a single value of the same name.
Another approach this is to indeed create a new type for this, so
class Post {
public List<string> Tags { get; set; }
...
would become
class PostAggregationResult {
public string Tags { get; set; }
...
That is very easy to work with, but if you have very various aggregation queries, you need a large number of classes which can be annoying.

How do I turn a one-to-many relationship into a list property using SauceDB?

Using SauceDB, how do I turn a one-to-many relationship, between say table "A" and table "B" respectively, into a list property (containing B objects) in the class that corresponds to table A? The relationship is represented by a foreign key in table B referring to table A (so that many B records can belong to one A record).
Sauce does not support Linq2SQL style navigation properties. However, there are two supported ways to work around this depending on your requirements.
1) Just do the join in your code
IDataStore dstore = .GetDataStore();
var query = from i in dstore.Query<MyTable>()
join x in dstore.Query<MyTable>() on i.Name equals x.Name
select new { };
2) Another way to do it is as follows, and gives a more Navigation Property Style use. Modify your object definition to contain a list and use an [AdditionalInit]
public class Foo
{
public int ID { get; set; }
public string Name { get; set; }
[IgnoredField]
public List<Bar> Bars { get; set; }
[AdditionalInit]
private void LoadBars(IDataStore dstore)
{
Bars = dstore.Query<Bar>().Where(r=> r.Foo = this.ID).ToList();
}
}
That should do what you seek, if you have any more questions let me know.
I found that I could use the AdditionalInit attribute in order to define a hook which gets called as a database object gets initialized. Since this hook can accept a data store, I can deduce the one-to-many relationship right there.
Here's the relevant excerpt of class A:
public class A
{
...
public List<B> Bs { get; private set; }
[AdditionalInit]
public void OnInit(IDataStore dstore)
{
Bs = dstore.Query<B>().Where(b => b.A.Id == Id).ToList();
}
}
Bear in mind I haven't been able to test this code yet.

Categories