Revisiting Action at a Distance

Three years ago, I did a couple blog posts on the software anti-pattern called Action at a Distance. I thought I was finished with that, but recently I learned of a way of utilizing an in-memory database. I love it! You don’t have to have a SQL Server instance around and it’s perfect for demo purposes. So, I’m going to update the relevant points from those blog posts, taking the in-memory database into consideration.

The Southwest Airlines Scheduling Fiasco

The 2022 holiday season meltdown of Southwest Airlines’ scheduling system brings into focus the danger of relying on the adage, “If it ain’t broke, don’t fix it.” Following that rule religiously leaves you vulnerable to unexpected happenstance.

I have several years’ experience writing software for enterprises. One rule often followed in this industry states, “If the software system ain’t broke, then don’t fix it.” This rule is stated to curb the tendency of software developers to try re-writing legacy software. I can understand the sentiment and as a developer I also understand the developer’s desire to either improve or replace all software systems with something that is new, or use newer technology or techniques, etc. However, a situation that wasn’t conceived of before, such as the winter storm that hit major airports during the holiday season, which is always one of the highest travel periods, resulted in Southwest’s meltdown.

A major contributor to this disaster was Southwest Airlines’ reliance on an old software scheduling system. I haven’t worked for Southwest Airlines, however from what I’ve read they haven’t updated their schedule system in an exceedingly long time. So long as life gives you good weather or at least weather that isn’t too far out of the norm, your flight staff are adequate and available, in other words, so long as you follow the happy path, everything works. But from my experience if agencies put too much reliance upon old systems and don’t bother to upgrade or improve them, then when other factors start to happen, like severe weather, flight staff not being available for whatever reasons, etc., all those factors will defeat the contingencies prepared for.

I am sure that the Southwest Airlines flight staff, ground crews, etc. all came together as best they could to restore service. More power to you folks!

There is an issue of upgrading, improving, or replacing older systems. It’s an unpleasant fact that upgrading/improving/replacing every system is expensive. In fact, very expensive. Here’s where applying the Pareto rule (the 80 – 20 rule) comes into play. Instead of trying to replace everything, identifying those systems which are more likely than other systems to contribute to a catastrophic failure pays dividends. I know very well that cutting down on technical debt doesn’t add value to the bottom line. However, any organization, as it continues to survive and thrive, as I’m sure Southwest Airlines will, accumulates technical debt. But removing technical debt is like losing weight by going to the gym. It prepares you for future, unforeseen circumstances that continuing to gain weight will inevitably result in.

Learning more about EF Core’s Power Tools

Continuing my series on steps I’ve recently taken to help upskill myself, I decided to go through a tutorial by Eric Ejlskov Jensen on his excellent Entity Framework Core’s Power Tools. This tutorial can be found at Add an admin CRUD web page to your ASP.NET Core web app in 5 minutes using EF Core Power Tools and CoreAdmin. I decided to create an ASP.NET MVC Core website using .NET 6. I stored my code in an Azure DevOps Services private repo. (Note: I use both Azure DevOps and GitHub, alternating between the two. I like to use both and keep my skills at writing CI/CD pipelines using both up to date. Also please note that I am using a local SQL Server Developer Edition database I have at home. Because of that I felt it best to keep this repo private.)

Following Eric’s instructions, I added Ed Andersen’s CoreAdmin NuGet package. (Eric didn’t say this, so be sure to add it.). Then I added the code lines of:

builder.Services.AddSqlServer<JobsearchContext>(builder.Configuration.GetConnectionString("Database"));

builder.Services.AddCoreAdmin();

After adding those lines and the app.MapDefaultControllerRoute() line just before app.Run(), I ran the app and got this when I went to the /CoreAdmin page:

That has all my tables in my local database for easy access!

Document my upskilling

An acquaintance of mine, Steve Jones, has challenged me to write a series of blog posts on my journey of learning how to upskill myself. This is a challenge, as I imagine much of this will be boring. I am concerned that no one will want to read this. At least it will be useful to me.

I am doing this because I’ve found that I need to upgrade some of my skills. I work in an environment where most of the developers are comfortable with not learning new (i.e.: current) skills. I don’t want to imply that they’re bad people. They are fine people, good citizens who love their families and contribute to their communities. They just aren’t interested in learning anything new. And unfortunately, it is easy to do in our environment.

But I like to learn. I pay for my own Pluralsight annual subscription, and I use it to learn current techniques. I am currently going through two Pluralsight courses, one on specifics of ASP.NET MVC in .NET 6 and another on what’s new in .NET 7. However, in this series of blog posts I’m going to describe here my learning experience in 2022. And where it takes me in 2023.

Tom Murdock

Today I learned that a good friend of mine, Tom Murdock, has passed away. We have long been involved with Microsoft development technologies, going back to VB6. We were involved in a VB group in the Albuquerque, NM area, in the late 90’s. That group eventually died, but along came .NET, C#, ASP.NET, etc. and we (along with a few other people) started the New Mexico .NET User Group (NMUG), still going today.

Tom was one of the biggest drivers of NMUG and supporters of NMUG. It is no exaggeration to say that NMUG exists today, in large part, due to Tom. His friendship was very important to me. He will be very sorely missed.

Guest Interview: C. E. Stone, author of “Starganauts”

Abigail Falanga, Author

First of all, allow me to scream about how absolutely glorious the title “Starganauts” is. It’s so amazing!! I love it. This book deserves to meet all success simply because of that name!!

Ahem. Anyway…

I’m (also) excited to join the blog tour for C. E. Stone’s science fantasy novel “Starganauts”! The journey this book has taken to publication is an epic one in itself, as you will read below, and it’s absolutely wonderful to celebrate its release.

So without further ado, the interview! (I didn’t mean to rhyme there)

Buy Link: https://buy.bookfunnel.com/cfskgfxq1i

C. E. Stone and I sit down for a virtual cup of coffee…

Me:Tell us something about your book “Starganauts” and the series it’s part of.

C. E.: Starganauts is the first novel in my Christian space opera series of the same name. It follows a group of humans who survive an apocalyptic attack on…

View original post 1,907 more words

Putting an existing Visual Studio project into a new Azure DevOps Services repo

At work we’re moving away from using Team Foundation Service’s (TFS) TFVC to Git. In order to gain experience as a team in working with Azure DevOps Services (ADS) the team I’m on are migrating some existing TFS repos we have on-prem, into various ADS repos.

The challenging part for me is fact that my colleague had created a new Git repo in ADS and I was working with an existing Visual Studio (VS) project, which wasn’t associated with any source control. I have Git repos both in ADS and GitHub. When working with Git and VS, I find it best to create a new Git repo either on ADS or GitHub, clone it to my machine, then create the VS project within that clone. The process I had to follow is more challenging than what I’m used to doing.

When working with Git in the command line, I prefer to use Posh-Git. Here are the steps I took:

  1. git init
  2. git checkout -b main
  3. git add .
  4. git remote add origin <the URL for the ADS repo>
  5. git commit -m “initial commit”
  6. git pull origin main –allow-unrelated-histories
  7. git push -u origin main

Step #1 created a local Git repo with a default branch named master. Step #2 renamed the default branch to main.

The other step of interest is #6. I made a mistake, before initializing my local repo, by adding a .gitignore and the README.md markdown file to remote. When I initially tried to push the changes to remote it complained, because the histories were out of sync. Thus that step to tell Git to ignore the unrelated histories.

These are the steps I took and it worked. If you know of a better way to put a VS project into a both a new, local Git repo and then into the remote, by all means please let me know!

How do recover a SQL Server instance

I’ve got SQL Server 2019 Developer Edition installed on my Windows 10 Professional machine. In the last couple of months my Windows profile became corrupted, so I had to abandon it and create a new Windows profile. (Not something I want to do ever again.) What I didn’t realize is now I’ve lost the ability to login into my SQL Server default instance. For whatever reason, although my new profile is in my local Administrators group, my new Windows profile isn’t. (I’d love to know why not – so if anyone knows, please fill me in.)

I’ve been looking around for ways to regain access to my SQL Server. Here’s a promising looking website Recover a lost SA password.

Another website which goes into more details, and eventually helped me solve my problem, is Connect to SQL Server when system administrators are locked out

The Action at a Distance Anti-pattern, Part 2

In the first part of this short series on the Action at a Distance I showed how a colleague of mine had, without knowing it, made the application we worked on, only run once. How the first instance would kill any other instances of it coming up. In this second, and final post I’ll show how another mistake was made which also illustrates the action at a distance anti-pattern. I’ve duplicated the pattern in the Action At A Distance 1 application I introduced in part 1. Please refer to that, as it has screen captures of the three windows that comprise Action At A Distance 1.

In Action At A Distance 1, there’s a Save and Cancel button on both the Authors and the Mystery Books windows. Recall that we were instructed to make the original WPF a launch a separate window for each function that the user wanted to do. I duplicated that UI/UX by having a separate Authors window come up, if the user clicked on the Authors button on the main window. The Authors window comes up as many times as the user clicks on the Authors button on the main window. The same is true with the Mystery Books button on the main window causing a Mystery Books window to popup up, as many times as the user clicks on the Mystery Books button on the main window. I discovered that this could cause the application to save data that the user may not have wanted to save. Or just as bad, it might cause the user to stop saving all data, even data they wanted saved, when canceling a save on another window. I copied the logic my co-workers had written into the original WPF app, into Action At A Distance 1. If you want you can look at the code in my GitHub repo for this project. For this part of the tutorial be sure to be in the master branch.

How Saving could be an action at a distance

To see what happened you have to look in a couple places. First the code from App.xaml.cs. In there the original coder wrote this property:

public static AuthorsModel MainDataContext
{
    get { return authorsModel; }
    set { authorsModel = value; }
}

The coder had written MainDataContext as a global variable seen throughout the application. The next part that contributed to the action at a distance anti-pattern being used was in all of the ViewModels. Here’s the ExecuteSaveCommand() from the AuthorsViewModel:

private void ExecuteSaveCommand()
{
	if (! SelectedAuthor.DateOfBirth.HasValue)
	{
		return;
	}

	//retrieve relevant data
	int selectedAuthorID = SelectedAuthor.ID;
	DateTime newDateOfBirth = SelectedAuthor.DateOfBirth.Value;

	var rec = App.MainDataContext.Authors.Where(a => a.ID == selectedAuthorID).FirstOrDefault();
	if (rec != null)
	{
		rec.DateOfBirth = newDateOfBirth;
		App.MainDataContext.SaveChanges();
	}

}

The problem is the App.MainDataContext.SaveChanges() entity framework command. It will save all pending changes. Including ones that the user might not want to save. Here’s the user case where this is a problem. The user opens Authors to make a change to one of the author’s birthdays. Then they open the Mystery Books window and make a change to one of the books publish date. They return to the Authors window, to save the change to that author’s birthday. But they decide that they publish date was fine the way it was, so they click the Cancel button. Well, its too late, they’ve already saved all of the pending changes.

How Canceling can be an action at a distance

In the original WPF app’s cancel function that my colleague wrote, he had a function which undoes all pending changes. I’ve reproduced the logic in the Action At A Distance 1 app:

        private void ExecuteCancelCommand()
        {
            var changedEntities = App.MainDataContext.ChangeTracker.Entries()
                .Where(x => x.State != EntityState.Unchanged).ToList();

            foreach (var entry in changedEntities.Where(x => x.State == EntityState.Modified))
            {
                entry.CurrentValues.SetValues(entry.OriginalValues);
                entry.State = EntityState.Unchanged;
            }

            foreach (var entry in changedEntities.Where(x => x.State == EntityState.Added))
            {
                entry.State = EntityState.Detached;
            }

            foreach (var entry in changedEntities.Where(x => x.State == EntityState.Deleted))
            {
                entry.State = EntityState.Unchanged;
            }
        }

Here’s how this code can cause an action at a distance anti-pattern mistake, using the Action At A Distance 1 app. Suppose the user opens the Authors app and modify some writer’s date of birth, then before saving their change they open the Mystery Books window and make a change to a publish date. But then they decide they didn’t want to save that change so they cancel the change to the mystery book’s publish date. Now they return to the author’s window and click the Save button. Well, it’s too late, because the above code will have undo all pending changes.

How I resolved these action at a distance problems in the FixedBranch

I removed the global MainDataContext variable from App.xaml.cs.

Then, using the AuthorsViewModel.cs as an example, the ExecuteSaveCommand() I changed as follows:

private void ExecuteSaveCommand()
{
	if (! SelectedAuthor.DateOfBirth.HasValue)
	{
		return;
	}

	//retrieve relevant data
	int selectedAuthorID = SelectedAuthor.ID;
	DateTime newDateOfBirth = SelectedAuthor.DateOfBirth.Value;

	using (var ctx = new AuthorsModel())
	{
		var rec = ctx.Authors.Where(a => a.ID == selectedAuthorID).FirstOrDefault();
		if (rec != null)
		{
			rec.DateOfBirth = newDateOfBirth;
			ctx.SaveChanges();
		}
	}
}

I used an local variable, ctx, to open a connection to the database, update the author’s date of birth, saved it, then closed the connection. This is what’s known as the Law of Demeter, which says you’re only to affect objects near by.

Because my Action At A Distance 1 app is so simple I didn’t have a cancel method – merely closing the window is sufficient to cancel the save.

I hope that this will help others to avoid writing action at a distance in their code. To learn more about action at a distance for software development, check out this Wikipedia article.

The Action at a Distance Anti-pattern

I’ve been writing code for a long time. The last 10 years or so I’ve delved into learning good software design principles, how to use them, when to use them and so on.

Also, I think it is very good for software developers to learn about software design anti-patterns. There are plenty. Indeed, I sometimes wonder if there aren’t more anti-patterns that good software design patterns. 🙂

One such anti-pattern which I’ve known about for years, but only recently learned it’s name, is known as Action at a Distance. At first I wasn’t sure about that name, but upon reflection I realized that it aptly describes what’s happening. Basically, its this, something happens in the application that was influenced by something else, somewhere else in the application, seemingly unrelated to what the user is currently doing. To the user it might almost look like magic. To a software developer, they might call it a Heisenbug, something that’s very hard to pin down, due to the seemingly arbitrary nature of the bug. Action at a distance bugs are hard to duplicate, unless you understand what is going on with that particular bug. And as all developers know, being able to duplicate a bug is paramount to getting it fixed.

Because of the difficult nature of identifying action at a distance bugs, I thought it would be good to explore some examples, which I have seen in production environments. No, I didn’t write them. But I do have an example of action at a distance anti-pattern, which I’ve taken from a place I worked. The application that we wrote was a Windows Presentation Foundation (WPF) application. Just because this is a WPF don’t dismiss it as some are likely to do. The technology involved isn’t as important as the action at a distance anti-pattern. Hopefully, seeing the code fragments I share here will help you identify the same thing in code you have to maintain or have written yourself. This application is a simple app displaying a few mystery authors and a few of their books. Note: This app is not a full featured app. I put it together in a short period of time, to illustrate the action at a distance anti-pattern, rather than being something that someone would use.

This application, named “Action At A Distance 1” (Originally I thought I might make two apps, but later rejected that idea) is not a typical WPF app. Most WPF apps I’ve seen or helped write, bring up a main hosting window, which other user controls come in through navigation. In this job the UI/UX designer rejected the normal WPF apps navigation, instead he chose a system of having lots of child windows pop up in reaction to users clicking buttons. In keeping with that design and especially as it is a part of the cause of action at a distance anti-pattern, I’ve duplicated it in the Action At a Distance 1 UX design. Here is a screen shot of the “landing page” as we called it:

Action At A Distance 1 start window

If the user clicks on the Authors button, then another window popups up with a small list of authors I’ve entered into a database. There can be as many Authors windows as the user clicks on/cares to bring up (that’s the UX design favored by the project manager/product owner):

September 15, 1890 is Agatha Christie’s birthday

The only field that I have editable in this window, is the date of birth field. (Remember, I wrote this in a short time and was more focused upon illustrating action at a distance, than providing a full featured app.) The Save and Cancel buttons are as they are in the app I helped write at that job.

And finally the Mystery Books button brings up the Mystery Books window, which the user can bring up as many times as they wish, following the pattern given us by the project manager/product owner:

This has the book title, author, the genre of mystery and the date of publication as best I could determine

(You can find my Action At A Distance 1 app at my GitHub repo here. I’ve designed this repo to have two branches, the master branch which illustrates two action at a distance issues, and another branch named FixedBranch, in which I show how I resolved the action at a distance problems.)

In the original WPF app at the job, at one point I wanted to bring up that app a second time. After all, I can bring up Excel or Word more than once. So I tried launching it a second time, but it didn’t come up. After testing it again, I decided to get into the Windows Event Viewer and see what it showed me. There I discovered that I was getting an error message that said, “The process was terminated due to an unhandled exception.” I’ve duplicated the problem in this simple Action At A Distance 1 app. If you either clone or download my repo, then open up the App.xaml.cs file, you’ll see in the constructor these lines of code:

if (ConfigurationManager.AppSettings["EnableLogging"] != null)
{
	if (ConfigurationManager.AppSettings["EnableLogging"].ToLower() == "true")
	{
		Logging.CoreEventLog.EnableLogging = true;
		try
		{
			Logging.CoreEventLog.IP_Address = Dns.GetHostAddresses(Environment.MachineName).FirstOrDefault().ToString();
		}
		catch (System.Net.Sockets.SocketException ex)
		{
			Logging.CoreEventLog.LogEvent("Dns.GetHostAddresses", null, null, ex.Message, true);
		}
	}
}

The Logging.CoreEventLog.EnableLogging = true; line invokes the CoreEventLog class. The person who wrote that class, which I’ve copied into this project’s master branch with few changes, has this class level variable assigned:

public static StreamWriter log_file = new StreamWriter(AppDomain.CurrentDomain.BaseDirectory + @"\CoreEvent.log", true);

Later in CoreEventLog you’ll see a function named WriteToLogFile(). Here it is from the master branch:

public static void WriteToLogFile(string Action, string Table_name, long? Record_id, string Message, bool? Error)
{
	if (EnableLogging == false)
	{
		return;
	}

	log_file.WriteLine();
	log_file.WriteLine("AppName: {0}", App.Name);
	log_file.WriteLine("AppVersion: {0}", App.Version);
	log_file.WriteLine("AppName: {0}", Environment.UserName);
	log_file.WriteLine("MachineName: {0}", Environment.MachineName);
	log_file.WriteLine("IP_Address: {0}", IP_Address);
	log_file.WriteLine("ActionDateTime: {0}", DateTime.Now);
	log_file.WriteLine("ActionTaken: {0}", Action);
	if (Table_name != null) log_file.WriteLine("TableName: {0}", Table_name);
	if (Record_id != null) log_file.WriteLine("RecordID: {0}", Record_id);
	if (Message != null) log_file.WriteLine("tSQL: {0}", Message);
	if (Error != null) log_file.WriteLine("ErrorMessage: {0}", Error);
	log_file.Flush();
}

Putting this all together, you can see that he opens the .log file at the beginning of the application and never closes it! This is the cause of “The process was terminated due to an unhandled exception” error. Once the first instance of Action At A Distance 1 gets a hold of the .log file, no other instance of Action At A Distance 1 can ever run again. At least not from the master branch.

If you have cloned/downloaded my repo, then switching to the FixedBranch you’ll see how I’ve resolved this issue. In the CoreEventLog.cs file I got rid of the class level variable named log_file. Then I changed the WriteToLogFile() method to look like this:

public static void WriteToLogFile(string Action, string Table_name, long? Record_id, string Message, bool? Error)
{
	if (EnableLogging == false)
	{
		return;
	}

	using (var log_file = new StreamWriter(AppDomain.CurrentDomain.BaseDirectory + @"\CoreEvent.log", true))
	{
		log_file.WriteLine();
		log_file.WriteLine("AppName: {0}", App.Name);
		log_file.WriteLine("AppVersion: {0}", App.Version);
		log_file.WriteLine("AppName: {0}", Environment.UserName);
		log_file.WriteLine("MachineName: {0}", Environment.MachineName);
		log_file.WriteLine("IP_Address: {0}", IP_Address);
		log_file.WriteLine("ActionDateTime: {0}", DateTime.Now);
		log_file.WriteLine("ActionTaken: {0}", Action);
		if (Table_name != null) log_file.WriteLine("TableName: {0}", Table_name);
		if (Record_id != null) log_file.WriteLine("RecordID: {0}", Record_id);
		if (Message != null) log_file.WriteLine("tSQL: {0}", Message);
		if (Error != null) log_file.WriteLine("ErrorMessage: {0}", Error);
		log_file.Flush();
	}

}

Here you can see that I made use of the using command to instantiate the local log_file variable. Then I’ve followed the original code to write the unhandled exception to the .log file. Once that block of code exists, then the connection to the .log file is closed.

I will resume this tutorial, showing the other action at a distance error illustrated in this small app, in another blog post.