You’re doing it wrong, alt text and title attributes

I had moved my mouse over an image on a local bank’s web site, and was surprised to see the name of the file pop-up.

SNAGHTML3867720c

The reason is that their web developer apparently doesn’t understand the meaning or intention behind the alt or title attribute of an HTML element.

Not that it isn’t handy to know the name of the file…. Smile

Maybe they could have used:

  • Passionate bank web site user
  • Man with loose tie
  • Man having good hair day
  • Or …. ?

(I noticed that they have this problem in many areas of their web site. Maybe their Banking skills are better than their web site skills?)

Paging Data with SQL Server Compact

image

If you’re using SQL Server Compact Editition version 4 or higher, there’s finally a decent and efficient way to do data paging:

var streets = db.Query<StreetName>(@"SELECT * FROM StreetNames
                   ORDER BY Id
                   OFFSET @offset ROWS
                   FETCH NEXT @rows ROWS ONLY",
                        new { offset = group * GroupSize, 
                            rows = GroupSize });

The code above uses Dapper-dot-net (with Contrib Extensions) to make the syntax of executing the Query succinct. (Recently, I’ve all but abandoned the Entity Framework in favor of Dapper).

The above code should be straightforward.

Given a table named, StreetNames created thusly:

db.Execute(@"CREATE TABLE StreetNames (          
            Id int IDENTITY NOT NULL,
            Name nvarchar(24) NOT NULL,
            CONSTRAINT pk_id PRIMARY KEY (Id))");

with some sample data inserted into the table:

foreach (var name in GetStreetNames())
{
    db.Execute("INSERT INTO StreetNames (Name) Values (@Name)",
        new { Name = name });
}

you’ll likely want to grab the data in pages. To get the data as pages, you’ll need to pick an index or something ordered (in this case, I used the Id column), specify the starting index and the total number to fetch:

OFFSET @offset ROWS
FETCH NEXT @rows ROWS ONLY

So, using the sample database:

using (var db = new SqlCeConnection(connectionString))
{
    db.Open();
    for (var group = 0; ; group++)
    {
        Console.WriteLine(string.Format("== GROUP {0} ==", group));
        var streets = db.Query<StreetName>(@"SELECT * FROM StreetNames
                           ORDER BY Id
                           OFFSET @offset ROWS
                           FETCH NEXT @rows ROWS ONLY",
                                new { offset = group * GroupSize, 
                                    rows = GroupSize });

        foreach (var street in streets)
        {
            Console.WriteLine(string.Format("{0} ({1})",
                street.Name, street.Id));
        }
        if (streets.Count() < GroupSize) { break; }
    }
}

The code loops until it can’t load a full GroupSize.

const int GroupSize = 8;

I added an extension method to IDbConnection to check whether a table exists:

public static class DbExtensions
{
    /// <summary>
    /// Determines whether the table exists
    /// </summary>
    /// <param name="connection">Existing, opened, database connection</param>
    /// <param name="tableName">The name of the table to test for.</param>
    /// <returns>True if table exists.</returns>
    public static bool TableExists(this IDbConnection connection, string tableName)
    {
        Debug.Assert(connection != null);
        Debug.Assert(!string.IsNullOrWhiteSpace(tableName));

        var cmd = connection.CreateCommand();
        cmd.CommandText = @"SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES 
                            WHERE TABLE_NAME=@TableName";
        var p1 = cmd.CreateParameter();
        p1.DbType = DbType.String;
        p1.ParameterName = "TableName";
        p1.Value = tableName;
        cmd.Parameters.Add(p1);

        var result = cmd.ExecuteScalar();
        return (int)result == 1;
    }
}

I really like the simple syntax it permits:

if (!db.TableExists("StreetNames"))

It’s used like this:

private static string CreateDatabase()
{
    var connectionString = string.Format("DataSource={0}", "test.sdf");
    SqlCeEngine engine = new SqlCeEngine(connectionString);
    try
    {
        engine.CreateDatabase();
    }
    catch (SqlCeException ex)
    {
        // file exists? (if so, we'll just ignore it ...)
        if (ex.NativeError != 25114) { throw; }
    }

    using (IDbConnection db = new SqlCeConnection(connectionString))
    {
        db.Open();
        // create a sample table 
        if (!db.TableExists("StreetNames"))
        {
            db.Execute(@"CREATE TABLE StreetNames (                
                        Id int IDENTITY NOT NULL,
                        Name nvarchar(24) NOT NULL,
                        CONSTRAINT pk_id PRIMARY KEY (Id))");
            foreach (var name in GetStreetNames())
            {
                db.Execute("INSERT INTO StreetNames (Name) Values (@Name)",
                    new { Name = name });
            }
        }
    }
    return connectionString;
}

Oddly, Sql Compact doesn’t throw proper (specific) exceptions, so you’ll be forced to check for NativeErrors or against strings rather than specific Exception types. (Seriously!)

Using the SqlCeEngine class, the code attempts to create a new Database file. If it already exists, it throws an exception. All but the file exists exception are rethrown.

Using an open connection and the TableExists extension, it creates a sample table called StreetNames, populated with this data:

private static IEnumerable<string> GetStreetNames()
{
    // Most common street names in UK, apparently :)
    yield return "High Street"; yield return "Station Road"; yield return "Main Street";
    yield return "Church Street"; yield return "Victoria Road"; yield return "Park Road";
    yield return "Church Road"; yield return "London Road"; yield return "Manor Road";
    yield return "New Road"; yield return "Park Avenue"; yield return "Queens Road";
    yield return "Kings Road"; yield return "Church Lane"; yield return "Green Lane";
    yield return "Alexandra Road"; yield return "The Crescent"; yield return "George Street";
    yield return "Grange Road"; yield return "Main Road"; yield return "King Street";
    yield return "The Avenue"; yield return "New Street"; yield return "North Street";
    yield return "Victoria Street"; yield return "West Street"; yield return "Queen Street";
    yield return "Springfield Road"; yield return "Stanley Road"; yield return "Albert Road";
    yield return "Albert Street"; yield return "Park Lane"; yield return "Chapel Street";
    yield return "Highfield Road"; yield return "The Green"; yield return "Mill Lane";
    yield return "Broadway"; yield return "St. Johns Road"; yield return "Marlborough Road";
    yield return "Windsor Road"; yield return "Forest Road"; yield return "South Street";
    yield return "Warwick Road"; yield return "Grove Road"; yield return "Kingsway";
    yield return "York Road"; yield return "Woodlands Road"; yield return "Clarence Road";
    yield return "School Lane"; yield return "Cromwell Road";
}

(I’d wanted to use the top 50 USA street names, but they’re far more boring as they’re like: Fourth Street, Fifth Street, Oak Street. Smile ).

There’s a tiny class to represent a StreetName that’s used later by the Dapper extension functions:

public class StreetName
{
    public int Id { get; set; }
    public string Name { get; set; }
}

The full main function:

static void Main(string[] args)
{
    const int GroupSize = 8;

    var connectionString = CreateDatabase();
    using (var db = new SqlCeConnection(connectionString))
    {
        db.Open();
        for (var group = 0; ; group++)
        {
            Console.WriteLine(string.Format("== GROUP {0} ==", group));
            var streets = db.Query<StreetName>(@"SELECT * FROM StreetNames
                               ORDER BY Id
                               OFFSET @offset ROWS
                               FETCH NEXT @rows ROWS ONLY",
                                    new { offset = group * GroupSize, 
                                        rows = GroupSize });

            foreach (var street in streets)
            {
                Console.WriteLine(string.Format("{0} ({1})",
                    street.Name, street.Id));
            }
            if (streets.Count() < GroupSize) { break; }
        }
    }

    Console.WriteLine("** Done **");
    Console.ReadKey();
}

The db.Query<StreetName> function is Dapper provided. Given the type and a Query, it automatically and efficiently constructs/deserializes objects of type StreetName for each row returned from the SQL query.

While the syntax isn’t quite as slick as  “Entity Framework” by any means, the resulting run-time efficiency far makes up for that as far as I’m concerned (and if you care about performance, you might want to re-evaluate your use of the EntityFramework as well).

I took the photo above years ago at the Boeing Museum of Flight in Seattle (which inspired me to look for videos). For fun, check out this video of a model SR-71 blackbird. It takes off around 2:21.

Model SR-71 Blackbird in flight.

Get path or location of currently executing batch/command file in Windows

I didn’t know it was this simple, and am posting this information on my blog so I find it in the future, but hopefully this will help someone else!

I’ve created a number of batch files over the years which routinely copy files from one location to another, usually as part of a backup strategy. However, I’ve always just hard-coded the paths of the drives, etc. into the batch files. While this works for drives which are permanently attached (or internal), it’s more fragile with external (flash/USB) drives. As I’ve never understood the logic of drive letter selection in Windows (letters usually are the same, but occasionally not), it meant that I was tweaking the drive letter in the batch file before running. Annoying, but it worked.

Thanks to more than a few web sites, I now know there is a much better way!

There are basically two decent options, depending on your scenario and requirements.

Option 1

If you are using drive letters (and not a mapped drive\network share), then you can use the variable %CD%.

It contains the “current directory.” So, that actually may be more than you wanted if the current directory isn’t the root of the drive.

image

Simple, just chop it off:

image

%CD:~0,2%

The colon and tilde character is a flag which indicates that a substring should be returned rather than the entire string. The first value is the zero-based starting index and the second is the number of characters you want to return:

image

The above starts at character 4, and includes 3 characters.

For fun, you can use negative values:

image

With only a single negative parameter, it returns the number of characters requested starting with the rightmost character.  (Check here for a bunch of examples on string manipulation.)

So, you could use knowledge in a batch file:

image

The above line uses robocopy (available in modern versions of Windows without an extra install) to copy from the folder \\server\Backups to the current path appended with \server\backups. So, if the batch file containing the robocopy command was executing on the J: drive, the resulting robocopy command would be:

image

By using the :~0,2 syntax, regardless of the folder the batch file is located in, it always copies to the root of the J drive (as the first two characters are J and : ).

Option 2

The other option is a bit different as it only works in a batch or command file.

image

Parameter zero (%0) in batch file represents the full path of the currently executing file (path and filename). The (dp) modifiers expand the value to be the “drive” and the “path,” excluding the file name.

image

You can manipulate the value as well:

image

I’m immediately going to adopt the first option into all of my “robocopy” batch files.