LINQ to SQL中数据库数据的本地化/ I18n

时间:2009-02-11 15:03:43

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

我的数据库中有状态表,而“本地化”表包含这些状态的特定于语言的版本。主状态表的要点是定义状态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);
}

5 个答案:

答案 0 :(得分:2)

就个人而言,我可能会将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

答案 1 :(得分:1)

一个选项可能是使用缓存应用程序块或ASP.NET缓存来维护本地化数据的缓存,然后在视图中引用该缓存。

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

答案 2 :(得分:0)

您可以在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的部分类上定义属性。

答案 3 :(得分:0)

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

我可能会使用i18n文本值的延迟(按需)缓存;这样:

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

我猜测i18n数据变化缓慢,因此缓存方法非常理想。

我会一次加载一个语言的所有相关字符串 - 所以第一次在威尔士语中需要一个状态(例如),我会加载所有威尔士语的状态字符串,并根据语言的标准代码(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)%>

答案 4 :(得分:0)

我找到了很好的解决方案。

'设计'如下:

  1. 使用PK ID创建一个名为“GlobalizedString”的表。
  2. 创建一个名为“LocalizedString”的表,其中包含对上述和以下字段的引用:
    • CultureId(参考一些文化查询表)
    • 内容(将使用上述语言的字符串)
  3. 在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属性在Linq2SQL上不起作用,我仍在寻找一种简单的方法来实现这一点(建议欢迎)。

    否则,系统正在为我们服务:)