【问题标题】:Localisation/I18n of database data in LINQ to SQLLINQ to SQL 中数据库数据的本地化/I18n
【发布时间】:2009-02-11 15:03:42
【问题描述】:

我的数据库中有状态表,以及包含这些状态的特定语言版本的“本地化”表。主状态表的要点是定义状态 ID 值,以及有关状态的其他元数据。 “本地化”表是根据用户的首选语言在 UI 中显示文本表示。这是一个示例架构:

create table [Language]
(
    ID smallint primary key,
    ISOName varchar(12)
)

create table EmployeeStatus
(
    ID smallint primary key,
    Code varchar(50)
)

create table EmployeeStatusLocalised
(
    EmployeeStatusID smallint,
    LanguageID smallint,
    Description varchar(50),
    constraint PK_EmployeeStatusLocalised primary key
        (EmployeeStatusID, LanguageID),
    constraint FK_EmployeeStatusLocalised_EmployeeStatus foreign key 
        (EmployeeStatusID) references EmployeeStatus (ID),
    constraint FK_EmployeeStatusLocalised_Language foreign key
        (LanguageID) references [Language] (ID)
)

create table Employee
(
    ID int identity(1,1) primary key,
    EmployeeName varchar(50) not null,
    EmployeeStatusID smallint not null,
    constraint FK_Employee_EmployeeStatus foreign key
        (EmployeeStatusID) references EmployeeStatus (ID)
)

这就是我通常访问该数据的方式:

select e.EmployeeName, esl.Description as EmployeeStatus
from Employee e
inner join EmployeeStatusLocalised esl on
    e.EmployeeStatusID = esl.EmployeeStatusID and esl.LanguageID = 1

不过,我并不很高兴我的 LINQ to SQL 以最有效的方式做事。这是一个例子:

using (var context = new MyDbDataContext())
{
    var item = (from record in context.Employees
                select record).Take(1).SingleOrDefault();

    Console.WriteLine("{0}: {1}", item.EmployeeName,
        item.EmployeeStatus.EmployeeStatusLocaliseds.
            Where(esl => esl.LanguageID == 1).Single().Description);
}

【问题讨论】:

    标签: c# .net linq linq-to-sql internationalization


    【解决方案1】:

    就个人而言,我可能会将 EmployeeStatus 代码留在数据库中,并将所有本地化逻辑移至客户端。如果这是一个 Web 应用程序(ASP.NET 或 ASP.NET MVC),那么您将使用 EmployeeStatus 代码作为资源文件的键,然后使用 UICulture="Auto" 和 Culture="Auto" 告诉 ASP .NET 根据“Accept-Language”HTTP 标头获取正确的资源。

    您将提供嵌入在您的应用中的默认(文化不敏感)资源,并允许 satalite 程序集在需要的地方覆盖默认值。

    对我来说,在数据库中添加本地化的问题在于,您首先会遇到更复杂的查询,您必须不断将语言环境注入到每个查询中,并且您无法缓存查询如此广泛。其次,您混合了包含实体的表和包含本地化的表。最后,需要一个 DBA 来进行本地化。

    理想情况下,您希望有人了解如何翻译文本来进行本地化,并让他们使用一些他们熟悉的工具。有很多 .resx 工具和允许语言专家“做他们的事”的应用程序。

    如果您因为“就是这样”而被数据库表困住进行本地化,那么也许您应该将查找单独查询到真实数据,然后在 UI 中将两者连接起来。这至少会为您将来提供一个“升级路径”到 .RESX。

    如果您对此领域感兴趣,应该查看 Guy Smith-Ferrier 关于 i18n 的书:

    http://www.amazon.co.uk/NET-Internationalization-Developers-Guide-Building/dp/0321341384/ref=sr_1_1?ie=UTF8&s=books&qid=1239106912&sr=8-1

    【讨论】:

      【解决方案2】:

      一种选择是维护本地化数据的缓存,使用缓存应用程序块或 ASP.NET 缓存之类的东西,然后只需在视图中引用该缓存。

      这将限制数据库调用的数量,因为 LINQ 可能不需要加载状态记录来获取本地化描述。

      【讨论】:

        【解决方案3】:

        您可以在 DataContext 中使用 LoadOptions,因此它会在初始查询中加载数据。线条周围的东西:

        var options = new DataLoadOptions();
        options.AssociateWith<Employee>(e=>
            e.EmployeeStatus.EmployeeStatusLocaliseds
            .Where(esl => esl.LanguageID == 1)
            );
        options.LoadWith<Employee>(e=>e.EmployeeStatus.EmployeeStatusLocaliseds);
        
            using (var context = new MyDbDataContext())
            {
                context.LoadOptions = options;
                var item = (from record in context.Employees
                            select record).Take(1).SingleOrDefault();
        
                Console.WriteLine("{0}: {1}", item.EmployeeName,
                    item.EmployeeStatus.EmployeeStatusLocaliseds
                    .Single().Description
                );
            }
        

        另一方面,状态可能几乎是静态数据,因此缓存它们会非常有效。如果您坚持使用生成的实体,您可以在使用缓存的部分 Employee 类上定义一个属性。

        【讨论】:

          【解决方案4】:

          除此之外:在这种情况下,我会考虑将状态 code(而不是 id)作为主键,并在 Employee 中对 Code 进行非规范化处理;这保留了外键,但减少了所需的连接和导航的数量。它还允许您将代码映射到 .NET 代码中的 enum

          我可能会使用 i18n 文本值的惰性(按需)缓存;这个:

          • 最小化查询复杂度
          • 最小化数据吞吐量/IO
          • 最大限度地减少对象身份/更改跟踪开销
          • 最大限度地减少不断发生的“实现”的数量
          • 允许您仅针对对象模型中的枚举进行编程,但显示 i18n 文本
          • 抽象了 i18n 实现(因此您可以根据需要切换到 resx 或类似的,或者自动翻译服务)
          • 允许您获取 i18n 值,而无需绑定到现有数据查询 - 即填充搜索屏幕的下拉框(与个别员工无关,因此示例查询没有帮助)

          我猜 i18n 数据变化缓慢,所以缓存方法是理想的。

          我会一次加载一种语言的所有 相关 字符串 - 所以第一次需要威尔士语的状态时(例如),我会加载 all威尔士语的状态字符串,并针对该语言的标准代码 (cy) 进行缓存。

          为了使视图代码更简单,请考虑在员工(在 UI 级别)使用扩展方法:

          public static class EmployeeExtensions {
              public static string GetStatusText(this Employee emp) {
                   /* do your funky thing, presumably using the HttpContext or
                   some other thread-static value to resolve the current culture */
              }
          }
          

          那么在你看来你可以使用:

          <%=emp.GetStatusText()%>
          

          等等。不幸的是,没有扩展属性,但是对于方法,您还可以选择将语言代码传递给方法(在添加参数之后):

          <%=emp.GetStatusText(lang)%>
          

          【讨论】:

            【解决方案5】:

            我为我们的场景找到了一个很好的解决方案。

            “设计”是这样的:

            1. 使用 PK ID 创建一个名为“GlobalizedString”的表。
            2. 创建一个名为“LocalizedString”的表,并引用上面和以下字段:
              • CultureId(参考一些文化查找表)
              • 内容(将采用上述语言的字符串)

            在 GlobalizedString 上,添加一个名为“Content”的属性(注意这不能通过 LINQ2SQL 查询,但可以在 LINQ2Objects 中使用),如下所示:

            public string Content
            {
              get { return LocalizedStrings.Single( 
                       x => x.Culture.CultureCode == 'my current CultureInfo's code'); }
              set { /* exercise for reader */ }
            }
            

            因此,您不再使用 nvarchar 列,而是指向 GlobalizedString 表。

            现在您可以使用普通的“逻辑”(例如绑定),只需参考 GlobalizedString 的 Content 属性即可获取当前语言的 Content :)

            如前所述,此 Content 属性不适用于 Linq2SQL,我仍在寻找一种简单的方法来实现这一点(欢迎提出建议)。

            否则,系统会很好地为我们服务 :)

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2011-01-29
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多