Logging Logger Exceptions

Sometimes your logger throws an exception while logging an exception. At least it does if you have luck swings like mine from time to time.

Here's how I deal with it.

using System.Diagnostics;

try
{
    //some logging code that can throw an exception
    //but the exception has to be handled and logged
    //somehow
}
catch (Exception e)
{
    try
    {
        //write exception to OS event log
        if (!EventLog.SourceExists("MyAppName"))
            EventLog.CreateEventSource("MyAppName", "Application");
        EventLog.WriteEntry("MyAppName", "Log Exception: " + e.Message);
    }
    catch
    {
        //sorry charlie
    }
}

It's not perfect, but it seems to work in most cases.

This code comes in handy especially in Windows Services where in the .NET Framework 2.0 and up (as near as I can tell), an unhandled exception will result in your service being stopped with little or no evidence as to why.

SQL Server TOP vs MySQL LIMIT

I've recently begun to explore MySQL a bit more, mostly out of curiosity. Primarily I work with SQL Server 2005 in my day job but it seems prudent to know a bit more about some of the other databases servers and it's been a while since I dusted off MySQL.

Happily I found that MySQL AB has released a really great ADO.NET provider for MySQL.

If you have used the SQL Server specific ADO.NET provider, you'll easily make the transition to using this one.

But I digress. The reason for this posting was to permanently remind myself and anyone else making a transition between these two database engines that TOP = LIMIT but not in the same place. That is to say, the TOP keyword is used in SQL Server to limit the number of returned rows just prior to the column specifiers. The LIMIT keyword however is used at the end of the select statement. Thus:

SELECT TOP 1 * FROM MYTABLE WHERE FKID = 3

And for MySQL use:

SELECT * FROM MYTABLE WHERE FKID = 3 LIMIT 1;

Yes, I know. It ought to be obvious, but having a quick reminder here will help to seal this minor difference into my brain.

Value of Schema Driven XML Configuration

If you've written an ASP.NET application, you've already taken advantage of schema driven XML configuration perhaps without even realizing it. You may have even ignored the web.config file if you're just starting out with ASP.NET. Or you may have used it extensively without considering the usefulness of using such a construct in your own applications.

My day job involves writing a complex application that implements a myriad of business rules, nearly all of which non-coders need to be able to modify from time to time without bothering the development team. That means using XML. But not just any old XML. I'm talking about well formed, validated XML using a nice XSD schema file controlled by the development team.

I recommend the reader purchase O'Reilly's book XML Schema by Eric van der Vlist. It's been a most trustworthy companion. And then to make the use of this XML configuration dreamscape even easier, I recommend Christian Weyer's contract first tool. I use this extremely useful tool to generate the XAL (XML access layer) so I don't have to write it by hand.

With these two tools in hand, you can create something like this in Visual Studio:

<?xml version="1.0" encoding="utf-8" ?>
<xs:schema id="watchconfig" targetNamespace="http://example.com/v1001/watchconfig.xsd"
    elementFormDefault="qualified"
    xmlns="http://example.com/v1001/watchconfig.xsd"
    xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <xs:element name="watchconfig">
        <xs:complexType>
            <xs:sequence>
                <xs:element ref="watchgroup" minOccurs="0" maxOccurs="unbounded" />
            </xs:sequence>
        </xs:complexType>
    </xs:element>
    <xs:element name="watchgroup">
        <xs:complexType>
            <xs:sequence>
                <xs:element ref="watcher" minOccurs="0" maxOccurs="unbounded" />
            </xs:sequence>
            <xs:attribute name="id" type="xs:string" use="required" />
            <xs:attribute name="literalsconfigpath" type="xs:string" />
        </xs:complexType>
    </xs:element>
    <xs:element name="watcher">
        <xs:complexType>
            <xs:attribute name="sourcepath" type="xs:string" use="required" />
            <xs:attribute name="wippath" type="xs:string" use="required" />
            <xs:attribute name="outpath" type="xs:string" use="required" />
            <xs:attribute name="ssispath" type="xs:string" use="required" />
            <xs:attribute name="intakemap" type="xs:string" use="required" />
        </xs:complexType>
    </xs:element>
</xs:schema>

And an XML file like this:

<?xml version="1.0" encoding="utf-8" ?>
<watchconfig xmlns="http://example.com/v1001/watchconfig.xsd" xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <watchgroup id="clientname" literalsconfigpath="H:\temp\literals_client">
        <watcher sourcepath="H:\temp\client\watch"
                    wippath="H:\temp\client\wip"
                    outpath="H:\temp\client\out"
                    ssispath="H:\temp\client\ssis"
                    intakemap="D:\Contracts\Code\map.xml" />
    </watchgroup>
</watchconfig>

And the nicely generated code like this:

/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "2.0.50727.42")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true, TypeName="watchconfig")]
[System.Xml.Serialization.XmlRootAttribute(Namespace="http://example.com/v1001/watchconfig.xsd", IsNullable=false, ElementName="watchconfig")]
[System.ComponentModel.TypeConverterAttribute(typeof(System.ComponentModel.ExpandableObjectConverter))]
public partial class Watchconfig
{

/// <remarks/>
private System.Collections.Generic.List<Watchgroup> watchgroup;

public Watchconfig()
{
}

public Watchconfig(System.Collections.Generic.List<Watchgroup> watchgroup)
{
this.watchgroup = watchgroup;
}

[System.Xml.Serialization.XmlElementAttribute("watchgroup", Order=0)]
public System.Collections.Generic.List<Watchgroup> Watchgroup
{
get
{
return this.watchgroup;
}
set
{
if ((this.watchgroup != value))
{
this.watchgroup = value;
}
}
}
}

/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "2.0.50727.42")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true, TypeName="watchgroup")]
[System.Xml.Serialization.XmlRootAttribute(Namespace="http://example.com/v1001/watchconfig.xsd", IsNullable=false, ElementName="watchgroup")]
[System.ComponentModel.TypeConverterAttribute(typeof(System.ComponentModel.ExpandableObjectConverter))]
public partial class Watchgroup
{

/// <remarks/>
private System.Collections.Generic.List<Watcher> watcher;

/// <remarks/>
private string id;

/// <remarks/>
private string literalsconfigpath;

public Watchgroup()
{
}

public Watchgroup(System.Collections.Generic.List<Watcher> watcher, string id, string literalsconfigpath)
{
this.watcher = watcher;
this.id = id;
this.literalsconfigpath = literalsconfigpath;
}

[System.Xml.Serialization.XmlElementAttribute("watcher", Order=0)]
public System.Collections.Generic.List<Watcher> Watcher
{
get
{
return this.watcher;
}
set
{
if ((this.watcher != value))
{
this.watcher = value;
}
}
}

[System.Xml.Serialization.XmlAttributeAttribute(AttributeName="id")]
public string Id
{
get
{
return this.id;
}
set
{
if ((this.id != value))
{
this.id = value;
}
}
}

[System.Xml.Serialization.XmlAttributeAttribute(AttributeName="literalsconfigpath")]
public string Literalsconfigpath
{
get
{
return this.literalsconfigpath;
}
set
{
if ((this.literalsconfigpath != value))
{
this.literalsconfigpath = value;
}
}
}
}

/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "2.0.50727.42")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true, TypeName="watcher")]
[System.Xml.Serialization.XmlRootAttribute(Namespace="http://example.com/v1001/watchconfig.xsd", IsNullable=false, ElementName="watcher")]
[System.ComponentModel.TypeConverterAttribute(typeof(System.ComponentModel.ExpandableObjectConverter))]
public partial class Watcher
{

/// <remarks/>
private string sourcepath;

/// <remarks/>
private string wippath;

/// <remarks/>
private string outpath;

/// <remarks/>
private string ssispath;

/// <remarks/>
private string intakemap;

public Watcher()
{
}

public Watcher(string sourcepath, string wippath, string outpath, string ssispath, string intakemap)
{
this.sourcepath = sourcepath;
this.wippath = wippath;
this.outpath = outpath;
this.ssispath = ssispath;
this.intakemap = intakemap;
}

[System.Xml.Serialization.XmlAttributeAttribute(AttributeName="sourcepath")]
public string Sourcepath
{
get
{
return this.sourcepath;
}
set
{
if ((this.sourcepath != value))
{
this.sourcepath = value;
}
}
}

[System.Xml.Serialization.XmlAttributeAttribute(AttributeName="wippath")]
public string Wippath
{
get
{
return this.wippath;
}
set
{
if ((this.wippath != value))
{
this.wippath = value;
}
}
}

[System.Xml.Serialization.XmlAttributeAttribute(AttributeName="outpath")]
public string Outpath
{
get
{
return this.outpath;
}
set
{
if ((this.outpath != value))
{
this.outpath = value;
}
}
}

[System.Xml.Serialization.XmlAttributeAttribute(AttributeName="ssispath")]
public string Ssispath
{
get
{
return this.ssispath;
}
set
{
if ((this.ssispath != value))
{
this.ssispath = value;
}
}
}

[System.Xml.Serialization.XmlAttributeAttribute(AttributeName="intakemap")]
public string Intakemap
{
get
{
return this.intakemap;
}
set
{
if ((this.intakemap != value))
{
this.intakemap = value;
}
}
}
}

(Sorry, indentation got hosed, but you won't need it since you'll be generating this code with one click.)

 

 

 

 

The Case Against Use Cases

I'm currently working on a rather complex ETL system in the medical industry. There are new business rules uncovered daily as development and analysis proceeds in parallel due to overwhelming business urgencies. The use cases in the system are limited pretty much to "Process File." Everything else is buried in a system of business rules more complex than I care to think about very often and a series of events and event handlers which process the file and implement the business rules.

My own approach to this challenge could have been much more organized had I purchased Karl E. Weigers's book More About Software Requirements earlier on in this project. He says, "Use cases are less valuable for projects involving data warehouses, batch processes, hardware projects with embedded control software, and computationally intensive applications. In these softs of systems, the deep complexity doesn't lie in the user-system interactions. It might be might be worthwhile to identify use cases for such a product, but use case analysis will fall short as a technique for defining all the system's behavior."

I could not agree more. Weigers goes on to recommend the use of event-response tables to provide a way of documenting the requirements of such complex systems which have little if any interaction with users. Granted, you could write a use case using the machine or file system or OS or scheduler or some other non-human entity as the actor, but the analogies break down when trying to document the requirements of the complex rules within the case.

The event-response table is a simple approach to organizing these details that in fact works much better than an ad hoc method of writing it all down in sequential paragraphs and then asking developers, in this case that developer being me, to interpret those requirements and design and code a solution that really works.

You simply need a three column table with the following headers: Event, System State, and Response.

Breaking up functional requirements in a complex rules-driven system with minimal human interaction can be a daunting task. You can make it a bit easier by using some simple organizing structures such as the event-response table.

IBM Going After MySQL or SQL Server 2005 Express with DB2 Express-C

I subscribe to CodeProject's newsletter and "product information" emails. Today I received an interesting missive with the headline: "Introducing the new DB2 Express-C". And I noted the "no charge edition" limits you 2 (dual core) processors, 4GB ram limit but with unlimited database size and number and unlimited users.

SQL Server 2005 Express is limited to one CPU and 1GB of ram with a max database size of 4GB. Hmm... I'll bite. I want to know more.

A little exploration reveals a nice article on the wiki about leveraging your MySQL skills. And there seems to be ample information on using DB2 with .NET.

Having never used DB2, I'm completely unaware of what I'm getting myself into, but I'm seriously considering diving into this latest addition to the free, and increasingly capable, database engines.

So I'm downloading now and I'll give it a whirl and report what I find back here. No promises on when.


 Followup - Who am I kidding. The install is HUGE and I have had no time to eval this thing. Toss another distraction overboard.

ASP.NET Theme Assignment

You learn something new every day. I just don't have time to blog about it every time. This one seemed significant enough.

I'm writing a little ASP.NET app to keep up my web skills since my day job keeps me busy cranking out back-end code. For the first time, I've tried playing with themes. I wanted to assign a theme based on a user configuration, so I unwittingly assigned it in the Page_Load event.

Bo no... Nasty little error message telling me I can't do that, so a bit of digging revealed it has to be done earlier in the life of the page.

protected void Page_PreInit(object sender, EventArgs e)
{
    this.Theme = GetTheme();
}

That did the trick.

A Kit for Writing Your Own Programming Language

I'm having fun. In the last couple of months, I've carved out about 20 hours of fun playing with some code and tools I found on www.devincook.com. If you're interested in the creation of programming languages, I want to encourage you to to check out that site and the code (link below) that I've derived and organized from code found on that site.

I even tried to share my enthusiasm with the .NET Users Group I like to attend, though as most attendees will confirm, my presentation was ill prepared, disorganized and rambled. Hopefully the 20 or so new attendees at the users group meeting will realize that my presentation is not representative of the excellent presentations the group is use to and will come back.

In the code you can download below, you'll find my first attempt at a language: TROLL — Tyler's Really Obtuse Little Language. It's based on the concepts in the sample SIMPLE interpreter I downloaded from Devin Cook's site under the C# Engines (I recommend Morozov's engine).

Why would anyone want to write their own programming language, you ask. Especially when we have C#, VB.NET, Boo and yes, even Java. Yeah, yeah, there's C++ and D as well, and a hundred others.

So why one more programming language? Because now you can.

It's a great learning experience. You'll learn about BNF, LALR and other fun acronymns. What's more, you may find it an empowering experience to write your own programming language and see your own made-up code executed. You might even find a real use for creating a 4th+ generation language for a specific, vertical solution.

Well, whatever your reason, download the code, download Devin Cook's GOLD Builder and have some fun.

Download code (659KB)

Get Mono the Easy Way

Thanks to Miguel and his team for making it easier than ever for us Windows-bound .NET geeks to give Mono a try. It's a fairly big download but well worth it. You can now download a VMWare virtual machine image of Mono 1.2.2.1 on openSUSE 10.2 and the free VMWare Player. Install the player and open the unzipped VM file. Easy peasy. I had to play with network settings a bit but that was easy.

There is no easier way to check out Mono on Linux. No partitions to worry about. No setup to worry about. No drivers to mess with such as the constant failure I would get with my dual monitor card when I tried earlier to get SUSE running on a separate partition on my box which led to me giving up.

I recommend you give it a try. Amazing what the Mono team has done. Kudos again to Miguel and his team and all those who have contributed to the Mono project.