A pleasant walk through computing

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

Sane Database Schema Conventions

Rationale

These are sane conventions for constructing and naming a database schema. They aren't new, and there's sure to be something someone doesn't like. They are biased toward the .Net EntityFramework, which itself was influenced by the Ruby on Rails ActiveRecord conventions by David Heinemeier Hansson.

Using these conventions makes it easier to translate the tables into classes. While this isn't always desirable (or correct), it often is.

Sample

This sample schema exemplifies the conventions, and shows most relationships you'll encounter. It's semi-realistic. PropertyRecords is contrived to show a pseudo one-to-one relationship. (Note: MS SQL doesn't allow a true one-to-one structure, as different tables' rows can't be simultaneously created.)

 

sample-schema

  • A Customer has an Initial contact Employee, Support Employee and a Salesperson. An Employee can service multiple Customers.
  • An Employee can be a Salesperson.
  • A Customer has one or more Addresses, an Address belongs to one Customer.
  • An Address has one Property Record, a Property Record is for one Address.
  • A Customer has zero or more Orders, an Order has one Customer.
  • An Order can have many Vendors, a Vendor can fulfill many Orders.
  • An Order can have many Promotions, a Promotion is for zero or more Orders.
  • An Order Promotion has zero or more customer notifications.

Table Column Layout

I like my table columns ordered this way.

  1. Primary key
  2. Foreign keys
  3. Regular columns
  4. Audit columns

General Names

Pluralize Most Table Names

Pluralizing table names reduces the chances of keyword conflicts, and matches how the table will (typically) be treated in an ORM tool.

Yes            |No               
---------------|-----------------
Customers      |Customer
Salespeople    |Salesperson

Don't pluralize many-to-many join tables. By convention, keep the table name parts alphabetical.

Note
Using Code First, Entity Framework might pluralize this to OrderVendors. Personally, I'd use the FluentAPI to force the table name to the OrderVendor.

Yes            |No               
---------------|-----------------
OrderVendor    |OrdersVendors, OrderVendors, VendorOrder              

Use PascalCase

Tables and columns should be in PascalCase.

Yes            |No               
---------------|-----------------
Customers      |customer
OrderNbr       |orderNbr

No Dashes or Underscores

It's tempting to separate words in either table or column names, but don't. Keeping them PascalCased makes the transition to classes easier and clearer. Also, some databases don't play nicely with underscores, or dashes, depending on how they're used.

Yes            |No               
---------------|-----------------
OrderVendor    |Order_Vendor
OrderNbr       |order-number
CustomerId     |Customer_ID

Keys

My personal preference is to end key names with "Id", rather than "ID". It reads just as well, and is consistent with PascalCasing and .Net naming conventions.

Primary Key

Some people prefer a primary key of just "Id", but if you need to run SQL queries (and you will), it's easier to have the table name in the primary key for creating joins and reading the results.

SELECT  c.CustomerId, c.Name, a.AddressId, a.Address1
FROM    Customers c
        JOIN Addresses a on c.CustomerId = a.CustomerId

--Result:

CustomerId  Name  AddressId Address1
----------  ----  --------- ----------
        23  Ron         402 12 Main St
        47  Eve          11 3 Polo Ave        

Simple Tables
TableName + "Id"

Yes            |No               
---------------|-----------------
CustomerId     |CustomerID, Customer_id, Customer_ID

Many-to-Many Tables
A regular join table doesn't need its own primary key. Just use the other tables' primary keys to form a composite key.

OrderVendor
===========
OrderId  (PK, FK)
VendorId (PK, FK)

The resulting classes should have these properties

class Order 
{
  IEnumerable<Vendor> Vendors
}

class Vendor
{
  IEnumerable<Order> Orders
}

A join table with payload--one that has its own columns and/or will be joined to another table, should have its own primary key of Table1+Table2+Id

OrderPromotion
==============
OrderPromotionId (PK)
OrderId          (FK)
PromotionId      (FK)

The resulting classes

class Order
{
  IEnumerable<OrderPromotion> OrderPromotions
  //You can manually add this method to get Promotions
  IEnumerable<Promotion> Promotions
  {get {return OrderPromotions.Select(op => op.Promotion);}}
}

class Promotion
{
  IEnumerable<OrderPromotion> Order Promotions
}

Foreign Key

When possible, use the referenced primary key name. If there are multiple foreign keys to the same table, end with the foreign primary key name.

Yes                       No               
-------------             -----------------
Employees
=========
EmployeeId (PK)

Customers                 Customers
=========                 =========
CustomerId (PK)           Customer_Id (PK)
SupportEmployeeId (FK)    SupportPerson (FK)
InitialEmployeeId (FK)    InitialEmp_ID (FK)

Addresses                 Addresses
=========                 =========
AddressId  (PK)           AddressId (PK)
CustomerId (FK)           CustID    (FK)

Date Columns

Use a verb, and end the date or datetime column names with "On".

Following this convention often leads to clearer column meanings, and consistency. For example, DateToPlace or PlaceDateTime becomes PlaceOrderOn.

Yes            |No               
---------------|-----------------
OrderedOn      |OrderDateTime
ShouldShipOn   |DateShipExpect, ExpectedShip_DT, AnticipatedDate

Don't Abbreviate

Abbreviations are, by their nature, ambiguous and culture-centric. Avoid them unless they are very common, consistent, and/or well-known in the organization or industry.

A good example of ambiguity is how to abbreviate "number". Even my schema example, "InvoiceNbr", is potentially ambiguous. But InvNo is worse. Is there another column "InvYes"?

Yes            |No               
---------------|-----------------
Customers      |Custs
CustomerId     |CustId
FirstName      |Fname
InvoiceNbr     |InvNo

Use Consistent Names (and Abbreviations if You Must)

  • If you must abbreviate, be consistent.
  • If it's spelled "InvoiceNbr" in one table, it's that way in all tables.
  • If everyone knows what DestinationBOL means, that might be OK. But maybe it's better to expand it to DestinationBillOfLading.

When in doubt, refer to Flatt's Law #6: Clarity is more important than brevity.

Yes            |No               
---------------|-----------------
InvoiceNbr     |InvoiceNum, InvNumber, Inv_Nmbr

Auditing

To be honest, I've often found audit columns to be more trouble than they're worth. I think if auditing is needed, it's better to have a separate audit history table where you can record many kinds of changes, including deletions.

But, if I am using them, and am tracking who took an action, I don't link to another table (such as Users or Employees), but instead record the physical name. This significantly reduces linking, and makes it easy to indicate that a process (rather than a person) performed an action. In other words, UpdatedBy is a string column and contains a value like "cflatt" or "Nightly Batch Process".

  • CreatedOn
  • CreatedBy
  • UpdatedOn
  • UpdatedBy

Inheritance

Inheritance can be modeled in the database a couple of different ways. I prefer Table Per Type, as shown by the Employee and Salesperson tables. Note that the Salesperson table has an EmployeeId primary key. This is what implies the inheritance. In the application's class model, these would become:

public class Employee
{
  int EmployeeId
  string Name 
}

public class Salesperson: Employee 
{
  double CommissionPercent 
}

public class Customer
{
  int CustomerId
  string Name
  Employee SupportEmployee
  Employee InitialEmployee
  Salesperson Salesperson
}

TFS Continuous Integration - Agent Installation and Visual Studio Licensing

The Summary

A build agent is what takes care of actually running a build definition. Agents can be installed on machines separate from the TFS server, allowing workload distribution.

The simple way to understand an agent is to imagine how you'd create continuous integration yourself.

  1. You'd have a machine that could build the software. That means you'd have to install anything needed to accomplish the build, such as Visual Studio, 3rd party controls, certificates, tools, etc.

  2. You'd write a script that could automate the build and report errors.

  3. You'd create a way of running that script on demand, such as developing a Windows service.

That's all TFS Build is doing. You configure the steps (the build script) on the TFS server as a build definition. You install an agent on a machine that can check out the source code and successfully build the application. TFS calls the agent on demand.

It was unclear to me if I needed a licensed version of Visual Studio, or VS installed at all. The answers are:

  • If you're not using the Visual Studio build step, and only the MS Build step, you might be able to get away with installing the 2015 Build Tools.
  • However, you'll probably need Visual Studio installed. It does not need to be licensed, assuming it's not also being used for development.

VS 2015 Licensing White Paper

Using Visual Studio on the Build Server: If you have one or more licensed users of Visual Studio Enterprise with MSDN, Visual Studio Professional with MSDN, or any Visual Studio cloud subscription then you may also install the Visual Studio software as part of Team Foundation Server 2017 Build Services. This way, you do not need to purchase a Visual Studio license to cover the running of Visual Studio on the build server for each person whose actions initiate a build.

References

Installing an agent is pretty simple. Really, just read Ben Day's post and you'll find out what you need. It's slightly outdated, but close enough. I've also listed the steps, below.

https://www.benday.com/2016/01/01/walkthrough-create-a-new-tfs2015-build-server/

The Installation

  1. Install everything needed to build the software. It's best to do this first.
  2. Download the agent from the TFS web. Manage Server (click the right corner gear) > click link "View collection administration page" > open Agent Queues tab > click "Download agent"
  3. Extract the zip into C:\TfsData\Agents[agent name]
  4. Run ConfigureAgent.cmd
  5. Mostly accept the defaults. The TFS server URL will be something like http://servername:8080/tfs. Answer Y to installing as a service.

After installation, you should see the agent in the Agent Queues.

Maintenance

Agent Versions

If you're using a local TFS installation, the agent version is tied to the TFS version. If you update TFS, be sure to update the agents. It's easy. In Agent Queues, right-click the queue and choose Update All Agents.

Adding Agent Capabilities

Normally, all you need to do is install the software with the capability, then restart the agent. However, here are a couple of articles related to capabilities.

How to Register Capabilities
Demands

The Wrap Up

Agents are just services that run build steps. An agent can be installed on almost any machine, letting you easily configure your build environment.

TFS Continuous Integration - ClickOnce Apps

The Summary

Oh, ClickOnce, you bane of development! You're always so attractive: easily created, self-updating installations. But, like a 21st century TV vampire, you end up sucking the life out of me when things get complicated.

In the case of continuous integration, we need to sign our application using a security certificate, to guarantee the publisher's identity. This makes sense, since the intent is that ClickOnce is installed and maintained from a web site.

http://stackoverflow.com/questions/8955332/what-is-signing-clickonce-manifests-for

So, there are two parts to manage in CI: the certificate and the signing.

There are several combinations for trying to build ClickOnce. Is your TFS on site, or are you using Visual Studio Online? Are you signing using a commercial, local-domain, or temporary certificate?

This document is for a specific circumstance:

  • Local TFS 2015
  • A temporary certificate

Note
This post will not deal with publishing a ClickOnce application via CI.

The Problems

So what happens when you try to build a ClickOnce app and on a separate CI server (without Visual Studio installed)?

It fails, that's what. At minimum, in the above scenario of using the default temporary certificate (which you shouldn't), it will fail because the signing utility, signtool.exe, isn't installed on the server.

How do you manage the signing process on a locally hosted machine?

Locally Installed TFS

Install SignTool.exe on the Server

When a developer creates a ClickOnce app, she must have the ClickOnce tools installed. In Visual Studio 2015, this is a feature checkbox during installation. If your forgot, you can open Programs and Features, right-click Visual Studio, and choose Change to rerun the installation.

https://social.msdn.microsoft.com/Forums/en-US/2d9414f5-1e78-4d33-b651-d09be74db80d/clickonce-publish-in-visual-studio-2015?forum=winformssetup

But we're not going to install Visual Studio on the server.

Just the Files?

Can we create the appropriate folder path on the server and just copy the needed files, instead of installing 1GB of utilities? Maybe. It looks like the required files are:

signtool.exe
mssign32.dll
wintrust.dll

And the paths are:

C:\Program Files (x86)\Windows Kits\8.1\bin\x86
C:\Program Files (x86)\Windows Kits\10\bin\x86

I used the first path on Windows 10 and it worked fine. This is definitely worth testing before installing the SDK.

Via the Windows SDK

We need to install the super-bloated SDK. Which one?

If you're on Windows 8.1/10/2012/2012R2, install the Windows 10 SDK.

https://developer.microsoft.com/en-us/windows/downloads/windows-10-sdk

If you're on Windows 7/8/2008R2, install the Windows 8 SDK.

https://developer.microsoft.com/en-us/windows/downloads/windows-8-1-sdk

During installation, choose the Windows Software Development Kit. Funny, in Windows 10 they moved the SDK to the bottom!

2017-03-03_151212

2017-03-03_151303

Note
On my Windows 10 machine, running Visual Studio 2015, when I installed the ClickOnce tools, signtool.exe was installed to the Windows 8.1 SDK folder instead of Windows 10. It works...but go figure.

The Wrap Up

For my specific case, it was relatively easy to get signing to work during the build. If a non-temporary certificate had been involved, I could have installed that to the server.

This doesn't answer what to do if using Visual Studio Online or some other continuous integration server. That will be an adventure for another day.