Локализация / I18n данных базы данных в LINQ to SQL - PullRequest
1 голос
/ 11 февраля 2009

В моей базе данных есть таблицы состояния и «локализованные» таблицы, которые содержат языковые версии этих статусов. Смысл основной таблицы состояния состоит в том, чтобы определить значения идентификатора состояния и другие метаданные о состоянии. «Локализованная» таблица предназначена для отображения текстового представления в пользовательском интерфейсе в соответствии с предпочтительным языком пользователя. Вот пример схемы:

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 ]

2 голосов
/ 07 апреля 2009

Лично я бы, вероятно, оставил коды EmployeeStatus в БД и перенес бы всю логику локализации в клиент. Если это веб-приложение (ASP.NET или ASP.NET MVC), вы должны использовать код EmployeeStatus в качестве ключа в файле ресурсов, а затем использовать UICulture = "Auto" и Culture = "Auto", чтобы сообщить ASP .NET, чтобы подобрать нужные ресурсы на основе HTTP-заголовка «Accept-Language».

Вы бы предоставили ресурсы по умолчанию (без учета культуры), встроенные в ваше приложение, и позволили бы сборкам саталитов переопределять значения по умолчанию там, где они были необходимы.

Для меня проблема с добавлением локализации в БД заключается в том, что вы вначале сталкиваетесь с гораздо более сложными запросами, вы должны продолжать накачивать локаль в каждый из этих запросов и не можете кэшировать выходные данные запросы так широко. Во-вторых, у вас есть смесь таблиц с сущностями и таблиц с локализацией. Наконец, для выполнения локализации требуется администратор базы данных.

В идеале вы хотите, чтобы кто-то, кто понимает, как переводить текст, выполнял локализацию, и чтобы он использовал какой-то инструмент, который им удобен. Существует множество инструментов .resx и приложений, которые позволяют языковым экспертам «делать свое дело».

Если вы застряли с таблицами БД для локализации, потому что «так оно и есть», то, возможно, вам следует запросить поиски по отдельности к реальным данным и соединить их в пользовательском интерфейсе. Это как минимум даст вам «путь обновления» до .RESX в будущем.

Вам следует ознакомиться с книгой Гая Смита-Ферье на 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 голос
/ 12 февраля 2009

Одним из вариантов может быть сохранение кэша локализованных данных, используя что-то вроде Caching Application Block или ASP.NET, а затем просто ссылаться на этот кэш в представлении.

Это ограничит количество обращений к базе данных, поскольку LINQ может не понадобиться загружать записи о состоянии, чтобы получить локализованное описание.

0 голосов
/ 03 апреля 2009

Я нашел довольно хорошее решение для нашего сценария.

«Дизайн» выглядит так:

  1. Создайте таблицу с именем «GlobalizedString» с идентификатором PK.
  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.

Теперь вместо обычной «логики» (например, привязки) вы просто обращаетесь к свойству Content объекта GlobalizedString, чтобы получить содержимое для текущего языка:)

Как уже говорилось ранее, это свойство Content не работает в Linq2SQL, и я все еще ищу простой способ сделать это возможным (предложения приветствуются).

В остальном система нам хорошо служит:)

0 голосов
/ 03 апреля 2009

В стороне: в этом сценарии я бы рассмотрел трактовку статуса код (а не id) в качестве первичного ключа, а Code денормализовал в Employee; это сохраняет внешний ключ, но уменьшает количество необходимых соединений и переходов. Это также позволяет вам потенциально сопоставить код с enum в вашем .NET-коде.

Я бы, вероятно, использовал ленивый (по запросу) кэш текстовых значений i18n; это:

  • минимизирует сложность запроса
  • минимизирует пропускную способность / IO
  • минимизирует затраты на идентификацию объекта / отслеживание изменений
  • минимизирует количество «материализаций», которые происходят постоянно
  • позволяет программировать только для перечисления в объектной модели, но отображать текст i18n
  • абстрагирует реализацию i18n (чтобы вы могли переключиться на resx или аналогичный, если вам нужно, или на службу автоматического перевода)
  • позволяет получать значения i18n без привязки к существующему запросу к данным, т. Е. Заполнять раскрывающийся список для экрана поиска (который не имеет ничего общего с отдельными сотрудниками, поэтому пример запроса не помогает)

Я предполагаю, что данные i18n медленно меняются, поэтому подход к кешу идеален.

Я бы загружал все связанные строки языка за раз - поэтому, когда в первый раз требуется статус на валлийском языке (например), я бы загружал все статус строки для валлийского языка и кеш со стандартным кодом (cy) для языка.

Чтобы упростить код представления, рассмотрите возможность использования метода расширения для сотрудника (на уровне пользовательского интерфейса):

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)%>
0 голосов
/ 03 апреля 2009

Вы можете использовать LoadOptions в DataContext, поэтому он загружает данные в начальном запросе. Что-то вокруг линий:

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, который использует кеш.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...