A pleasant walk through computing

Comment for me? Send an email. I might even update the post!

LINQPad - Refresh Entity after SubmitChanges to Avoid Cached Results

Today I was using LINQPad to see how SQL record values were being affected by table triggers. The results were puzzling, so I did the same test using SQL Management Studio and got different results. Clearly, there was caching going on!

Here's roughly what my LINQPad C# Statements script looked like (table names changed to protect the innocent). I was going directly against the database, not using an Entity Framework DbContext, so LINQPad is using LinqToSql for the connection.

var reportId = "1234";
var header = ReportHeaders.Single(a => a.ReportID == reportId);
//Change Status from P to C. Trigger should reset to P because Completed is false.
header.Status ='C';
header.Completed = false;
SubmitChanges();

var report = ReportParents.Single(a => a.ReportID == reportId);
var d = new {HeaderStatus =    header.Status,    ReportStatus =    report.Status,
             HeaderCompleted = header.Completed, ReportCompleted = report.Completed};

d.Dump();

And my result:

But in the database, both statuses were (correctly) set back to P.

HEADER
STATUS Completed
------ ---------
P      0

REPORT
STATUS Completed
------ ---------
P      0

This was happening because Linq2Sql caches query results during a single Context session. Caching can be turned off by not tracking objects (ObjectTrackingEnabled = false;), but that will prevent SubmitChanges from running.

The solution is to use Context.Refresh to requery the database for the entity.

var reportId = "1234";
var header = ReportHeaders.Single(a => a.ReportID == reportId);
//Change Status from P to C. Trigger should reset to P because Completed is false.
header.Status ='C';
header.Completed = false;
SubmitChanges();

//Force fresh result. Caching was preventing accurate trigger results.
this.Refresh(RefreshMode.OverwriteCurrentValues,header);

var report = ReportParents.Single(a => a.ReportID == reportId);
var d = new {HeaderStatus =    header.Status,    ReportStatus =    report.Status,
             HeaderCompleted = header.Completed, ReportCompleted = report.Completed};

d.Dump();

Now, running the script returns the correct results.

References

Create Blog Post Using Markdown Monster's Command Addin

This is a quick entry, mostly for me. I've been using the very good Markdown editor, Markdown Monster, and wanted to automate creating blog posts. I have a manual process that I thought I could somewhat automate.

MM has an addin that lets me write C# scripting and control a lot of the editor.

Through both the documentation plus trial-and-error, I was able to do the following:

  1. Prompt for a post title.
  2. Create the post file in a known folder.
  3. Add metadata (frontmatter).
  4. Open in a tab for editing.
  5. Open the post folder's "images" folder.
  6. Commit and publish

Here's the code for creating the post, some of which is seen in the above screenshot.

There were some things I couldn't accomplish, noted in the code comments, probably due to my ignorance of how the MM API is supposed to work.

#r Westwind.Utilities.dll
#r Microsoft.VisualBasic.dll
using Westwind.Utilities;
using System.IO;

string defaultPath = @"c:\path\to\posts";

string title = Microsoft.VisualBasic.Interaction.InputBox("File Name", "Enter file name", "", 600, 300);

if (String.IsNullOrWhiteSpace(title))
{
    return null;
}

string datetime = DateTime.Now.ToString("yyyyMMdd-HHmm");
string folder = Path.Combine(defaultPath, datetime + " " + title);
string file = Path.Combine(folder, title + ".md");
string images = Path.Combine(folder, "images");
string publishedOn = DateTime.Now.ToString("yyyy-MM-dd HH:mm");
string slug = title.ToLower();
slug = slug.Replace(" ", "_");
slug = slug.Replace("?", "_");
slug = slug.Replace("&", "_");
slug = slug.Replace(".", "_");
string meta = String.Format(@"---
Title          : {0}
PublishedOn    : {1}
Slug           : {2}
Tags           : 
Status         : Draft
---
",title, publishedOn, slug);

Directory.CreateDirectory(images);
File.WriteAllText(file,meta);
//Model.ExecuteProcess("explorer.exe", images);
//System.Threading.Thread.Sleep(2000);
Model.Window.OpenTab(file);
//I can't figure out how to set the cursor position
//The code seems right, but the cursor doesn't move unless I run the command
//from the addin window. Even then, the Console.WriteLine is what
//forces it to work.
var editor = Model.ActiveEditor;
var doc = Model.ActiveDocument;
var len = doc.CurrentText.Split('\n').Length;
Console.WriteLine(len);
Console.WriteLine(editor.GetLineNumber());
editor.SetCursorPosition(0,len);
editor.SetEditorFocus();


//Couldn't get this working
//var editor = Model.ActiveEditor;
////HACK: For some reason this allows the document to be edited.
//Console.WriteLine(editor.GetLineNumber());
//editor.SetSelection(meta);
//editor.SaveDocument();

To open the images folder, I created this command-let:

#r Westwind.Utilities.dll

using Westwind.Utilities;
using System.IO;
using System.Windows;

string folder = Model.ActiveDocument.Filename;
folder = Directory.GetParent(folder).FullName;

string images = Path.Combine(folder, "images");
if (Directory.Exists(images)) {
    Model.ExecuteProcess("explorer.exe", images);
}
else {
    MessageBox.Show("No folder " + images);
}

And, to publish, I do this. I have a batch file in my blog folder that takes care of the actual commit/push to Git.

#r Westwind.Utilities.dll

using Westwind.Utilities;
using System.IO;
using System.Windows;
using System.Diagnostics;

string folder = Model.ActiveDocument.Filename;
folder = Directory.GetParent(Path.Combine(folder,@"..\..\..\")).FullName;

string cmd = Path.Combine(folder, "publish.cmd");
cmd = @"""" + cmd + @"""";
Console.WriteLine(cmd);
//Model.ExecuteProcess("cmd.exe", "/C " + cmd);
var si = new ProcessStartInfo();
si.FileName = "cmd.exe";
si.Arguments = "/C " +  cmd;
si.WorkingDirectory = folder;

Process.Start(si);

Batch Image Resize Checking Min Max in Both Dimensions

Here's some code that you can feel free to use, if it helps you. I had a large folder of photos that I use for Windows wallpaper. The sizes were all over the place, and I really wanted to better match my monitor's resolution. I looked around and didn't find a utility that exactly met my needs, which were:

  1. Batch resize photos using not to exceed values for width and height.
  2. Ignore files below specified minimum width or height.
  3. Simple way to ignore files based on name.

Basically, I wanted to resize most of my pictures to not exceed the monitor's natural width or height, and ignore really small ones.

For exampe, let's say I had these photos. (All dimensions are given in WIDTH x HEIGHT)

  1. One in landscape mode at 2500x1600
  2. One in landscape mode at 1400x1300
  3. One in landscape mode at 1000x500
  4. One in portrait mode at 2400x400 (weird!)
  5. One in portrait mode at 400x500

In the code, I set the variables this way:

    // Set the min/max values of a LANDSCAPE photo.
    // The code automatically handles if the photo is in portrait mode.
    int maxWidth = 1800;
    int maxHeight = 1190;

    int minWidth = 900;
    int minHeight = 600;

In plain-Engish, I'm expecting this:

  1. The width is too big, so reduce to 1800x1152
  2. The height is too big, so reduce to 1281x1190
  3. Both are too small. Resize to best fit of 1800x900
  4. Height is too big, reduce to 1190x198
  5. Ignored, because both dimensions are below our min dimensions

Here's the code. Happy resizing!

Warning
I'm not responsible if this doesn't work. Backup your photos, and I recommend using an empty target folder.

Dependency: NuGet package ImageLibrary

void Main()
{
	bool OkToSave = true;
    
    //Comment this to run in "test" mode
	if (!OkToSave) { Console.WriteLine("Not OK to Save"); return;}

    // Set the min/max values of a LANDSCAPE photo.
    // The code automatically handles if the photo is in portrait mode.
    int maxWidth = 1800;
    int maxHeight = 1190;

    int minWidth = 900;
    int minHeight = 600;

    // Set the source and target folders
    string sourceFolder = 	@"C:\Users\charl\Google Drive\Documents\Clients\_SM\SktBlogImages";
	string targetFolder = @"C:\Users\charl\Google Drive\Documents\Clients\_SM\Apps\HCMA Web Site Slider\SktBlogImages";
	Directory.CreateDirectory(targetFolder);
	
	string[] formats = { ".bmp", ".jpg", ".jpeg", ".png"};

    //Exclude these files, if any. E.g. {"Bad Hair Day", "Music Date"}
    //Note that if the filename includes the string, it will be exluded.
    string[] files = { };
    
	foreach (string file in Directory.EnumerateFiles(sourceFolder))
	{
		string fileName = Path.GetFileName(file);
		string ext = Path.GetExtension(file).ToLower();
		if (formats.Contains(ext) & !files.Contains(fileName))
		{
			var image = new KalikoImage(file);
            //LANDSCAPE
			int width = maxWidth;
			int height = maxHeight;
            //PORTRAIT            
			if (image.IsPortrait)
			{
				width = maxHeight;
				height = maxWidth;
				minWidth = minHeight;
				minHeight = minWidth;
			}

			KalikoImage scaledImage;
			//ignore if
			if (image.Height < minHeight & image.Width < minWidth){
				scaledImage = image;
			}
			else
			{
				//resize, preserve aspect ratio			
				scaledImage = image.Scale(new FitScaling(maxWidth, maxHeight));
			}
			//save with original resolution
			ImageFormat format;
			if (new string[] { ".jpg", ".jpeg" }.Contains(ext)) { format = ImageFormat.Jpeg; }
			else if (ext == ".gif") { format = ImageFormat.Gif; }
			else if (ext == ".bmp") { format = ImageFormat.Bmp; }
			else { format = ImageFormat.Png; }

			Console.WriteLine(fileName + "   " + scaledImage.Width + " x " + scaledImage.Height);
			if (OkToSave) { scaledImage.SaveImage(Path.Combine(targetFolder, fileName), format, true);}
		}
	}
}