我有一个应用程序,多个商店将共享数据。有一个Options表定义了各种程序选项。我有一个varchar列,它定义了值类型,int,bool,Guid,string等。有两个表定义选项值,一个用于系统范围的选项,另一个用于本地选项。基本选项表定义是否可以在系统范围选项之前选择本地选项。 Shop和Global选项表基本上是相同的结构,除了ShopOption表具有记录所属商店的ShopId FK。这些表中的每一个都将选项值存储为varchar,尽管该字符串可能表示整数,Guid,bool,或者实际上可能是字符串。我需要显示一个带有本地选项选项卡的表单,一个全局选项选项卡,以及一个用于指定商店是否可以覆盖全局的选项卡。我做得不对的是获得一个选项实体,并将值作为它应该的类型。
例如:
GetOption(SessionTimeout)应返回Option实体,值应为整数类型。
GetOption(DefaultCustomer)应返回一个Option实体,该值应为Guid类型。
我正在寻找使用设计模式的答案,并认为工厂模式可能是我想要的,但我只是没有得到它。
答案 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
)
类型安全显然是一件好事,但这里的成本很高:
如果您有5个选项,如果这个数字可能会随着时间的推移保持不变,那么第二个解决方案就有其优点。
另一方面,如果你计划最终选择成千上万的选项,这对我来说听起来很简单:追求通用性!
在您的应用程序代码中,使用通用方法很容易解决您的问题:
OptionEntity<T> GetOptions<T>(string OptionName, T defaultvalue);
编辑以回答以下布莱恩的评论:
是的,如果要存储10000个值,则会有10000列。对于你将要写的每一张桌子都是如此。选项表没有什么特别之处。什么都没有。
这一切都取决于我们选择的抽象级别。例如,你如何存储国际象棋棋盘位置?您可以清楚地使用64列表(64个值 - > 64列) 或者您可以使用仅有4列的设计(游戏ID,x,y,内容)。根据具体情况,你不认为两者都足够吗?
在这种特定情况下,如果可以动态创建选项,或者预期它们的数量会呈指数增长,那么这些选项在某种程度上只是另一种类型的数据。并且您不希望在架构中存储数据,是吗?