如何实现具有多种返回类型的选项

时间:2009-12-29 21:18:28

标签: c# design-patterns

我有一个应用程序,多个商店将共享数据。有一个Options表定义了各种程序选项。我有一个varchar列,它定义了值类型,int,bool,Guid,string等。有两个表定义选项值,一个用于系统范围的选项,另一个用于本地选项。基本选项表定义是否可以在系统范围选项之前选择本地选项。 Shop和Global选项表基本上是相同的结构,除了ShopOption表具有记录所属商店的ShopId FK。这些表中的每一个都将选项值存储为varchar,尽管该字符串可能表示整数,Guid,bool,或者实际上可能是字符串。我需要显示一个带有本地选项选项卡的表单,一个全局选项选项卡,以及一个用于指定商店是否可以覆盖全局的选项卡。我做得不对的是获得一个选项实体,并将值作为它应该的类型。

例如:
GetOption(SessionTimeout)应返回Option实体,值应为整数类型。
GetOption(DefaultCustomer)应返回一个Option实体,该值应为Guid类型。

我正在寻找使用设计模式的答案,并认为工厂模式可能是我想要的,但我只是没有得到它。

5 个答案:

答案 0 :(得分:9)

潜在的问题是你正在遭受inner platform effect,你试图通过存储varchar 应该不同的数据库来在数据库中建立一个数据库,键入的列。

您已经为自己提供了在运行时添加选项的能力。但是,如果应用程序不理解它们,它们就没有任何意义,并且您无法在运行时添加该理解。必须在设计时知道这组选项,这意味着可以在设计时知道模式,这意味着您无需将结构抽象为varchar值。

创建一个包含代表每个选项的列的表,并使用常规的ORM实践来声明它映射到的数据类型。

抽象实际上并没有给你买任何东西。

根据OP的评论进行编辑:

要实现级联设置,您可以创建一个OptionSet表,其中每个选项都有一列。只有一行代表全局集。对于可由管理器覆盖的每个选项,请将可为空的列添加到Store表。

然后你可以有一个方法要求Store合并有效选项:

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
        }
    }
}

正如您所看到的,这允许所有内容保持强类型,同时解决无法覆盖的选项问题。它还表达了通过在Store表上指定所有选项(反映其范围)并使它们可为空(可反映其可选性质)来覆盖选项可以的意图。

好的部分是没有新技术可供学习或“魔术”实现(除非你没有看到?? operator,这相当于T-SQL的COALESCE函数)

答案 1 :(得分:4)

你要求的是后期绑定,即能够在运行时而不是编译时分配变量类型。直接的答案是C#目前不支持,当它受支持时,它仍然无法完全解决您的问题。

最好的办法是使用泛型,这样可以提高类型安全性,但仍然无法防止愚蠢的错误。创建一个类似的方法:

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
}

将允许您尝试将数据库结果强制转换为T的返回类型,但如果转换失败,它将生成异常(即:您尝试将GUID选项请求为int)。

答案 2 :(得分:3)

看一下Martin Fowlers的书Analysis Patterns。 Fowler称之为“数量”,请看第3章的开头。这基本上是相同的,只需用“选项”替换“数量”即可。当然,这只是数据库中问题的一部分。对于应用程序部分,我建议使用Yooder的解决方案。

答案 3 :(得分:2)

我在类似情况下经常看到的是提供类型默认值:

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

答案 4 :(得分:2)

有两种解决方案,每种解决方案都有其优点和缺点:

选项1:通用性

系统范围的选项表,定义如下:

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. 
)

用户选项表:

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

代码包含与OptionName列匹配的枚举,因此从代码解析选项是微不足道的。

缺点:

  • 类型安全只能使用约束或触发器来实现(这显然比列类型更难维护)
  • 直接从数据库中使用存储的选项更加困难(因为解析逻辑存在于应用程序代码中)
  • 检索特定用户的所有选项的成本更高(您不能只选择用户行)

选项2:专业化(强打字)

强类型选项表,每个选项包含一列

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

类型安全显然是一件好事,但这里的成本很高:

  • 添加新选项需要架构更改
  • 用于更新表的存储过程将包含大量重复的代码,因为逻辑(例如isLocked机制,或者您可能想要添加的一些额外日志记录)必须反复为每个字段重复。这就是你最终得到包含1500个参数的存储过程的方法。
  • 此解决方案无法很好地扩展,因为表格不能包含无限数量的列(例如,请参阅SQL Server 2008的最大值)。

如果您有5个选项,如果这个数字可能会随着时间的推移保持不变,那么第二个解决方案就有其优点。

另一方面,如果你计划最终选择成千上万的选项,这对我来说听起来很简单:追求通用性!


在您的应用程序代码中,使用通用方法很容易解决您的问题:

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

编辑以回答以下布莱恩的评论:

  

是的,如果要存储10000个值,则会有10000列。对于你将要写的每一张桌子都是如此。选项表没有什么特别之处。什么都没有。

这一切都取决于我们选择的抽象级别。例如,你如何存储国际象棋棋盘位置?您可以清楚地使用64列表(64个值 - > 64列) 或者您可以使用仅有4列的设计(游戏ID,x,y,内容)。根据具体情况,你不认为两者都足够吗?

在这种特定情况下,如果可以动态创建选项,或者预期它们的数量会呈指数增长,那么这些选项在某种程度上只是另一种类型的数据。并且您不希望在架构中存储数据,是吗?