Sql server 本地化SQL Server(2005/2008)数据库的最佳做法 问题:
我相信你们中的许多人都面临着将数据库后端本地化到应用程序的挑战。如果你还没有,那么我会很有信心地说,你将来不得不这么做的可能性很大。我说的是为数据库实体存储多个文本翻译(货币等也可以这么说) 例如,经典的“Category”表可能有一个名称和一个应该全球化的描述列。一种方法是为每个实体创建一个“文本”表,然后根据提供的语言进行连接以检索值 这就给您留下了许多“文本”表,每个表对应于您想要本地化的实体,并添加了一个TextType来区分它可能存储的各种文本 我很好奇,在SQL Server 2005/2008数据库中实现这种支持时,是否有任何有文档记录的最佳实践/设计模式(我对RDBMS的具体描述是因为它可能包含受支持的关键字以及有助于实现的关键字) 关于XML方法的思考 我一直在玩弄的一个想法(尽管目前为止只是在我的头脑中)是利用SQLServer2005中引入的XML数据类型。其想法是创建支持XML数据类型本地化的列(并将模式绑定到该列)。XML将包含本地化字符串以及它所绑定的语言代码/区域性 类似于Sql server 本地化SQL Server(2005/2008)数据库的最佳做法 问题:,sql-server,linq-to-sql,database-design,localization,Sql Server,Linq To Sql,Database Design,Localization,我相信你们中的许多人都面临着将数据库后端本地化到应用程序的挑战。如果你还没有,那么我会很有信心地说,你将来不得不这么做的可能性很大。我说的是为数据库实体存储多个文本翻译(货币等也可以这么说) 例如,经典的“Category”表可能有一个名称和一个应该全球化的描述列。一种方法是为每个实体创建一个“文本”表,然后根据提供的语言进行连接以检索值 这就给您留下了许多“文本”表,每个表对应于您想要本地化的实体,并添加了一个TextType来区分它可能存储的各种文本 我很好奇,在SQL Server 200
Product
ID (int, identity)
Name (XML ...)
Description (XML ...)
然后,您将有类似这样的XML
<localization>
<text culture="sv-SE">Detta är ett namn</text>
<text culture="en-EN">This is a name</text>
</localization>
您将获得带有所有本地化文本的产品,这意味着您必须在客户端进行提取。这里最大的问题显然是每个查询必须返回的额外数据量,其好处是设计更简洁,没有查找表、联接等
顺便说一句,在我的设计中最终使用的方法是什么?我仍然会使用Linq To SQL(.NET平台)来查询数据库(XML方法应该是一个问题,因为它会返回一个XElement,可以在客户端进行解释)
因此,关于数据库本地化设计模式的建议,以及可能对XML思想的评论,将非常可取。我不明白为什么需要多个文本表。具有“全局”唯一文本ID的单个文本表就足够了。该表将包含ID、language、text列,并且您将只获得需要呈现的语言中的文本(或者可能根本不检索文本)。连接应该相当有效,因为(ID、语言)的组合是主键。这是一个很难回答的问题,因为答案中有太多的“它取决于”: 答案取决于数据库中本地化项的数量、部署场景、缓存问题、访问模式等。如果你能给我们一些关于应用程序有多大,有多少并发用户,以及如何部署的数据,那将非常有帮助 一般而言,我通常使用以下两种方法之一:
第一种方法的优点是具有良好的VisualStudio支持。第二种方法的优点是集中部署。我认为使用XML列存储本地化值没有任何好处。除非你有一个项目的所有本地化版本“在一个地方”,如果这对你有价值的话 我建议在每个具有可本地化项的表中使用cultureID列。这样,您根本不需要任何XML处理。您的数据已经存在于关系模式中,那么当关系模式完全能够处理问题时,为什么还要引入另一层复杂性呢 假设“sv SE”的cultureID=1,“en en”的cultureID=2 然后您的查询将被修改为
SELECT *
From Product
Where Product.ID = 10 AND Product.cultureID = 1
一个瑞典客户
我经常在本地化数据库中看到这种解决方案。它可以根据区域性的数量和数据记录的数量进行良好的扩展。它避免了XML解析和处理,并且易于实现
还有一点:XML解决方案为您提供了不需要的灵活性:例如,您可以从“Name”列中获取“sv SE”值,从“Description”列中获取“en en”值。但是,您不需要这样做,因为您的客户一次只请求一种文化。灵活性通常是有代价的。在这种情况下,您需要单独解析所有列,而使用cultureID解决方案,您可以获得整个记录,其中包含所请求的区域性的所有值。我认为您可以坚持使用XML,它允许更干净的设计。我将进一步利用
xml:lang
属性,该属性:
请注意,与单独的表1相比,此解决方案是一个优雅但效率较低的解决方案。对于某些应用程序来说,这可能没问题。我喜欢XML方法,因为如果没有瑞典语翻译(cultureID=1),则单独的表解决方案不会返回结果,除非您进行外部联接。但无论如何,你不能回到英语上来。使用XML方法,您可以简单地回到英语。
关于producitve环境中的XML方法有什么消息吗?这里是Rick Strahl博客上的一些想法: 我更喜欢在UserSetting表中使用一个开关,这是通过调用存储过程来使用的。。。下面是一些代码
CREATE TABLE [dbo].[Lang_en_US_Msg](
[MsgId] [int] IDENTITY(1,1) NOT NULL,
[MsgKey] [varchar](200) NOT NULL,
[MsgTxt] [varchar](2000) NOT NULL,
[MsgDescription] [varchar](2000) NOT NULL,
CONSTRAINT [PK_Lang_US-us__Msg] PRIMARY KEY CLUSTERED
(
[MsgId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[User](
[UserId] [int] IDENTITY(1,1) NOT NULL,
[FirstName] [varchar](50) NOT NULL,
[MiddleName] [varchar](50) NULL,
[LastName] [varchar](50) NULL,
[DomainName] [varchar](50) NULL,
CONSTRAINT [PK_User] PRIMARY KEY CLUSTERED
(
[UserId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
CREATE TABLE [dbo].[UserSetting](
[UserSettingId] [int] IDENTITY(1,1) NOT NULL,
[UserId] [int] NOT NULL,
[CultureInfo] [varchar](50) NOT NULL,
[GuiLanguage] [varchar](10) NOT NULL,
CONSTRAINT [PK_UserSetting] PRIMARY KEY CLUSTERED
(
[UserSettingId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
去
我总体上看到了delima—您有一个必须表示为单个实例的实体(例如一个ProductID为“10”),但有多个不同列/属性的本地化文本。这是一个艰难的过程,我确实看到了PO的必要性
<l10n>
<text xml:lang="sv-SE">Detta är ett namn</text>
<text xml:lang="en-EN">This is a name</text>
</l10n>
SELECT Name.value('(l10n/text[lang()="en"])[1]', 'NVARCHAR(MAX)')
FROM Product
WHERE Product.ID=10;
CREATE TABLE [dbo].[Lang_en_US_Msg](
[MsgId] [int] IDENTITY(1,1) NOT NULL,
[MsgKey] [varchar](200) NOT NULL,
[MsgTxt] [varchar](2000) NOT NULL,
[MsgDescription] [varchar](2000) NOT NULL,
CONSTRAINT [PK_Lang_US-us__Msg] PRIMARY KEY CLUSTERED
(
[MsgId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[User](
[UserId] [int] IDENTITY(1,1) NOT NULL,
[FirstName] [varchar](50) NOT NULL,
[MiddleName] [varchar](50) NULL,
[LastName] [varchar](50) NULL,
[DomainName] [varchar](50) NULL,
CONSTRAINT [PK_User] PRIMARY KEY CLUSTERED
(
[UserId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
CREATE TABLE [dbo].[UserSetting](
[UserSettingId] [int] IDENTITY(1,1) NOT NULL,
[UserId] [int] NOT NULL,
[CultureInfo] [varchar](50) NOT NULL,
[GuiLanguage] [varchar](10) NOT NULL,
CONSTRAINT [PK_UserSetting] PRIMARY KEY CLUSTERED
(
[UserSettingId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
ALTER TABLE [dbo].[UserSetting] ADD CONSTRAINT [DF_UserSetting_CultureInfo] DEFAULT ('fi-FI') FOR [CultureInfo]
GO
CREATE TABLE [dbo].[Lang_fi_FI_Msg](
[MsgId] [int] IDENTITY(1,1) NOT NULL,
[MsgKey] [varchar](200) NOT NULL,
[MsgTxt] [varchar](2000) NOT NULL,
[MsgDescription] [varchar](2000) NOT NULL,
[DbSysNameForExpansion] [varchar](50) NULL,
CONSTRAINT [PK_Lang_Fi-fi__Msg] PRIMARY KEY CLUSTERED
(
[MsgId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
CREATE PROCEDURE [dbo].[procGui_GetPageMsgs]
@domainUser varchar(50) , -- the domain_user performing the action
@msgOut varchar(4000) OUT, -- the (error) msg to be shown to the user
@debugMsgOut varchar(4000) OUT , -- this variable holds the debug msg to be shown if debug level is enabled
@ret int OUT -- the variable indicating success or failure
AS
BEGIN -- proc start
SET NOCOUNT ON;
declare @procedureName varchar(200)
declare @procStep varchar(4000)
set @procedureName = ( SELECT OBJECT_NAME(@@PROCID))
set @msgOut = ' '
set @debugMsgOut = ' '
set @procStep = ' '
BEGIN TRY --begin try
set @ret = 1 --assume false from the beginning
--===============================================================
--debug set @procStep=@procStep + 'GETTING THE GUI LANGUAGE FOR THIS USER '
--===============================================================
declare @guiLanguage nvarchar(10)
if ( @domainUser is null)
set @guiLanguage = (select Val from AppSetting where Name='guiLanguage')
else
set @guiLanguage = (select GuiLanguage from UserSetting us join [User] u on u.UserId = us.UserId where u.DomainName=@domainUser)
set @guiLanguage = REPLACE ( @guiLanguage , '-' , '_' ) ;
--===============================================================
set @procStep=@procStep + ' BUILDING THE SQL QUERY '
--===============================================================
DECLARE @sqlQuery AS nvarchar(2000)
SET @sqlQuery = 'SELECT MsgKey , MsgTxt FROM dbo.lang_' + @guiLanguage + '_Msg'
--===============================================================
set @procStep=@procStep + 'EXECUTING THE SQL QUERY'
--===============================================================
print @sqlQuery
exec sp_executesql @sqlQuery
set @debugMsgOut = @procStep
set @ret = @@ERROR
END TRY --end try
BEGIN CATCH
PRINT 'In CATCH block.
Error number: ' + CAST(ERROR_NUMBER() AS varchar(10)) + '
Error message: ' + ERROR_MESSAGE() + '
Error severity: ' + CAST(ERROR_SEVERITY() AS varchar(10)) + '
Error state: ' + CAST(ERROR_STATE() AS varchar(10)) + '
XACT_STATE: ' + CAST(XACT_STATE() AS varchar(10));
set @msgOut = 'Failed to execute ' + @sqlQuery
set @debugMsgOut = ' Error number: ' + CAST(ERROR_NUMBER() AS varchar(10)) +
'Error message: ' + ERROR_MESSAGE() + 'Error severity: ' + CAST(ERROR_SEVERITY() AS varchar(10)) +
'Error state: ' + CAST(ERROR_STATE() AS varchar(10)) + 'XACT_STATE: ' + CAST(XACT_STATE() AS varchar(10))
--record the error in the database
--debug
--EXEC [dbo].[procUtils_DebugDb]
-- @DomainUser = @domainUser,
-- @debugmsg = @debugMsgOut,
-- @ret = 1,
-- @procedureName = @procedureName ,
-- @procedureStep = @procStep
-- set @ret = 1
END CATCH
return @ret
END --procedure end
Given a users culture is "sv-se"
When the user views a post list
It should list posts only in "sv-se" culture
SELECT * FROM Post WHERE CultureUI IN ('sv-se', 'en-us')
Label1.Text = GetLocalResourceObject("TokenStoredInDatabase").ToString()
* id
* price
* stocklevel
* active
* name
* shortdescription
* longdescription
* id
* products_id
* name
* shortdescription
* longdescription
string query = "";
if(string.IsNullOrEmpty(culture)) {
// No culture specified, no join needed.
query = "SELECT p.price, p.name, p.shortdescription FROM products p WHERE p.price > ?Price";
} else {
query = "SELECT p.price, case when pg.name is null then p.name else pg.name end as 'name', case when pg.shortdescription is null then p.shortdescription else pg.shortdescription end as 'shortdescription' FROM products p"
+ " LEFT JOIN products_globalization pg ON pg.products_id = p.id AND pg.culture = ?Culture"
+ " WHERE p.price > ?Price";
}