Friday, June 01, 2007

Enum Flags, Specification Pattern & Explicit Semantics

We use the specification pattern for all the dynamic query operations in our WCF service, and this includes using Nullable<T> on all criteria elements and several "multiple choice" criteria based on [Flags] enumerations. To ensure that our contracts convey explicit semantics, we have used long meaningful names in the enumerations instead of the more cryptic codes that are wellknown in the internal system and that are the domain terms used by the biz-people. These internal codes is not, however, very meaningful to the partners that are consuming the WCF service.

As the multi-choice lists sometimes contain 20+ items, I feared that interpreting the [Flags] enum
and building the repository query would become huge switch statements with lots of ugly bit-wise "and" logic to deduce which of the 20+ items were specified in the criteria. In addition, I needed a way to add the translated code items to a SQL @parameter list as part of a SQL in clause to actually filter records on the criteria.

As I never embark on implementing something ugly, I decided to look for a simpler way of interpreting the [Flags] enum. Knowing that an enum internally is represented by the integers and not the human-friendly names, I decided to add another internal enum that exactly mirrors the contract enum, just using the internal domain codes, and then cast to the internal enum. But the DataContractSerializer has a simpler approach using the [EnumMember] attribute:

[DataContract]
[Flags]

public enum DisciplineFlags
{
None = 0,
. . .
[EnumMember(Value = "
Instrumentation ")]
I = 0x000100,
[EnumMember(Value = "
MarineOperations")]
J = 0x000200,
[EnumMember(Value = "
Materials")]
M = 0x000400,
[EnumMember(Value = "
Navigation")]
N = 0x000800,
//
[EnumMember(Value = "
Process")]
P = 0x001000,
[EnumMember(Value = "
QualityManagement")]
Q = 0x002000,
[EnumMember(Value = "
Piping")]
S = 0x004000,
[EnumMember(Value = "
Telecommunications")]
T = 0x008000,
. . .
}

Note that you must apply the [DataContract] attribute to the enum for the [EnumMember] to take effect. If you just use the plain enum as a [DataMember] property, then the internal names will be published in the contract.

The "None" item is there as the default value for the DataContractSerializer when the incoming XML specification contains no flags filter element (xsi:nil="true"). The nullable flags filter must be asserted like this before usage:

if (filter.DisciplineFlags.HasValue
&& filter.DisciplineFlags != DisciplineFlags.None)

{
. . .
}


So now I had the real one-letter domain codes, but how to avoid bit-wise interpretation ? Also, I needed a list of the codes making up the flags combination. The answer is really simple:

string disciplineList = filter.DisciplineFlags.ToString();
string[] inValues = disciplineList.Split(new char[] {','}, StringSplitOptions.RemoveEmptyEntries);

The last problem is that you cannot add the list of values for an in-clause as a @parameter. Just concatenating the CSV-string into the SQL is not an option due to SQL-injection attacks. So a for-loop is needed to add SQL parameters and related values:

for (int i = 0; i < inValues.Length; i++ )
{
string param = "@in" + i;
if (i > 0) sql += ", ";
sql += param;
filterCommand.Parameters.AddWithValue(param, inValues[i].Trim());
}

I would rather have avoided the for-loop, but at least the specification interpreter became much simpler than I expected it to be. No pesky switch statements and bit-wise logic.

See the end of this earlier post for further details about implementing specification filters using TableAdapters.

No comments: