Sunday, March 22, 2020

So You Have To Work From Home --- Now What?


The work from home (WFH) experience is going to be new to a whole lot of people, but not for me. I've been working from home (off and on) for more than 30 years after having a few kids in the early 80's. In the late 80's, I was asked if I could write some software (to interface with cash registers and produce back-end reports) and I said sure (with no experience at all with cash registers)! When I was done writing that successful product, I started my own software business from home.

Everything was quite different in those days. I connected to "chat rooms" for technical information using a modem. Those chat rooms eventually became "forums" (where I made many friends). The Internet was there (although it was typically referred to as the World Wide Web), but it was nothing like it is today!

My business morphed into software contracting and finally (after being tired of being a "starving programmer"), I worked as an employee at a number of companies over the next 20 years ... some companies insisted that everyone work on the premises, others (the enlightened companies) allowed work from home. All in all, I've worked from home much more often than not.

In the last 30+ years, I've learned a lot about being productive in a home environment. These things apply to everyone who has to work from home, whether you're an office worker, in management or in the tech industry.

Get A Room!

First, you've got to have a space dedicated to work. A separate room is preferable, but not always possible. I've been lucky to have a separate room that I could dedicate as my "office space" in every house (or apartment) that I've lived in. Sometimes, you just *have to* be able to shut the door!!  ;0) 

At the very least, you should have a space where you can spread out all your work stuff and keep it there. Setting up your work stuff on the dining room table, that needs to be used for eating dinner every evening, is not an ideal set up. But, if it's all you've got, you've got to at least be organized. Maybe keep your stuff in a large Rubbermaid container, or something similar, that can easily be moved.

Get Connected

Every job these days entails having a computer, if only for email if nothing else. You'll most likely need a way to connect to the company network from home. The best way is through a VPN (Virtual Private Network, which is highly recommended for security purposes). There are other methodologies for connecting, such as TeamViewer and LogMeIn. Your company IT people should be able to help you get going with the setup for remote connections.

Endless Meetings? Nah!

You'll also need to be able to conduct and/or attend virtual meetings. It can be done solely with phone conference calls, but frequently a meeting needs to have the ability to share screens or have a camera to show a whiteboard or other things that aren't on a computer (and also, to see your co-workers "face-to-face"). There are plenty of virtual meeting software options, such as Skype, GoToMeeting, Microsoft Teams and more.  

The up-side to this is that you and your co-workers can probably do away with a lot of excess meetings, since a lot of the communication can be easily accomplished with email. If that's not working for a particular problem, for whatever reason, then don't hesitate to have a phone call or use the meeting tools.

There are a lot of distractions in an office environment (people popping their head in your office ... hey, ya got a minute for a quick question?) These kinds of distractions interrupt your work, but they're so common in an office environment that people don't even think about them as disruptive to your concentration.

The company that I currently work for (we all work remotely) has instituted  "Office Hours" from 9:00-10:00 AM and from 2:00-3:00 PM ... during each of those one hour time slots, everyone is fair game for interruptions with work-related questions. And when you're really busy, you can ignore email outside of those two time slots and address them only during the appropriate time slot (unless, of course, it's an urgent matter that needs addressing immediately).

Balance Working Hours With Private Hours

Figure out what will make you feel like you're "at work". Some people like to get dressed in work clothes every morning (well, maybe "Casual Friday" kind of work clothes). For some, it's that first cup of coffee, or sorting through your email or reading a work-related website. For me, it's the coffee ... I usually work in my bathrobe for a couple of hours first.  (I know, TMI ... ha ha)

You should figure out what your work hours will be and then stick to it! If you don't, it can impact you in two different ways:

One way is distraction ... oh, I just need to do this one thing first before I start work (which may balloon to a few more things), etc.  Well, think about it ... when you actually had to drive to the office every day, that "one thing" didn't get done before you went to work, did it? So, don't make it any different now that you're home all day. (Although, granted, you might have to be more flexible if you have children who are home from school).

The flip side of distraction is working too much. It's an easy trap to fall into ... your work is right there at home and it's easy to be tempted to keep working longer than you would at the office. Don't do it ... at the end of your self-appointed office hours, that's it. Don't even think about work. Now is your personal/family time and work will wait until tomorrow morning. 

Bottom line: it's all about good work habits. In other words, discipline!

Make Time For Yourself

Take a lunch break. A real lunch break ... don't make a sandwich and take it back to eat at your desk. Eat lunch in the kitchen, or on your back deck/balcony/whatever if it's a nice day.

Exercise. You'll be a lot more sedentary working from home (without those trips to the water cooler to gossip with your co-workers). Take a quick 10 or 15 minute walk, run or bike ride at lunchtime or mid-afternoon. If the weather's not cooperating, just walking around your house will work too (especially if you have stairs you can go up and down).

And Finally ...

This emergency situation won't last forever (hopefully). You may find that working from home suits you just fine! You may find that you're way more productive at home than you could ever be at the office (without sacrificing your "me time"). And your employer may discover that many of their employees *can* work productively from home and start allowing it on a regular basis.

Stay Safe!  =0)









Saturday, February 29, 2020

Organize Your Projects With Partial Classes

I was recently asked if I had any type of procedure or process that I follow for creating Classes. I responded, "No, I don’t have anything concrete for doing that. Sometimes whatever you’re trying to accomplish can dictate how you will handle creating classes".

But here's one tip that I find can often be useful and that is, as the title of this post indicates, using partial classes for project organization. It can also be useful when you have a few different team members that need to work on different parts of the class. Let me explain ...

Let's say you have an application that processes various types of messages that it receives. This is what MyMessage class might look like:

public class MyMessage
{
public string MessageType { get; set; }
public string MessageData { get; set; }
public DateTime CreatedDatetime { get; set; }
public string ReceivedFrom { get; set; }
public string SendTo { get; set; }
}

Now, let's say that you have three categories of messages: Incidents, Requests, Resources. You might organize this by having four partial class files. These four files could be named MessageProcessor.cs, MessageIncidentProcessor.cs, MessageResourceProcessor.cs and MessageRequestProcessor.cs. Note that all these files start with the word "Message" ... this effectively groups them all together in your project making it obvious that they belong together and making them easy to find in your project.

One could also use project folders for organizing similar functionality, but I often find that it's easier to find what you're looking for by grouping by name of file and not "hiding" files in folders. Maybe that's just me.  ;0) It probably also depends on the size of your project (if you have a lot of files in your project, perhaps folder organization is better). However, that is *NOT* the topic of this blog post! It's about partial classes!  =0)

As you may have guessed by now, the partial class will be called MessageProcessor and the MessageProcessor.cs file will contain all the variables and methods that are common between the various MessageTypes. Any method that is common to at least two MessageTypes belongs here. The main functionality of the code in the MessageProcessor.cs file is to receive the message, and then call the appropriate method to process it (which will be methods in the other .cs files).

OK, now for the code:

First, the "controlling" MessageProcessor.cs partial class file.

public partial class MessageProcessor
{
// Note that the constructor is only in this "main" class file
public MessageProcessor()
{
// initialize stuff here, probably having to do with setting up
// for receiving messages, and whatever else all three message types
// have in common for
}
// Next, we'll have all methods that are common to all types of Messages
// The first being processing the incoming message and calling the appropriate methods
public bool ProcessMessage(MyMessage message)
{
bool IsOK = true;
switch (message.MessageType)
{
// Incidents
case "NewIncident":
IsOK = this.ProcessNewIncident(message);
break;
case "UpdateIncident":
IsOK = this.ProcessIncidentUpdate(message);
break;
case "CloseIncident":
IsOK = this.ProcessIncidentClose(message);
break;
// Resources
case "NewResource":
IsOK = this.ProcessNewResource(message);
break;
case "AssignResource":
IsOK = this.ProcessResourceAssignment(message);
break;
case "UpdateResource":
IsOK = this.ProcessResourceUpdate(message);
break;
// Requests
case "RequestForTransfer":
IsOK = this.ProcessTransferRequest(message);
break;
default:
// some default behavior or
// log error or ignore or whatever
break;
}
return IsOK;
}
// Add additional methods that are common across all MessageTypes
}

Name the next file MessageIncidentProcessor.cs

public partial class MessageProcessor
{
protected bool ProcessNewIncident(MyMessage message)
{
bool IsOK = true;
// Necessary code to process, setting IsOK as needed
return IsOK;
}
protected bool ProcessIncidentUpdate(MyMessage message)
{
bool IsOK = true;
// Necessary code to process, setting IsOK as needed
return IsOK;
}
protected bool ProcessIncidentClose(MyMessage message)
{
bool IsOK = true;
// Necessary code to process, setting IsOK as needed
return IsOK;
}
// Add additional methods that are only used for Incidents
}

The next file is called MessageResourceProcessor.cs

public partial class MessageProcessor
{
protected bool ProcessNewResource(MyMessage message)
{
bool IsOK = true;
// Necessary code to process, setting IsOK as needed
return IsOK;
}
protected bool ProcessResourceAssignment(MyMessage message)
{
bool IsOK = true;
// Necessary code to process, setting IsOK as needed
return IsOK;
}
protected bool ProcessResourceUpdate(MyMessage message)
{
bool IsOK = true;
// Necessary code to process, setting IsOK as needed
return IsOK;
}
// Add additional methods that are only used for Resources
}

And, lastly, call this file MessageRequestProcessor.cs

public partial class MessageProcessor
{
protected bool ProcessTransferRequest(MyMessage message)
{
bool IsOK = true;
// Necessary code to process, setting IsOK as needed
return IsOK;
}
// Add additional methods that are only used for Requests
}

Well, now let's say that the Requests need an additional constructor. Let's change the MessageRequestProcessor.cs file to look like this:

public partial class MessageProcessor
{
private bool SendResponse = false;

// An additional constructor needed for Requests
public MessageProcessor(bool sendResponse) : base()
{
this.SendResponse = sendResponse;
}
protected bool ProcessTransferRequest(MyMessage message)
{
bool IsOK = true;
// Necessary code to process, setting IsOK as needed
if (this.SendResponse)
{
// code to send the response
}
return IsOK;
}
// Add additional methods that are only used for Requests
}

Now, how does this structure help co-worker cooperation? If I'm working on Incidents and another developer on my Team is working on Resources, we don't have to worry about stepping on each other's toes and making sure that we are careful with TFS check-ins and check-outs. TFS normally makes this fairly painless, but not always. Sometimes the process of merging code that I'm checking in with code that another developer has checked in, does not go easily. I'd rather avoid it.  This way, my co-worker checks out the MessageResourceProcessor.cs file and I check out the MessageIncidentProcessor.cs file ... and we're both happy!!  =0)

I hope this post has given you some good ideas ... Happy Coding!  =0)

Friday, January 31, 2020

Merge Tables And Sum Columns

A question on the MSDN Forums prompted me to write the topic of today's blog post. The question, in a nutshell, involved how to merge data into one DataTable from two DataTables originating from two different sources and provide a sum of a common column in that new DataTable.

I will illustrate the concept with an example from my fictitious ride-sharing system that I used in two previous blog posts:

https://geek-goddess-bonnie.blogspot.com/2019/09/parsing-data-with-metadata.html
https://geek-goddess-bonnie.blogspot.com/2019/12/create-dictionary-using-linq.html

The RideShare application maintains a database containing all the cars for each of the Companies that subscribe to their Service. One of the Services that this RideShare application provides is to keep track of the mileage that each car has driven every day in its database.  Each Car Company will send information to RideShare from every car that the company operates. This is a very simplistic version of this hypothetical system, because in reality we'd keep a lot more information (such as a TripID, probably a begin/end time for each Trip, etc.).  But since I'm only using this for an example of how to "Merge and Sum", I'm not going to bother with all that. I'll only be interested in the TripMileage and the TripDate (since we'll be summing all Mileages for each Date for each Car).

Let's say that RideShare is a startup company and currently only has two Customers, GeeksRide and UberGeeks. The XML for the DataSet that RideShare has retrieved from its database for today's trips, looks like this:

<RideShareOwnerDataSet>
<CompanyInfo>
<CarCompanyID>1</CarCompanyID>
<CarCompany>GeeksRide</CarCompany>
</CompanyInfo>
<CompanyInfo>
<CarCompanyID>2</CarCompanyID>
<CarCompany>UberGeek</CarCompany>
</CompanyInfo>
<CarInfo>
<CarNumberID>1</CarNumberID>
<CarCompanyID>1</CarCompanyID>
<CarNumber>C001</CarNumber >
<TripMileage>111.1</TripMileage >
<TripDate>2019-01-31</TripDate >
</CarInfo>
<CarInfo>
<CarNumberID>1</CarNumberID>
<CarCompanyID>2</CarCompanyID>
<CarNumber>C201</CarNumber >
<TripMileage>121.1</TripMileage >
<TripDate>2019-01-31</TripDate >
</CarInfo>
<CarInfo>
<CarNumberID>2</CarNumberID>
<CarCompanyID>2</CarCompanyID>
<CarNumber>C202</CarNumber >
<TripMileage>222.2</TripMileage >
<TripDate>2019-01-31</TripDate >
</CarInfo>
<CarInfo>
<CarNumberID>3</CarNumberID>
<CarCompanyID>2</CarCompanyID>
<CarNumber>C203</CarNumber >
<TripMileage>333.3</TripMileage >
<TripDate>2019-01-31</TripDate >
</CarInfo>
</RideShareOwnerDataSet>

During the course of the day, RideShare receives data from it's two customers (perhaps after every trip, or perhaps at intervals during the day ... that's totally up to the Customers as to when they send data to RideShare). So, let's say that RideShare receives data from UberGeek. It looks like this:

<RideShareOwnerDataSet>
<CompanyInfo>
<CarCompanyID>2</CarCompanyID>
<CarCompany>UberGeek</CarCompany>
</CompanyInfo >
<CarInfo>
<CarNumberID>2</CarNumberID>
<CarCompanyID>2</CarCompanyID>
<CarNumber>C202</CarNumber >
<TripMileage>22.2</TripMileage >
<TripDate>2019-01-31</TripDate >
</CarInfo>
<CarInfo>
<CarNumberID>4</CarNumberID>
<CarCompanyID>2</CarCompanyID>
<CarNumber>C204</CarNumber >
<TripMileage>42.2</TripMileage >
<TripDate>2019-01-31</TripDate >
</CarInfo>
</RideShareOwnerDataSet>

UberGeek is reporting the mileage from two different cars, one (ID 2) is already in the database and the other (ID 4) is a brand new car. We need to merge this new information with the data from the database, and add up all mileage, so that each car has only one row that contains the total mileage for this day so far.

I decided to try my hand at doing this with LINQ.

I chose to simply create a new DataSet/DataTable to hold the revised data, rather than updating the existing DataSet (since a new DataTable was the original problem posted to the Forum).

I am also using a Typed DataSet as it is much better in terms of usability for just about anything you want to do with DataSets. Since I'm providing the XML above, it can easily be used to create a Typed DataSet. See the second link to my blog above (Create Dictionary Using LINQ), and scroll down the section "XML Data and a Typed DataSet) for info about how to do this if you don't already know.

So, let's start looking at code. I took the above XML and saved it to two different XML files and then read them into two DataSets.

RideShareOwnerDataSet dsRideShare = new RideShareOwnerDataSet();
RideShareOwnerDataSet dsCustomerRide = new RideShareOwnerDataSet();

dsRideShare.ReadXml("RideShareFromDatabase.xml");
dsCustomerRide.ReadXml("RideShareFromCustomer.xml");

var dsResult = this.MergeAndSumData(dsRideShare, dsCustomerRide);

You can then use the dsResult DataSet to update your database, however that is beyond the scope of this post.

Here is the MergeAndSumData() method:

private RideShareOwnerDataSet MergeAndSumData(RideShareOwnerDataSet dsDatabase, RideShareOwnerDataSet dsCustomer)
{
RideShareOwnerDataSet dsResult = new RideShareOwnerDataSet();
RideShareOwnerDataSet.CarInfoDataTable dtResult = dsResult.CarInfo;

// First, we Merge the CarInfo DataTables from both DataSets into one DataTable.
var dtMerged = dsDatabase.CarInfo.Copy() as RideShareOwnerDataSet.CarInfoDataTable;
dtMerged.Merge(dsCustomer.CarInfo);

var test =
dtMerged.Where(row => !row.IsTripDateNull())
.GroupBy(row => new { row.CarCompanyID, row.CarNumberID, row.CarNumber, row.TripDate }, (key, group) => new
{
key.CarNumberID,
key.CarCompanyID,
key.CarNumber,
key.TripDate,
// And Sum the Mileages for each car here:
TripMileage = group.Sum(s => s.TripMileage)
})
.Select(row => dsResult.CarInfo.LoadDataRow(new object[]
{
row.CarNumberID,
row.CarCompanyID,
row.CarNumber,
row.TripDate,
row.TripMileage
}, LoadOption.Upsert))
.ToList();

// Since we are returning the entire DataSet, we might as well put the original CompanyInfo data in its DataTable
// in case the calling method might need to have both DataTables contain data.
dsResult.CompanyInfo.Merge(dsDatabase.CompanyInfo);

return dsResult;
}

A couple of things to note about the above method:

  • We're using a .ToList() at the end of the LINQ query, but we really don't care about the List (which ends up in the variable called test) ... it's used simply because the act of running .ToList() then executes the query. Until then, LINQ hasn't done anything and dsResult.CarInfo will still be empty.
  • The order that the column variables are used in the LoadDataRow() is important. It corresponds to the order that columns are defined in the DataTable.

That's all there is to it. And while the LINQ looks fairly straightforward now that I've written it, I had not used LoadDataRow much in the past, and never had I used it in a LINQ query! So, this post took a lot of experimenting and Googling. Which is why it took me so long to finish writing it!! 

And now you know the rest of the story. 

Happy Coding!!  =0)

Monday, December 30, 2019

Create a Dictionary using LINQ

Dictionaries are useful to use in your code, and occasionally I need to look up a word I don't know!!  Oh wait ... that's a different kind of dictionary!  ;0)

Say that you have some data that you'd like to put into a Dictionary. One reason for using a Dictionary might be to make it easy to process one group of items at a time. Another reason might be for easily finding data based on the dictionary key. The data could be in a file or maybe it's been retrieved from TCP or from a database. It doesn't matter. Data that might look something like this:

MyFirstGroup:
item1

MySecondGroup:
item2
item3
item4

Or maybe delimited data, like this:

|CARCOMPANY=GeeksRide|CARNUMBER=C121|CARNUMBER=C122|CARCOMPANY=UberGeek|CARNUMBER=C133|

The above string may look a bit familiar to regular readers of my blog. It's similar to data coming from a fictional ride-sharing system that I "invented" in order to write this blog post about parsing data: https://geek-goddess-bonnie.blogspot.com/2019/09/parsing-data-with-metadata.html

Oh, and how about XML? Here's some XML (in this case, representing a DataSet):

<RideShareOwnerDataSet>
  <CompanyInfo>
    <CarCompanyID>1</CarCompanyID>
    <CarCompany>GeeksRide</CarCompany>
  </CompanyInfo>
  <CompanyInfo>
    <CarCompanyID>2</CarCompanyID>
    <CarCompany>UberGeek</CarCompany>
  </CompanyInfo>
  <CarInfo>
    <CarNumberID>1</CarNumberID>
    <CarCompanyID>1</CarCompanyID>
    <CarNumber>C121</CarNumber>
  </CarInfo>
  <CarInfo>
    <CarNumberID>2</CarNumberID>
    <CarCompanyID>1</CarCompanyID>
    <CarNumber>C122</CarNumber>
  </CarInfo>
  <CarInfo>
    <CarNumberID>3</CarNumberID>
    <CarCompanyID>2</CarCompanyID>
    <CarNumber>C133</CarNumber>
  </CarInfo>
</RideShareOwnerDataSet>

Of course, depending on the data, the processing in your application could be quite different. So, let's take a look at how we would handle these three very different types of data.

In all of these examples, I am making use of LINQ syntax to create the Dictionary. In two of these examples, I make use of LINQ's .Aggregate() function. Here is a good link that explains how .Aggregate works:

https://stackoverflow.com/questions/7105505/linq-aggregate-algorithm-explained

Lines of Data

For this first example, I put the data into a text file, that I called GroupAndItem.txt. We can use the File.ReadLines() to read each line of data and utilize the .Aggregate() function of LINQ to make it pretty easy to dump this data into a Dictionary:

string key = "";
var dcSectionItems = File.ReadLines("GroupAndItem.txt")
    .Aggregate(new Dictionary<string, List<string>>(), (a, s) => {
        var i = s.IndexOf(':');
        if (i < 0) a[key].Add(s);
        else a[(key = s.Substring(0, i))] = new List<string>();
    
        return a;
    });


And then we can show the data by iterating the Dictionary utilizing Console.WriteLine() for display.

foreach (var kvp in dcSectionItems)
{
    Console.WriteLine("{0}:", kvp.Key); // section name
    foreach (var item in kvp.Value)
    {
        Console.WriteLine("\t{0}", item); // item name
    }
}


Delimited Data

Rather than reading from a file this time, I simply used a string to hold the pipe-delimited data. In practice, this data could be coming in from TCP, a web service or a database. Notice that GeeksRide has 2 cars, while UberGeek has only one. Again, I make use of the LINQ .Aggregate() function to create the Dictionary:

string pipeCompanyCars = "|CARCOMPANY=GeeksRide|CARNUMBER=C121|CARNUMBER=C122|CARCOMPANY=UberGeek|CARNUMBER=C133|";

string[] entries = pipeCompanyCars.Split(new char[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
key = "";
var dcCompanyCars = entries
    .Aggregate(new Dictionary<string, List<string>>(), (a, s) => {
        var i = s.IndexOf("CARCOMPANY=");
        if (i >= 0) a[(key = s.Substring(11))] = new List<string>();
        else
        {
            i = s.IndexOf("CARNUMBER=");
            if (i >= 0) a[key].Add(s.Substring(10));
        }
        return a;
    });


And, again, show the data in the Dictionary the same way:

foreach (var kvp in dcCompanyCars)
{
    Console.WriteLine("{0}:", kvp.Key); // car company name
    foreach (var item in kvp.Value)
    {
        Console.WriteLine("\t{0}", item); // car numbers
    }
}



XML Data


I almost exclusively utilize XML with DataSets. Most of the time, the DataSets in my applications are Typed DataSets. But, every once in a while, I'll use a plain old DataSet for one-time uses. My first example with XML data here will simply use a plain old DataSet.

The XML I'm using could have just as easily been retrieved from an .xml file to fill the DataSet using ds.ReadXml("FileName.xml"), but here I am filling the DataSet from an XML string. Here's the string and the code to read it into a DataSet :

string xmlCompanyCars =
    "<RideShareOwnerDataSet>" +
      "<CompanyInfo>" +
        "<CarCompanyID>1</CarCompanyID>" +
        "<CarCompany>GeeksRide</CarCompany>" +
      "</CompanyInfo >" +
      "<CompanyInfo>" +
        "<CarCompanyID>2</CarCompanyID>" +
        "<CarCompany>UberGeek</CarCompany>" +
      "</CompanyInfo >" +
      "<CarInfo>" +
        "<CarNumberID>1</CarNumberID>" +
        "<CarCompanyID>1</CarCompanyID>" +
        "<CarNumber>C121 </CarNumber >" +
      "</CarInfo>" +
      "<CarInfo>" +
        "<CarNumberID>2</CarNumberID>" +
        "<CarCompanyID>1</CarCompanyID>" +
        "<CarNumber>C122 </CarNumber >" +
      "</CarInfo>" +
      "<CarInfo>" +
        "<CarNumberID>3</CarNumberID>" +
        "<CarCompanyID>2</CarCompanyID>" +
        "<CarNumber>C133 </CarNumber >" +
      "</CarInfo>" +
    "</RideShareOwnerDataSet >";

DataSet ds = new DataSet();
StringReader sr = new StringReader(xmlCompanyCars);
ds.ReadXml(sr, XmlReadMode.InferSchema);

If you use XML strings to fill DataSet a lot, instead of the above code, I recommend creating a handy Extension method, like the following:

// Extension methods for DataSet:
public static void FillWithXml(this DataSet ds, string XML)
{
    StringReader sr = new StringReader(XML);
    ds.ReadXml(sr, XmlReadMode.InferSchema);
    ds.AcceptChanges();
}

Here, you'd use it like this:

ds.FillWithXml(xmlCompanyCars);

Here's the rest of the code for this XML example:

// Now let's get that DataSet into a Dictionary!
// We do need a "dummy" DataTable for this to work
DataTable dt = new DataTable();
dt.Columns.Add("CarCompany");
dt.Columns.Add("CarNumber");

var dcCompanyCarsFromDataSet = ds.Tables[0].AsEnumerable()
    .Join(ds.Tables[1].AsEnumerable(),
        rowCompany => rowCompany.Field<string>("CarCompanyID"), rowCar => rowCar.Field<string>("CarCompanyID"),
        (rowCompany, rowCar) =>
        {
            DataRow newRow = dt.NewRow();
            newRow["CarCompany"] = rowCompany.Field<string>("CarCompany");
            newRow["CarNumber"] = rowCar.Field<string>("CarNumber");
            return newRow;
        })
    .GroupBy(rowKey => rowKey.Field<string>("CarCompany"), rowValue => rowValue.Field<string>("CarNumber"))
    .ToDictionary(rowKey => rowKey.Key, rowValue => rowValue.ToList());

Notice the syntax for accessing the columns of data in LINQ with a regular DataSet. You *must* specify the type of data with .Field<string>("ColumnName"). There is no way around this (unless you use a Typed DataSet ... more on that in my final example). Also notice that I did *NOT* use the .Aggregate() function here. I could not figure out how to do it that way (and I doubt that it can be done, but if any of my faithful readers want to give it a try ... go for it! Leave me a comment if you figure it out). But the .GroupBy() and the .ToDictionary() work just fine!

One more thing that you may notice, and that is the CarCompanyID and the CarNumberID. In the XML, you can clearly see that they are numbers, most likely an int. But, because I read the XML in to a regular DataSet (and even though I used XmlReadMode.InferSchema), it did not infer that those numbers were anything but a string. Which is OK in this case, but just something to be aware of. We could have added Tables and Columns to that DataSet before we read in the XML data, but why bother? If I wanted that, I'd use a Typed DataSet.

And use a similar foreach, as before, to iterate the Dictionary:

foreach (var kvp in dcCompanyCarsFromDataSet)
{
    Console.WriteLine("{0}:", kvp.Key); // car company name
    foreach (var item in kvp.Value)
    {
        Console.WriteLine("\t{0}", item); // car numbers
    }
}

XML Data and a Typed DataSet

To experiment with Typed DataSets if you've never used them, since we have available XML, you can use ds.WriteSchema("RideShareOwnerDataSet.xsd") method to create an .xsd file. Then add the .xsd file to your solution and run the MSDataSetGenerator to create a Typed DataSet. Here's one of my blog posts about how to do that:

https://geek-goddess-bonnie.blogspot.com/2010/04/create-xsd.html

The relevant (to this post) part of that blog post is this:

Simply add that .xsd to your DataSet project, right-click on the .xsd and choose "Run Custom Tool". This is what will generate the Typed DataSet for you. If that option doesn't show up in your context menu, choose Properties instead and type "MSDataSetGenerator" in the Custom Tool property. After that, any time you make a change to the .xsd in the XML Editor and save the change, the Typed DataSet gets regenerated.

I edited the .xsd to change the datatypes of the IDs to an int, instead of a string. Go ahead and do that if you'd like to.

// We still need a "dummy" table
DataTable dt = new DataTable();
dt.Columns.Add("CarCompany");
dt.Columns.Add("CarNumber");

var dcCompanyCarsFromTypedDataSet = dsRideShareOwner.CompanyInfo
    .Join(dsRideShareOwner.CarInfo,
        // Notice that we can use the Typed syntax for the columns now
        rowCompany => rowCompany.CarCompanyID, rowCar => rowCar.CarCompanyID,
        (rowCompany, rowCar) =>
        {
            // Use the Typed syntax here too
            DataRow newRow = dt.NewRow();
            newRow["CarCompany"] = rowCompany.CarCompany;
            newRow["CarNumber"] = rowCar.CarNumber;
            return newRow;
        })
    // The GroupBy still needs to use the untyped syntax, because these Rows are from the untyped "dummy" DataTable
    .GroupBy(rowKey => rowKey.Field<string>("CarCompany"), rowValue => rowValue.Field<string>("CarNumber"))
    .ToDictionary(rowKey => rowKey.Key, rowValue => rowValue.ToList());

You'll notice that the LINQ syntax is quite different for Typed DataSet versus non-typed. We do *not* have to specify Field<string> for the columns we are using, since we can use the Typed property to access the column data: rowCompany.CarCompanyID, instead of rowCompany.Field<string>("CarCompanyID").  Much cleaner.

And, as before, iterate the Dictionary:

foreach (var kvp in dcCompanyCarsFromTypedDataSet)
{
    Console.WriteLine("{0}:", kvp.Key); // car company name
    foreach (var item in kvp.Value)
    {
        Console.WriteLine("\t{0}", item); // car numbers
    }
}

So, that's it. It's been a fun experiment for me as I wrote this blog post. I hope that you, Dear Reader, can put this to good use.

Happy Coding!!  =0)