How to implement parameters with multiple return types

I have an application in which several stores will share data. There is an options table that defines various program parameters. I have a varchar column that defines the type of value, int, bool, Guid, string, etc. There are two tables that define parameter values, one for system parameters and one for local options. The table of basic options determines whether it is possible to select a local option before the system parameter. The Shop and Global option tables basically have the same structure, except that the ShopOption table has ShopId FK for the store to which the record belongs. Each of these tables stores the parameter values ​​as varchar, although the string can be an integer, Guid, bool, or it can actually be a string. I need to show a tabbed form for a local parameter, a global option tab and a tab to indicate whether the store can exceed the global one. What I am not doing right is to get the option object and get the value as the type it should be.

For instance:
GetOption (SessionTimeout) must return an Option object, and the value must be an integer type.
GetOption (DefaultCustomer) should return an Option object, and the value should be of type Guid.

I am looking for an answer using design patterns, and I think that the factory pattern may be what I want, but I just don't understand it.

+4
source share
5 answers

There are two solutions, each of which has its own advantages and disadvantages:

Option 1: Genericity

a table of parameters for the entire system, defined as follows:

Create table tbGlobalOptions ( OptionName Varchar(255) Identity, OptionValue Varchar(255), OptionType varchar(255) isLocked bit --this indicated the value cannot be overridden by the user. ) 

And the table of user options:

 Create table tbUserOptions ( OptionName varchar(255) UserID bigint, OptionValue varchar(255), Active bit ) -- extra fields for logging omitted -- keys omitted 

The code contains an enumeration corresponding to the OptionName column, so parsing parameters from the code is trivial.

Minuses:

  • type safety can only be implemented using constraints or triggers (which is clearly more difficult to maintain than column types).
  • it’s more difficult to use saved parameters directly from the database (since the parsing logic lives in the application code)
  • getting all parameters for a specific user is more expensive (you cannot just select a user line)

Option 2: Specialization (and Strong Typing)

Strongly typed options table containing one column for each option

 Create table tbOptions ( UserId bigint, -- 0 for global defaults Option1 int, Option2 varchar(max) Option3 int, ... Option426 bit ) 

The type of security is certainly a good thing, but here it has huge costs:

  • adding a new option requires changing the scheme
  • the stored procedures used to update the table will contain a lot of duplicated code, since the logic (for example, the isLocked mechanism or some additional entries that you can add) must be repeated over and over for each field. Here's how you ended up with stored procedures containing 1,500 arguments.
  • This solution does not scale well because the table cannot have an unlimited number of columns (see maximum values ​​for SQL Server 2008 here for example).

If you have 5 options, and if that number is likely to remain unchanged over time, the second solution has its advantages.

If, on the other hand, you plan to eventually use thousands of options, this sound, as if without problems, for me: go to the pedigree!


In the application code, your problem is quite easily resolved using the general method:

 OptionEntity<T> GetOptions<T>(string OptionName, T defaultvalue); 

Change the response to Brian's comment below:

And yes, if there are 10,000 values ​​to store, there will be 10,000 columns. This is true for every table you will ever write. The options table is nothing special. Nothing.

It all depends on the level of abstraction that we have chosen. How could you maintain a chessboard position, for example? You can clearly use a table of 64 columns (64 values ​​→ 64 columns) or you can use a design with only 4 columns (game identifier, x, y, content). Don't you think that both may be appropriate depending on the situation?

In this particular case, if the parameters can be created on the fly, or if their numbers grow exponentially, these parameters are to some extent just another type of data. And you don't want to store data in your schema, right?

+2
source

The main problem is that you are suffering from the internal effect of the platform where you are trying to create a database in a database by storing as varchar what should be great, printed columns.

You have given yourself the opportunity to add parameters at runtime. However, they mean nothing if the application does not understand them, and you cannot add this understanding at run time. The set of parameters must be known at design time, which means that the circuit can be known at design time, which means that you do not need to abstract the structure into varchar values.

Create a table with columns representing each of your options, and use the usual ORM methods to declare the data type to which it refers.

Abstraction does not actually buy you.

Edit in response to a comment from OP:

To implement cascading settings, you can make an OptionSet table with a column for each parameter. There will be only one row representing the global set. For each option that can be overridden by the manager, add a column with a null value to the Store table.

Then you can use the method that asks the Store combine the effective parameters:

 public class Store { public virtual bool? AllowSavePasswords { get; set; } public virtual OptionSet GetEffectiveOptions(OptionSet globalOptions) { return new OptionSet { AllowSavePasswords = this.AllowSavePasswords ?? globalOptions.AllowSavePasswords, LoginTimeout = globalOptions.LoginTimeout // Repeat pattern for all options } } } 

As you can see, this allows everyone to remain strictly typed when deciding on options that cannot be overridden. It also expresses the intention to redefine the parameters, indicating all of them in the Store table (reflecting their scope) and making them nullified (reflecting their optional nature).

The nice part is that there are no new training methods or “magic” for implementation (if you have not seen the ? Operator, which is equivalent to the T-SQL COALESCE function).

+9
source

What you are asking for is late binding, i.e. the ability to assign a variable type at runtime instead of compile time. The immediate answer is that C # does not currently support this, and when it is supported, it will still not be able to completely solve your problem.

It is best to use generics that will increase type safety, but still not prevent stupid errors. Creating a method of type:

 public T GetOption<T>(string key) { // Retrieve the option type and value // Check that the option type and return type (T) are compatible // cast the option value to T // return the value } 

will allow you to try to apply the database result to the return type T, but it will throw an exception if the reset fails (i.e. you are trying to request the GUID parameter as int).

+4
source

Take a look at Martin Fowlers book Analysis Templates . Fowler calls it “Quantity,” look at the beginning of Chapter 3. This is essentially the same, just replace “Quantity” with “Option.” Of course, this is just part of the problem database. For part of the application, I suggest using the Yooder solution.

+3
source

Something I've seen quite often in similar situations is providing a typed default value:

 OptionEntity<T> GetOptions<T>(string OptionName, T defaultvalue); 
+2
source

All Articles