C# Design – Multi-User Configuration Settings Design Patterns

cconfigurationdatabase-designdesigndesign-patterns

I am designing a flexible and extensible way to store configuration settings for users.

Database Table Design:

╔═════════════════════════════╗ 
║ ConfigurationItemDefinition ║ 
╠═════════════════════════════╣ 
║ ID                          ║ 
║ Name                        ║ 
║ Type                        ║ 
║ DefaultValue (varchar)      ║ 
║ ConfigurationItemAdapterID  ║ 
╚═════════════════════════════╝ 
              |
╔═══════════════════════════════╗
║ ConfigurationItem             ║
╠═══════════════════════════════╣
║ ID                            ║
║ UserID                        ║
║ ConfigurationItemDefinitionID ║
║ Value (varchar)               ║
╚═══════════════════════════════╝

Explanation:

The ConfigurationItemDefinition table holds the definition for configuration settings. Type is the .NET type which the value would be parsed/serialized into when queried in the code.

The ConfigurationItem table is where configuration values are set for each user. If there is no value explicitly set for a specific user for some configuration item definition, then the DefaultValue value from ConfigurationItemDefinition is used.

ConfigurationItemAdapterID is determines what technique is to be used to 'get' and 'set' the configuration value. For example, an int value would use a basic Parse (from string -> int). A CultureInfo object would need to use a serialization adapter. The way in which configuration values are get and set is determined by the adapter (examples below).

C# Code

In C#, a setting would be acquired like this:

// This would use the Parse adapter
int timeout = someUser.GetConfigurationValue<int>("TimeoutValue"); 

// This would use the Serialization adapter
CultureInfo cultureInfo = someUser.GetConfigurationValue<CultureInfo>("CultureInformation");

And set like this:

// This would use the Parse adapter
someUser.SetConfigurationValue<bool>("IsLive", true);

Please critique this design. Are there more standard ways of dealing with such a problem?

Best Answer

That solution looks altogether workable to me.

One nitpick: I find that when I have to store different types of things in a uniform way, and there are only a small, non-expandable number of types that can exist, I'd rather have separate methods readInt(), readString() etc. and live with a small amount of duplication rather than specify the generic parameter on every access. At least, I would write "syntactic sugar" wrappers in that style. But that really is a question of what you consider more readable. Obviously, if there are an unbounded number of types that you have to support, the generic solution is the way to go.